Skip to content

Commit 5571b83

Browse files
committed
[benchmark] Driver: log measurement metadata
Added --meta option to log measurement metadata: * PAGES – number of memory pages used * ICS – number of involuntary context switches * YIELD – number of voluntary yields (Pages and ICS were previously available only in --verbose mode.)
1 parent ec32140 commit 5571b83

File tree

2 files changed

+76
-22
lines changed

2 files changed

+76
-22
lines changed

benchmark/utils/DriverUtils.swift

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,23 @@ import LibProc
2121

2222
import TestsUtils
2323

24+
struct MeasurementMetadata {
25+
let maxRSS: Int /// Maximum Resident Set Size (B)
26+
let pages: Int /// Maximum Resident Set Size (pages)
27+
let ics: Int /// Involuntary Context Switches
28+
let vcs: Int /// Voluntary Context Switches
29+
let yields: Int /// Yield Count
30+
}
31+
2432
struct BenchResults {
2533
typealias T = Int
2634
private let samples: [T]
27-
let maxRSS: Int?
35+
let meta: MeasurementMetadata?
2836
let stats: Stats
2937

30-
init(_ samples: [T], maxRSS: Int?) {
38+
init(_ samples: [T], _ metadata: MeasurementMetadata?) {
3139
self.samples = samples.sorted()
32-
self.maxRSS = maxRSS
40+
self.meta = metadata
3341
self.stats = self.samples.reduce(into: Stats(), Stats.collect)
3442
}
3543

@@ -93,6 +101,9 @@ struct TestConfig {
93101
// Should we log the test's memory usage?
94102
let logMemory: Bool
95103

104+
// Should we log the measurement metadata?
105+
let logMeta: Bool
106+
96107
/// After we run the tests, should the harness sleep to allow for utilities
97108
/// like leaks that require a PID to run on the test harness.
98109
let afterRunSleep: UInt32?
@@ -116,6 +127,7 @@ struct TestConfig {
116127
var sampleTime: Double?
117128
var verbose: Bool?
118129
var logMemory: Bool?
130+
var logMeta: Bool?
119131
var action: TestAction?
120132
var tests: [String]?
121133
}
@@ -161,6 +173,8 @@ struct TestConfig {
161173
help: "increase output verbosity")
162174
p.addArgument("--memory", \.logMemory, defaultValue: true,
163175
help: "log the change in maximum resident set size (MAX_RSS)")
176+
p.addArgument("--meta", \.logMeta, defaultValue: true,
177+
help: "log the metadata (memory usage, context switches)")
164178
p.addArgument("--delim", \.delim,
165179
help:"value delimiter used for log output; default: ,",
166180
parser: { $0 })
@@ -191,6 +205,7 @@ struct TestConfig {
191205
delta = c.delta ?? false
192206
verbose = c.verbose ?? false
193207
logMemory = c.logMemory ?? false
208+
logMeta = c.logMeta ?? false
194209
afterRunSleep = c.afterRunSleep
195210
action = c.action ?? .run
196211
tests = TestConfig.filterTests(registeredBenchmarks,
@@ -217,6 +232,7 @@ struct TestConfig {
217232
MinSamples: \(minSamples ?? 0)
218233
Verbose: \(verbose)
219234
LogMemory: \(logMemory)
235+
LogMeta: \(logMeta)
220236
SampleTime: \(sampleTime)
221237
NumIters: \(numIters ?? 0)
222238
Quantile: \(quantile ?? 0)
@@ -371,6 +387,7 @@ final class TestRunner {
371387
var start, end, lastYield: Timer.TimeT
372388
let baseline = TestRunner.getResourceUtilization()
373389
let schedulerQuantum = UInt64(10_000_000) // nanoseconds (== 10ms, macos)
390+
var yieldCount = 0
374391

375392
init(_ config: TestConfig) {
376393
self.c = config
@@ -381,6 +398,7 @@ final class TestRunner {
381398
/// Offer to yield CPU to other processes and return current time on resume.
382399
func yield() -> Timer.TimeT {
383400
sched_yield()
401+
yieldCount += 1
384402
return timer.getTime()
385403
}
386404

@@ -414,38 +432,52 @@ final class TestRunner {
414432
var u = rusage(); getrusage(rusageSelf, &u); return u
415433
}
416434

417-
/// Returns maximum resident set size (MAX_RSS) delta in bytes.
435+
static let pageSize: Int = {
436+
#if canImport(Darwin)
437+
let pageSize = _SC_PAGESIZE
438+
#else
439+
let pageSize = Int32(_SC_PAGESIZE)
440+
#endif
441+
return sysconf(pageSize)
442+
}()
443+
444+
/// Returns metadata about the measurement, such as memory usage and number
445+
/// of context switches.
418446
///
419447
/// This method of estimating memory usage is valid only for executing single
420448
/// benchmark. That's why we don't worry about reseting the `baseline` in
421449
/// `resetMeasurements`.
422450
///
423451
/// FIXME: This current implementation doesn't work on Linux. It is disabled
424452
/// permanently to avoid linker errors. Feel free to fix.
425-
func measureMemoryUsage() -> Int? {
453+
func collectMetadata() -> MeasurementMetadata? {
426454
#if os(Linux)
427455
return nil
428456
#else
429-
guard c.logMemory else { return nil }
430457
let current = TestRunner.getResourceUtilization()
431-
let maxRSS = current.ru_maxrss - baseline.ru_maxrss
432-
#if canImport(Darwin)
433-
let pageSize = _SC_PAGESIZE
434-
#else
435-
let pageSize = Int32(_SC_PAGESIZE)
436-
#endif
437-
let pages = { maxRSS / sysconf(pageSize) }
458+
func delta(_ stat: KeyPath<rusage, Int>) -> Int {
459+
return current[keyPath: stat] - baseline[keyPath: stat]
460+
}
461+
let maxRSS = delta(\rusage.ru_maxrss)
462+
let pages = maxRSS / TestRunner.pageSize
438463
func deltaEquation(_ stat: KeyPath<rusage, Int>) -> String {
439464
let b = baseline[keyPath: stat], c = current[keyPath: stat]
440465
return "\(c) - \(b) = \(c - b)"
441466
}
442467
logVerbose(
443468
"""
444-
MAX_RSS \(deltaEquation(\rusage.ru_maxrss)) (\(pages()) pages)
469+
MAX_RSS \(deltaEquation(\rusage.ru_maxrss)) (\(pages) pages)
445470
ICS \(deltaEquation(\rusage.ru_nivcsw))
446471
VCS \(deltaEquation(\rusage.ru_nvcsw))
472+
yieldCount \(yieldCount)
447473
""")
448-
return maxRSS
474+
return MeasurementMetadata(
475+
maxRSS: maxRSS,
476+
pages: pages,
477+
ics: delta(\rusage.ru_nivcsw),
478+
vcs: delta(\rusage.ru_nvcsw),
479+
yields: yieldCount
480+
)
449481
#endif
450482
}
451483

@@ -469,6 +501,7 @@ final class TestRunner {
469501
private func resetMeasurements() {
470502
let now = yield()
471503
(start, end, lastYield) = (now, now, now)
504+
yieldCount = 0
472505
}
473506

474507
/// Time in nanoseconds spent running the last function
@@ -566,7 +599,7 @@ final class TestRunner {
566599
samples = samples.map { $0 * lf }
567600
}
568601

569-
return BenchResults(samples, maxRSS: measureMemoryUsage())
602+
return BenchResults(samples, collectMetadata())
570603
}
571604

572605
var header: String {
@@ -588,7 +621,8 @@ final class TestRunner {
588621
["#", "TEST", "SAMPLES"] +
589622
(c.quantile.map(quantiles)
590623
?? ["MIN", "MAX", "MEAN", "SD", "MEDIAN"].map(withUnit)) +
591-
(c.logMemory ? ["MAX_RSS(B)"] : [])
624+
(c.logMemory ? ["MAX_RSS(B)"] : []) +
625+
(c.logMeta ? ["PAGES", "ICS", "YIELD"] : [])
592626
).joined(separator: c.delim)
593627
}
594628

@@ -605,12 +639,14 @@ final class TestRunner {
605639
$0.encoded.append($1 - $0.last); $0.last = $1
606640
}.encoded : qs
607641
}
608-
return (
609-
[r.sampleCount] +
642+
let values: [Int] = [r.sampleCount] +
610643
(c.quantile.map(quantiles)
611644
?? [r.min, r.max, r.mean, r.sd, r.median]) +
612-
[r.maxRSS].compactMap { $0 }
613-
).map { (c.delta && $0 == 0) ? "" : String($0) } // drop 0s in deltas
645+
(c.logMemory ? [r.meta?.maxRSS].compactMap { $0 } : []) +
646+
(c.logMeta ? r.meta.map {
647+
[$0.pages, $0.ics, $0.yields] } ?? [] : [])
648+
return values.map {
649+
(c.delta && $0 == 0) ? "" : String($0) } // drop 0s in deltas
614650
}
615651
let benchmarkStats = (
616652
[index, t.name] + (results.map(values) ?? ["Unsupported"])

test/benchmark/Benchmark_O.test.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,22 @@ VENTILES: V7(μs),V8(μs),V9(μs),VA(μs),VB(μs),VC(μs),VD(μs),VE(μs),VF(μs
145145
VENTILES: VH(μs),VI(μs),VJ(μs),MAX(μs)
146146
````
147147

148+
### Reporting Measurement Metadata
149+
The presence of optional argument `--meta`, controls logging of measurement
150+
metadata at the end of the benchmark summary.
151+
152+
* PAGES – number of memory pages used
153+
* ICS – number of involuntary context switches
154+
* YIELD – number of voluntary yields
155+
156+
````
157+
RUN: %Benchmark_O 0 --quantile=1 --meta | %FileCheck %s --check-prefix META
158+
META: #,TEST,SAMPLES,MIN(μs),MAX(μs),PAGES,ICS,YIELD
159+
RUN: %Benchmark_O 0 --quantile=1 --meta --memory \
160+
RUN: | %FileCheck %s --check-prefix MEMMETA
161+
MEMMETA: #,TEST,SAMPLES,MIN(μs),MAX(μs),MAX_RSS(B),PAGES,ICS,YIELD
162+
````
163+
148164
### Verbose Mode
149165
Reports detailed information during measurement, including configuration
150166
details, environmental statistics (memory used and number of context switches)
@@ -162,7 +178,8 @@ RUN: | %FileCheck %s --check-prefix RUNJUSTONCE \
162178
RUN: --check-prefix CONFIG \
163179
RUN: --check-prefix LOGVERBOSE \
164180
RUN: --check-prefix MEASUREENV \
165-
RUN: --check-prefix LOGFORMAT
181+
RUN: --check-prefix LOGFORMAT \
182+
RUN: --check-prefix YIELDCOUNT
166183
CONFIG: NumSamples: 2
167184
CONFIG: Tests Filter: ["1", "Ackermann", "1", "AngryPhonebook"]
168185
CONFIG: Tests to run: Ackermann, AngryPhonebook
@@ -175,6 +192,7 @@ LOGVERBOSE: Sample 1,{{[0-9]+}}
175192
MEASUREENV: MAX_RSS {{[0-9]+}} - {{[0-9]+}} = {{[0-9]+}} ({{[0-9]+}} pages)
176193
MEASUREENV: ICS {{[0-9]+}} - {{[0-9]+}} = {{[0-9]+}}
177194
MEASUREENV: VCS {{[0-9]+}} - {{[0-9]+}} = {{[0-9]+}}
195+
YIELDCOUNT: yieldCount 1
178196
RUNJUSTONCE-LABEL: 1,Ackermann
179197
RUNJUSTONCE-NOT: 1,Ackermann
180198
LOGFORMAT: ,{{[0-9]+}},{{[0-9]+}},,{{[0-9]*}},{{[0-9]+}}

0 commit comments

Comments
 (0)