@@ -21,15 +21,23 @@ import LibProc
21
21
22
22
import TestsUtils
23
23
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
+
24
32
struct BenchResults {
25
33
typealias T = Int
26
34
private let samples : [ T ]
27
- let maxRSS : Int ?
35
+ let meta : MeasurementMetadata ?
28
36
let stats : Stats
29
37
30
- init ( _ samples: [ T ] , maxRSS : Int ? ) {
38
+ init ( _ samples: [ T ] , _ metadata : MeasurementMetadata ? ) {
31
39
self . samples = samples. sorted ( )
32
- self . maxRSS = maxRSS
40
+ self . meta = metadata
33
41
self . stats = self . samples. reduce ( into: Stats ( ) , Stats . collect)
34
42
}
35
43
@@ -93,6 +101,9 @@ struct TestConfig {
93
101
// Should we log the test's memory usage?
94
102
let logMemory : Bool
95
103
104
+ // Should we log the measurement metadata?
105
+ let logMeta : Bool
106
+
96
107
/// After we run the tests, should the harness sleep to allow for utilities
97
108
/// like leaks that require a PID to run on the test harness.
98
109
let afterRunSleep : UInt32 ?
@@ -116,6 +127,7 @@ struct TestConfig {
116
127
var sampleTime : Double ?
117
128
var verbose : Bool ?
118
129
var logMemory : Bool ?
130
+ var logMeta : Bool ?
119
131
var action : TestAction ?
120
132
var tests : [ String ] ?
121
133
}
@@ -161,6 +173,8 @@ struct TestConfig {
161
173
help: " increase output verbosity " )
162
174
p. addArgument ( " --memory " , \. logMemory, defaultValue: true ,
163
175
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) " )
164
178
p. addArgument ( " --delim " , \. delim,
165
179
help: " value delimiter used for log output; default: , " ,
166
180
parser: { $0 } )
@@ -191,6 +205,7 @@ struct TestConfig {
191
205
delta = c. delta ?? false
192
206
verbose = c. verbose ?? false
193
207
logMemory = c. logMemory ?? false
208
+ logMeta = c. logMeta ?? false
194
209
afterRunSleep = c. afterRunSleep
195
210
action = c. action ?? . run
196
211
tests = TestConfig . filterTests ( registeredBenchmarks,
@@ -217,6 +232,7 @@ struct TestConfig {
217
232
MinSamples: \( minSamples ?? 0 )
218
233
Verbose: \( verbose)
219
234
LogMemory: \( logMemory)
235
+ LogMeta: \( logMeta)
220
236
SampleTime: \( sampleTime)
221
237
NumIters: \( numIters ?? 0 )
222
238
Quantile: \( quantile ?? 0 )
@@ -371,6 +387,7 @@ final class TestRunner {
371
387
var start , end , lastYield : Timer . TimeT
372
388
let baseline = TestRunner . getResourceUtilization ( )
373
389
let schedulerQuantum = UInt64 ( 10_000_000 ) // nanoseconds (== 10ms, macos)
390
+ var yieldCount = 0
374
391
375
392
init ( _ config: TestConfig ) {
376
393
self . c = config
@@ -381,6 +398,7 @@ final class TestRunner {
381
398
/// Offer to yield CPU to other processes and return current time on resume.
382
399
func yield( ) -> Timer . TimeT {
383
400
sched_yield ( )
401
+ yieldCount += 1
384
402
return timer. getTime ( )
385
403
}
386
404
@@ -414,38 +432,52 @@ final class TestRunner {
414
432
var u = rusage ( ) ; getrusage ( rusageSelf, & u) ; return u
415
433
}
416
434
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.
418
446
///
419
447
/// This method of estimating memory usage is valid only for executing single
420
448
/// benchmark. That's why we don't worry about reseting the `baseline` in
421
449
/// `resetMeasurements`.
422
450
///
423
451
/// FIXME: This current implementation doesn't work on Linux. It is disabled
424
452
/// permanently to avoid linker errors. Feel free to fix.
425
- func measureMemoryUsage ( ) -> Int ? {
453
+ func collectMetadata ( ) -> MeasurementMetadata ? {
426
454
#if os(Linux)
427
455
return nil
428
456
#else
429
- guard c. logMemory else { return nil }
430
457
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
438
463
func deltaEquation( _ stat: KeyPath < rusage , Int > ) -> String {
439
464
let b = baseline [ keyPath: stat] , c = current [ keyPath: stat]
440
465
return " \( c) - \( b) = \( c - b) "
441
466
}
442
467
logVerbose (
443
468
"""
444
- MAX_RSS \( deltaEquation ( \rusage . ru_maxrss) ) ( \( pages ( ) ) pages)
469
+ MAX_RSS \( deltaEquation ( \rusage . ru_maxrss) ) ( \( pages) pages)
445
470
ICS \( deltaEquation ( \rusage . ru_nivcsw) )
446
471
VCS \( deltaEquation ( \rusage . ru_nvcsw) )
472
+ yieldCount \( yieldCount)
447
473
""" )
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
+ )
449
481
#endif
450
482
}
451
483
@@ -469,6 +501,7 @@ final class TestRunner {
469
501
private func resetMeasurements( ) {
470
502
let now = yield ( )
471
503
( start, end, lastYield) = ( now, now, now)
504
+ yieldCount = 0
472
505
}
473
506
474
507
/// Time in nanoseconds spent running the last function
@@ -566,7 +599,7 @@ final class TestRunner {
566
599
samples = samples. map { $0 * lf }
567
600
}
568
601
569
- return BenchResults ( samples, maxRSS : measureMemoryUsage ( ) )
602
+ return BenchResults ( samples, collectMetadata ( ) )
570
603
}
571
604
572
605
var header : String {
@@ -588,7 +621,8 @@ final class TestRunner {
588
621
[ " # " , " TEST " , " SAMPLES " ] +
589
622
( c. quantile. map ( quantiles)
590
623
?? [ " 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 " ] : [ ] )
592
626
) . joined ( separator: c. delim)
593
627
}
594
628
@@ -605,12 +639,14 @@ final class TestRunner {
605
639
$0. encoded. append ( $1 - $0. last) ; $0. last = $1
606
640
} . encoded : qs
607
641
}
608
- return (
609
- [ r. sampleCount] +
642
+ let values : [ Int ] = [ r. sampleCount] +
610
643
( c. quantile. map ( quantiles)
611
644
?? [ 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
614
650
}
615
651
let benchmarkStats = (
616
652
[ index, t. name] + ( results. map ( values) ?? [ " Unsupported " ] )
0 commit comments