Skip to content

Commit 49c1d8c

Browse files
committed
Add option to show benchmark charts
1 parent 96fb215 commit 49c1d8c

File tree

3 files changed

+146
-5
lines changed

3 files changed

+146
-5
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#if os(macOS)
13+
14+
import Charts
15+
import SwiftUI
16+
17+
struct BenchmarkChart: View {
18+
struct Comparison: Identifiable {
19+
var id = UUID()
20+
var name: String
21+
var baseline: BenchmarkResult
22+
var latest: BenchmarkResult
23+
}
24+
25+
var comparisons: [Comparison]
26+
27+
var body: some View {
28+
VStack(alignment: .leading) {
29+
ForEach(comparisons) { comparison in
30+
let new = comparison.latest.median.seconds
31+
let old = comparison.baseline.median.seconds
32+
Chart {
33+
chartBody(
34+
name: comparison.name,
35+
new: new,
36+
old: old,
37+
sampleCount: comparison.latest.samples)
38+
}
39+
.chartXAxis {
40+
AxisMarks { value in
41+
AxisTick()
42+
AxisValueLabel {
43+
Text(String(format: "%.5fs", value.as(Double.self)!))
44+
}
45+
}
46+
}
47+
.chartYAxis {
48+
AxisMarks { value in
49+
AxisGridLine()
50+
AxisValueLabel {
51+
HStack {
52+
Text(value.as(String.self)!)
53+
let delta = (new - old) / old * 100
54+
Text(String(format: "%+.2f%%", delta))
55+
.foregroundColor(delta <= 0 ? .green : .yellow)
56+
}
57+
}
58+
}
59+
}
60+
.frame(idealHeight: 60)
61+
}
62+
}
63+
}
64+
65+
@ChartContentBuilder
66+
func chartBody(
67+
name: String,
68+
new: TimeInterval,
69+
old: TimeInterval,
70+
sampleCount: Int
71+
) -> some ChartContent {
72+
// Baseline bar
73+
BarMark(
74+
x: .value("Time", old),
75+
y: .value("Name", "\(name) (\(sampleCount) samples)"))
76+
.position(by: .value("Kind", "Baseline"))
77+
.foregroundStyle(.gray)
78+
79+
// Latest result bar
80+
BarMark(
81+
x: .value("Time", new),
82+
y: .value("Name", "\(name) (\(sampleCount) samples)"))
83+
.position(by: .value("Kind", "Latest"))
84+
.foregroundStyle(LinearGradient(
85+
colors: [.accentColor, new - old <= 0 ? .green : .yellow],
86+
startPoint: .leading,
87+
endPoint: .trailing))
88+
89+
// Comparison
90+
RuleMark(x: .value("Time", new))
91+
.foregroundStyle(.gray)
92+
.lineStyle(.init(lineWidth: 0.5, dash: [2]))
93+
}
94+
}
95+
96+
struct BenchmarkResultApp: App {
97+
static var comparisons: [BenchmarkChart.Comparison]?
98+
99+
var body: some Scene {
100+
WindowGroup {
101+
if let comparisons = Self.comparisons {
102+
ScrollView {
103+
BenchmarkChart(comparisons: comparisons)
104+
}
105+
} else {
106+
Text("No data")
107+
}
108+
}
109+
}
110+
}
111+
112+
#endif

Sources/RegexBenchmark/BenchmarkRunner.swift

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ extension BenchmarkRunner {
8989
try results.save(to: url)
9090
}
9191

92-
func compare(against compareFilePath: String) throws {
92+
func compare(against compareFilePath: String, showChart: Bool) throws {
9393
let compareFileURL = URL(fileURLWithPath: compareFilePath)
9494
let compareResult = try SuiteResult.load(from: compareFileURL)
9595
let compareFile = compareFileURL.lastPathComponent
@@ -121,14 +121,40 @@ extension BenchmarkRunner {
121121
for item in improvements {
122122
printComparison(name: item.key, diff: item.value)
123123
}
124+
125+
#if os(macOS)
126+
if showChart {
127+
print("""
128+
=== Comparison chart =================================================================
129+
Press Control-C to close...
130+
""")
131+
BenchmarkResultApp.comparisons = {
132+
var comparisons: [BenchmarkChart.Comparison] = []
133+
for (name, baseline) in compareResult.results {
134+
if let latest = results.results[name] {
135+
comparisons.append(
136+
.init(name: name, baseline: baseline, latest: latest))
137+
}
138+
}
139+
return comparisons.sorted {
140+
let delta0 = Float($0.latest.median.seconds - $0.baseline.median.seconds)
141+
/ Float($0.baseline.median.seconds)
142+
let delta1 = Float($1.latest.median.seconds - $1.baseline.median.seconds)
143+
/ Float($1.baseline.median.seconds)
144+
return delta0 > delta1
145+
}
146+
}()
147+
BenchmarkResultApp.main()
148+
}
149+
#endif
124150
}
125151
}
126152

127153
struct BenchmarkResult: Codable {
128154
let median: Time
129155
let stdev: Double
130156
let samples: Int
131-
157+
132158
init(_ median: Time, _ stdev: Double, _ samples: Int) {
133159
self.median = median
134160
self.stdev = stdev

Sources/RegexBenchmark/CLI.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ struct Runner: ParsableCommand {
1717
@Option(help: "The result file to compare against")
1818
var compare: String?
1919

20+
@Flag(help: "Show comparison chart")
21+
var showChart: Bool = false
22+
2023
@Flag(help: "Quiet mode")
2124
var quiet = false
2225

@@ -40,12 +43,12 @@ struct Runner: ParsableCommand {
4043
runner.suite = runner.suite.filter { b in !b.name.contains("NS") }
4144
}
4245
runner.run()
43-
if let compareFile = compare {
44-
try runner.compare(against: compareFile)
45-
}
4646
if let saveFile = save {
4747
try runner.save(to: saveFile)
4848
}
49+
if let compareFile = compare {
50+
try runner.compare(against: compareFile, showChart: showChart)
51+
}
4952
}
5053
}
5154
}

0 commit comments

Comments
 (0)