Skip to content

Commit 8e48f03

Browse files
authored
Fix missing time unit conversions in JSON and CSV (#54)
This change makes JSON and CSV report results in time adjusted time units (i.e. adjust ns to ms based on `--time-unit ms`). Previously, only Console reporter would adjust time units. Now the functionality is factored out and is used by all reporters uniformly.
1 parent cce6720 commit 8e48f03

File tree

3 files changed

+157
-44
lines changed

3 files changed

+157
-44
lines changed

Sources/Benchmark/BenchmarkColumn.swift

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,21 @@ public struct BenchmarkColumn: Hashable {
2424
/// Function to compute a value for each cell based on results.
2525
public let value: (BenchmarkResult) -> Double
2626

27+
/// Unit of the column value.
28+
public let unit: Unit
29+
2730
/// Visual alignment to either left or right side of the column.
2831
public let alignment: Alignment
2932

3033
/// Formatter function for pretty human-readable console output.
3134
public let formatter: Formatter
3235

36+
public enum Unit: Hashable {
37+
case time
38+
case inverseTime
39+
case none
40+
}
41+
3342
public enum Alignment: Hashable {
3443
case left
3544
case right
@@ -38,13 +47,26 @@ public struct BenchmarkColumn: Hashable {
3847
public init(
3948
name: String,
4049
value: @escaping (BenchmarkResult) -> Double,
41-
alignment: Alignment,
42-
formatter: @escaping Formatter = BenchmarkFormatter.number
50+
unit: Unit = .none,
51+
alignment: Alignment = .right,
52+
formatter optionalFormatter: Formatter? = nil
4353
) {
4454
self.name = name
4555
self.value = value
56+
self.unit = unit
4657
self.alignment = alignment
47-
self.formatter = formatter
58+
if let formatter = optionalFormatter {
59+
self.formatter = formatter
60+
} else {
61+
switch unit {
62+
case .time:
63+
self.formatter = BenchmarkFormatter.time
64+
case .inverseTime:
65+
self.formatter = BenchmarkFormatter.inverseTime
66+
case .none:
67+
self.formatter = BenchmarkFormatter.number
68+
}
69+
}
4870
}
4971

5072
/// Registry that represents a mapping from known column
@@ -62,30 +84,25 @@ public struct BenchmarkColumn: Hashable {
6284
result["time"] = BenchmarkColumn(
6385
name: "time",
6486
value: { $0.measurements.median },
65-
alignment: .right,
66-
formatter: BenchmarkFormatter.time)
87+
unit: .time)
6788
result["std"] = BenchmarkColumn(
6889
name: "std",
6990
value: { $0.measurements.std / $0.measurements.median * 100 },
7091
alignment: .left,
7192
formatter: BenchmarkFormatter.stdPercentage)
7293
result["iterations"] = BenchmarkColumn(
7394
name: "iterations",
74-
value: { Double($0.measurements.count) },
75-
alignment: .right,
76-
formatter: BenchmarkFormatter.number)
95+
value: { Double($0.measurements.count) })
7796
result["warmup"] = BenchmarkColumn(
7897
name: "warmup",
7998
value: { $0.warmupMeasurements.sum },
80-
alignment: .right,
81-
formatter: BenchmarkFormatter.time)
99+
unit: .time)
82100

83101
// Opt-in alternative columns.
84102
result["median"] = BenchmarkColumn(
85103
name: "median",
86104
value: { $0.measurements.median },
87-
alignment: .right,
88-
formatter: BenchmarkFormatter.time)
105+
unit: .time)
89106
result["min"] = BenchmarkColumn(
90107
name: "min",
91108
value: { result in
@@ -95,8 +112,7 @@ public struct BenchmarkColumn: Hashable {
95112
return (0)
96113
}
97114
},
98-
alignment: .right,
99-
formatter: BenchmarkFormatter.time)
115+
unit: .time)
100116
result["max"] = BenchmarkColumn(
101117
name: "max",
102118
value: { result in
@@ -106,23 +122,19 @@ public struct BenchmarkColumn: Hashable {
106122
return (0)
107123
}
108124
},
109-
alignment: .right,
110-
formatter: BenchmarkFormatter.time)
125+
unit: .time)
111126
result["total"] = BenchmarkColumn(
112127
name: "total",
113128
value: { ($0.measurements.sum) },
114-
alignment: .right,
115-
formatter: BenchmarkFormatter.time)
129+
unit: .time)
116130
result["avg"] = BenchmarkColumn(
117131
name: "avg",
118132
value: { ($0.measurements.average) },
119-
alignment: .right,
120-
formatter: BenchmarkFormatter.time)
133+
unit: .time)
121134
result["average"] = BenchmarkColumn(
122135
name: "avg",
123136
value: { ($0.measurements.average) },
124-
alignment: .right,
125-
formatter: BenchmarkFormatter.time)
137+
unit: .time)
126138
result["std_abs"] = BenchmarkColumn(
127139
name: "std_abs",
128140
value: { ($0.measurements.std) },
@@ -139,8 +151,7 @@ public struct BenchmarkColumn: Hashable {
139151
result[name] = BenchmarkColumn(
140152
name: name,
141153
value: { ($0.measurements.percentile(v)) },
142-
alignment: .left,
143-
formatter: BenchmarkFormatter.time)
154+
unit: .time)
144155
}
145156

146157
return result
@@ -234,10 +245,29 @@ public struct BenchmarkColumn: Hashable {
234245
}
235246
} else {
236247
let value = column.value(result)
248+
let adjustedValue: Double
249+
switch column.unit {
250+
case .time:
251+
switch result.settings.timeUnit {
252+
case .ns: adjustedValue = value
253+
case .us: adjustedValue = value / 1000.0
254+
case .ms: adjustedValue = value / 1000_000.0
255+
case .s: adjustedValue = value / 1000_000_000.0
256+
}
257+
case .inverseTime:
258+
switch result.settings.inverseTimeUnit {
259+
case .ns: adjustedValue = value
260+
case .us: adjustedValue = value * 1000.0
261+
case .ms: adjustedValue = value * 1000_000.0
262+
case .s: adjustedValue = value * 1000_000_000.0
263+
}
264+
case .none:
265+
adjustedValue = value
266+
}
237267
if pretty {
238-
content = column.formatter(value, result.settings)
268+
content = column.formatter(adjustedValue, result.settings)
239269
} else {
240-
content = String(value)
270+
content = String(adjustedValue)
241271
}
242272
}
243273
row[column] = content

Sources/Benchmark/BenchmarkFormatter.swift

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,12 @@ public enum BenchmarkFormatter {
2828

2929
/// Show number with the corresponding time unit.
3030
public static let time: Formatter = { (value, settings) in
31-
switch settings.timeUnit {
32-
case .ns: return "\(value) ns"
33-
case .us: return "\(value/1000.0) us"
34-
case .ms: return "\(value/1000_000.0) ms"
35-
case .s: return "\(value/1000_000_000.0) s"
36-
}
31+
return "\(value) \(settings.timeUnit)"
3732
}
3833

3934
/// Show number with the corresponding inverse time unit.
4035
public static let inverseTime: Formatter = { (value, settings) in
41-
switch settings.inverseTimeUnit {
42-
case .ns: return "\(value) /ns"
43-
case .us: return "\(value*1000.0) /us"
44-
case .ms: return "\(value*1000_000.0) /ms"
45-
case .s: return "\(value*1000_000_000.0) /s"
46-
}
36+
return "\(value) /\(settings.inverseTimeUnit)"
4737
}
4838

4939
/// Show value as percentage.

Tests/BenchmarkTests/BenchmarkReporterTests.swift

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ final class BenchmarkReporterTests: XCTestCase {
8282
assertConsoleReported(results, expected)
8383
}
8484

85-
func testConsoleCountersAreReported() throws {
85+
func testConsoleCounters() throws {
8686
let results: [BenchmarkResult] = [
8787
BenchmarkResult(
8888
benchmarkName: "fast", suiteName: "MySuite",
@@ -106,7 +106,7 @@ final class BenchmarkReporterTests: XCTestCase {
106106
assertConsoleReported(results, expected)
107107
}
108108

109-
func testConsoleWarmupReported() throws {
109+
func testConsoleWarmup() throws {
110110
let results: [BenchmarkResult] = [
111111
BenchmarkResult(
112112
benchmarkName: "fast", suiteName: "MySuite",
@@ -130,7 +130,7 @@ final class BenchmarkReporterTests: XCTestCase {
130130
assertConsoleReported(results, expected)
131131
}
132132

133-
func testConsoleTimeUnitReported() throws {
133+
func testConsoleTimeUnit() throws {
134134
let results: [BenchmarkResult] = [
135135
BenchmarkResult(
136136
benchmarkName: "ns", suiteName: "MySuite",
@@ -248,6 +248,59 @@ final class BenchmarkReporterTests: XCTestCase {
248248
assertJSONReported(results, expected, settings: settings)
249249
}
250250

251+
func testJSONTimeUnit() throws {
252+
let results: [BenchmarkResult] = [
253+
BenchmarkResult(
254+
benchmarkName: "ns", suiteName: "MySuite",
255+
settings: BenchmarkSettings([TimeUnit(.ns)]),
256+
measurements: [123_456_789],
257+
warmupMeasurements: [],
258+
counters: [:]),
259+
BenchmarkResult(
260+
benchmarkName: "us", suiteName: "MySuite",
261+
settings: BenchmarkSettings([TimeUnit(.us)]),
262+
measurements: [123_456_789],
263+
warmupMeasurements: [],
264+
counters: [:]),
265+
BenchmarkResult(
266+
benchmarkName: "ms", suiteName: "MySuite",
267+
settings: BenchmarkSettings([TimeUnit(.ms)]),
268+
measurements: [123_456_789],
269+
warmupMeasurements: [],
270+
counters: [:]),
271+
BenchmarkResult(
272+
benchmarkName: "s", suiteName: "MySuite",
273+
settings: BenchmarkSettings([TimeUnit(.s)]),
274+
measurements: [123_456_789],
275+
warmupMeasurements: [],
276+
counters: [:]),
277+
]
278+
let expected = #"""
279+
{
280+
"benchmarks": [
281+
{
282+
"name": "MySuite.ns",
283+
"time": 123456789.0
284+
},
285+
{
286+
"name": "MySuite.us",
287+
"time": 123456.789
288+
},
289+
{
290+
"name": "MySuite.ms",
291+
"time": 123.456789
292+
},
293+
{
294+
"name": "MySuite.s",
295+
"time": 0.123456789
296+
}
297+
]
298+
}
299+
"""#
300+
let settings = BenchmarkSettings([Columns(["name", "time"])])
301+
assertJSONReported(results, expected, settings: settings)
302+
}
303+
251304
func testCSVEmpty() {
252305
let results: [BenchmarkResult] = []
253306
let expected = #"""
@@ -301,16 +354,56 @@ final class BenchmarkReporterTests: XCTestCase {
301354
assertCSVReported(results, expected, settings: settings)
302355
}
303356

357+
func testCSVTimeUnit() throws {
358+
let results: [BenchmarkResult] = [
359+
BenchmarkResult(
360+
benchmarkName: "ns", suiteName: "MySuite",
361+
settings: BenchmarkSettings([TimeUnit(.ns)]),
362+
measurements: [123_456_789],
363+
warmupMeasurements: [],
364+
counters: [:]),
365+
BenchmarkResult(
366+
benchmarkName: "us", suiteName: "MySuite",
367+
settings: BenchmarkSettings([TimeUnit(.us)]),
368+
measurements: [123_456_789],
369+
warmupMeasurements: [],
370+
counters: [:]),
371+
BenchmarkResult(
372+
benchmarkName: "ms", suiteName: "MySuite",
373+
settings: BenchmarkSettings([TimeUnit(.ms)]),
374+
measurements: [123_456_789],
375+
warmupMeasurements: [],
376+
counters: [:]),
377+
BenchmarkResult(
378+
benchmarkName: "s", suiteName: "MySuite",
379+
settings: BenchmarkSettings([TimeUnit(.s)]),
380+
measurements: [123_456_789],
381+
warmupMeasurements: [],
382+
counters: [:]),
383+
]
384+
let expected = #"""
385+
name,time
386+
MySuite.ns,123456789.0
387+
MySuite.us,123456.789
388+
MySuite.ms,123.456789
389+
MySuite.s,0.123456789
390+
"""#
391+
let settings = BenchmarkSettings([Columns(["name", "time"])])
392+
assertCSVReported(results, expected, settings: settings)
393+
}
394+
304395
static var allTests = [
305396
("testConsoleBasic", testConsoleBasic),
306-
("testConsoleCountersAreReported", testConsoleCountersAreReported),
307-
("testConsoleWarmupReported", testConsoleWarmupReported),
308-
("testConsoleTimeUnitReported", testConsoleTimeUnitReported),
397+
("testConsoleCounters", testConsoleCounters),
398+
("testConsoleWarmup", testConsoleWarmup),
399+
("testConsoleTimeUnit", testConsoleTimeUnit),
309400
("testJSONEmpty", testJSONEmpty),
310401
("testJSONBasic", testJSONBasic),
311402
("testJSONEscape", testJSONEscape),
403+
("testJSONTimeUnit", testJSONTimeUnit),
312404
("testCSVEmpty", testCSVEmpty),
313405
("testCSVBasic", testCSVBasic),
314406
("testCSVEscape", testCSVEscape),
407+
("testCSVTimeUnit", testCSVTimeUnit),
315408
]
316409
}

0 commit comments

Comments
 (0)