File: Benchmark.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (147 lines) | stat: -rw-r--r-- 5,232 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
 Licensed under Apache License v2.0 with Runtime Library Exception

 See https://swift.org/LICENSE.txt for license information
 See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import Foundation

/// A logger that runs benchmarks and stores the results.
public class Benchmark: Encodable {
    /// True if the process is supposed to run benchmarks.
    public let isEnabled: Bool
    
    /// If defined, filter the metrics to log with that value.
    public let metricsFilter: String?
    
    /// Creates a new benchmark log that can store metric results.
    /// - Parameters:
    ///   - isEnabled: If `true`, store metrics in the data model.
    ///   - metricsFilter: If set, filter the logged metrics with this string.
    ///   If `nil`, all metrics are stored in the log.
    init(isEnabled: Bool = true, metricsFilter: String? = nil) {
        self.isEnabled = isEnabled
        self.metricsFilter = metricsFilter
    }
    
    /// The shared instance to use for logging.
    public static let main: Benchmark = Benchmark(
        isEnabled: ProcessInfo.processInfo.environment["DOCC_BENCHMARK"] == "YES",
        metricsFilter: ProcessInfo.processInfo.environment["DOCC_BENCHMARK_FILTER"]
    )

    /// The benchmark timestamp.
    public let date = Date()

    /// The benchmark platform name.
    #if os(macOS)
    public let platform = "macOS"
    #elseif os(iOS)
    public let platform = "iOS"
    #elseif os(Linux)
    public let platform = "Linux"
    #elseif os(Android)
    public let platform = "Android"
    #else
    public let platform = "unsupported"
    #endif

    /// The list of metrics included in this benchmark.
    public var metrics: [BenchmarkMetric] = []
    
    enum CodingKeys: String, CodingKey {
        case date, metrics, arguments, platform
    }
    
    /// Prepare the gathered measurements into a benchmark results.
    ///
    /// The prepared benchmark results are sorted in a stable order that's suitable for presentation.
    ///
    /// - Returns: The prepared benchmark results for all the gathered metrics.
    public func results() -> BenchmarkResults? {
        guard isEnabled else { return nil }
        
        let metrics = metrics.compactMap { log -> BenchmarkResults.Metric? in
            guard let result = log.result else {
                return nil
            }
            let id = (log as? DynamicallyIdentifiableMetric)?.identifier ?? type(of: log).identifier
            let displayName = (log as? DynamicallyIdentifiableMetric)?.displayName ?? type(of: log).displayName
            return .init(id: id, displayName: displayName, value: result)
        }
        
        return BenchmarkResults(
            platformName: platform,
            timestamp: date,
            doccArguments: Array(CommandLine.arguments.dropFirst()),
            unorderedMetrics: metrics
        )
    }
    
    public func encode(to encoder: Encoder) throws {
        try results()?.encode(to: encoder)
    }
}

private extension Benchmark {
    func shouldLogMetricType(_ metricType: BenchmarkMetric.Type) -> Bool {
        return isEnabled && (metricsFilter == nil || metricType.identifier.hasPrefix(metricsFilter!))
    }
}

/// Logs a one-off metric value.
/// - Parameters:
///   - metric: The one-off metric
///   - log: The log to add the metric to.
public func benchmark<E>(add metric: @autoclosure () -> E, benchmarkLog log: Benchmark = .main) where E: BenchmarkMetric {
    guard log.shouldLogMetricType(E.self) else { return }

    log.metrics.append(metric())
}

/// Begins the given metric.
/// - Parameters:
///   - metric: The metric to begin measuring.
///   - log: The log that may filter out the metric.
public func benchmark<E>(begin metric: @autoclosure () -> E, benchmarkLog log: Benchmark = .main) -> E? where E: BenchmarkBlockMetric {
    guard log.shouldLogMetricType(E.self) else { return nil }

    let metric = metric()
    metric.begin()
    return metric
}

/// Ends the given metric and adds it to the log.
/// - Parameters:
///   - metric: The metric to end and log.
///   - log: The log to add the metric to.
public func benchmark<E>(end metric: @autoclosure () -> E?, benchmarkLog log: Benchmark = .main) where E: BenchmarkBlockMetric {
    guard log.shouldLogMetricType(E.self), let metric = metric() else { return }

    metric.end()
    log.metrics.append(metric)
}

@discardableResult
/// Measures a metric around the given closure.
/// - Parameters:
///   - metric: The metric to measure and log.
///   - log: The log to add the metric to.
///   - body: The closure around which to measure the metric.
/// - Returns: The return value from the closure.
public func benchmark<E, Result>(wrap metric: @autoclosure () -> E, benchmarkLog log: Benchmark = .main, body: () throws -> Result) rethrows -> Result where E: BenchmarkBlockMetric {
    if log.shouldLogMetricType(E.self) {
        let event = metric()
        event.begin()
        let result = try body()
        event.end()
        log.metrics.append(event)
        return result
    } else {
        return try body()
    }
}