8
8
// http://www.apache.org/licenses/LICENSE-2.0
9
9
//
10
10
11
+ // swiftlint:disable file_length type_body_length line_length identifier_name
12
+
13
+ import Foundation
11
14
import Numerics
12
- import TextTable
13
15
14
16
/**
15
17
* Number of significant digits for values recorded in histogram.
@@ -49,7 +51,6 @@ public enum HistogramOutputFormat {
49
51
* they are encountered. Note that recording calls that cause auto-resizing may take longer to execute, as resizing
50
52
* incurs allocation and copying of internal data structures.
51
53
*/
52
-
53
54
public struct Histogram < Count: FixedWidthInteger > {
54
55
/// The lowest value that can be discerned (distinguished from 0) by the histogram.
55
56
public let lowestDiscernibleValue : UInt64
@@ -69,17 +70,13 @@ public struct Histogram<Count: FixedWidthInteger> {
69
70
// Biggest value that can fit in bucket 0
70
71
let subBucketMask : UInt64
71
72
72
- @usableFromInline
73
- var maxValue : UInt64 = 0
73
+ @usableFromInline var maxValue : UInt64 = 0
74
74
75
- @usableFromInline
76
- var minNonZeroValue : UInt64 = . max
75
+ @usableFromInline var minNonZeroValue : UInt64 = . max
77
76
78
- @usableFromInline
79
- var counts : [ Count ]
77
+ @usableFromInline var counts : [ Count ]
80
78
81
- @usableFromInline
82
- var _totalCount : UInt64 = 0
79
+ @usableFromInline var _totalCount : UInt64 = 0
83
80
84
81
/// Total count of all recorded values in the histogram
85
82
public var totalCount : UInt64 { _totalCount }
@@ -88,7 +85,7 @@ public struct Histogram<Count: FixedWidthInteger> {
88
85
public let numberOfSignificantValueDigits : SignificantDigits
89
86
90
87
/// Control whether or not the histogram can auto-resize and auto-adjust its ``highestTrackableValue``.
91
- public var autoResize : Bool = false
88
+ public var autoResize = false
92
89
93
90
let subBucketHalfCountMagnitude : UInt8
94
91
@@ -167,7 +164,7 @@ public struct Histogram<Count: FixedWidthInteger> {
167
164
// fits in 62 bits is debatable, and it makes it harder to work through the logic.
168
165
// Sums larger than 64 are totally broken as leadingZeroCountBase would go negative.
169
166
precondition ( unitMagnitude + subBucketHalfCountMagnitude <= 61 ,
170
- " Invalid arguments: Cannot represent numberOfSignificantValueDigits worth of values beyond lowestDiscernibleValue " )
167
+ " Invalid arguments: Cannot represent numberOfSignificantValueDigits worth of values beyond lowestDiscernibleValue " )
171
168
172
169
// Establish leadingZeroCountBase, used in bucketIndexForValue() fast path:
173
170
// subtract the bits that would be used by the largest value in bucket 0.
@@ -226,8 +223,12 @@ public struct Histogram<Count: FixedWidthInteger> {
226
223
return false
227
224
}
228
225
229
- if index >= counts. count && autoResize {
230
- resize ( newHighestTrackableValue: value)
226
+ if index >= counts. count {
227
+ if autoResize {
228
+ resize ( newHighestTrackableValue: value)
229
+ } else {
230
+ return false
231
+ }
231
232
}
232
233
233
234
incrementCountForIndex ( index, by: count)
@@ -472,7 +473,7 @@ public struct Histogram<Count: FixedWidthInteger> {
472
473
* - Returns: The mean value (in value units) of the histogram data.
473
474
*/
474
475
public var mean : Double {
475
- if ( totalCount == 0 ) {
476
+ if totalCount == 0 {
476
477
return 0.0
477
478
}
478
479
var totalValue : Double = 0
@@ -488,7 +489,7 @@ public struct Histogram<Count: FixedWidthInteger> {
488
489
* - Returns: The standard deviation (in value units) of the histogram data.
489
490
*/
490
491
public var stdDeviation : Double {
491
- if ( totalCount == 0 ) {
492
+ if totalCount == 0 {
492
493
return 0.0
493
494
}
494
495
@@ -515,7 +516,7 @@ public struct Histogram<Count: FixedWidthInteger> {
515
516
/**
516
517
* Represents a value point iterated through in a Histogram, with associated stats.
517
518
*/
518
- public struct IterationValue {
519
+ public struct IterationValue : Equatable {
519
520
/**
520
521
* The actual value level that was iterated to by the iterator.
521
522
*/
@@ -537,6 +538,15 @@ public struct Histogram<Count: FixedWidthInteger> {
537
538
*/
538
539
public let percentile : Double
539
540
541
+ /**
542
+ * The percentile level that the iterator returning this ``IterationValue`` had iterated to.
543
+ * Generally, `percentileLevelIteratedTo` will be equal to or smaller than `percentile`,
544
+ * but the same value point can contain multiple iteration levels for some iterators. E.g. a
545
+ * percentile iterator can stop multiple times in the exact same value point (if the count at
546
+ * that value covers a range of multiple percentiles in the requested percentile iteration points).
547
+ */
548
+ public let percentileLevelIteratedTo : Double
549
+
540
550
/**
541
551
* The count of recorded values in the histogram that were added to the ``totalCountToThisValue`` as a result
542
552
* on this iteration step. Since multiple iteration steps may occur with overlapping equivalent value ranges,
@@ -577,7 +587,7 @@ public struct Histogram<Count: FixedWidthInteger> {
577
587
578
588
var countAtThisValue : Count = 0
579
589
580
- private var freshSubBucket : Bool = true
590
+ private var freshSubBucket = true
581
591
582
592
init ( histogram: Histogram ) {
583
593
self . histogram = histogram
@@ -600,16 +610,19 @@ public struct Histogram<Count: FixedWidthInteger> {
600
610
}
601
611
}
602
612
603
- mutating func makeIterationValueAndUpdatePrev( value: UInt64 ? = nil ) -> IterationValue {
613
+ mutating func makeIterationValueAndUpdatePrev( value: UInt64 ? = nil , percentileIteratedTo : Double ? = nil ) -> IterationValue {
604
614
let valueIteratedTo = value ?? self . valueIteratedTo
605
615
606
616
defer {
607
617
prevValueIteratedTo = valueIteratedTo
608
618
totalCountToPrevIndex = totalCountToCurrentIndex
609
619
}
610
620
611
- return IterationValue ( value: valueIteratedTo, prevValue: prevValueIteratedTo, count: countAtThisValue,
612
- percentile: ( 100.0 * Double( totalCountToCurrentIndex) ) / Double( arrayTotalCount) ,
621
+ let percentile = ( 100.0 * Double( totalCountToCurrentIndex) ) / Double( arrayTotalCount)
622
+
623
+ return IterationValue (
624
+ value: valueIteratedTo, prevValue: prevValueIteratedTo, count: countAtThisValue,
625
+ percentile: percentile, percentileLevelIteratedTo: percentileIteratedTo ?? percentile,
613
626
countAddedInThisIterationStep: totalCountToCurrentIndex - totalCountToPrevIndex,
614
627
totalCountToThisValue: totalCountToCurrentIndex, totalValueToThisValue: totalValueToCurrentIndex)
615
628
}
@@ -665,7 +678,7 @@ public struct Histogram<Count: FixedWidthInteger> {
665
678
defer {
666
679
incrementIterationLevel ( )
667
680
}
668
- return impl. makeIterationValueAndUpdatePrev ( )
681
+ return impl. makeIterationValueAndUpdatePrev ( percentileIteratedTo : percentileLevelToIterateTo )
669
682
}
670
683
impl. incrementSubBucket ( )
671
684
}
@@ -1012,69 +1025,66 @@ public struct Histogram<Count: FixedWidthInteger> {
1012
1025
outputValueUnitScalingRatio: Double ,
1013
1026
percentileTicksPerHalfDistance ticks: Int = 5 ,
1014
1027
format: HistogramOutputFormat = . plainText) {
1028
+ // small helper to pad strings to specific widths, for some reason "%10s"/"%10@" doesn't work in String.init(format:)
1029
+ func padded( _ s: String , to: Int ) -> String {
1030
+ if s. count < to {
1031
+ return String ( repeating: " " , count: to - s. count) + s
1032
+ }
1033
+ return s
1034
+ }
1015
1035
1016
1036
if format == . csv {
1017
- return outputPercentileDistributionCsv ( to: & stream, outputValueUnitScalingRatio: outputValueUnitScalingRatio, percentileTicksPerHalfDistance: ticks)
1037
+ stream. write ( " \" Value \" , \" Percentile \" , \" TotalCount \" , \" 1/(1-Percentile) \" \n " )
1038
+ } else {
1039
+ stream. write ( " \( padded ( " Value " , to: 12 ) ) \( padded ( " Percentile " , to: 14 ) ) \( padded ( " TotalCount " , to: 10 ) ) \( padded ( " 1/(1-Percentile) " , to: 14 ) ) \n \n " )
1018
1040
}
1019
1041
1020
- let table = TextTable< IterationValue> {
1021
- let lastLine = ( $0. percentile == 100.0 )
1042
+ let percentileFormatString = format == . csv ?
1043
+ " %. \( numberOfSignificantValueDigits. rawValue) f,%.12f,%d,%.2f \n " :
1044
+ " %12. \( numberOfSignificantValueDigits. rawValue) f %2.12f %10d %14.2f \n "
1022
1045
1023
- return [
1024
- Column ( " Value " <- " %. \( self . numberOfSignificantValueDigits. rawValue) f " . format ( Double ( $0. value) / outputValueUnitScalingRatio) , width: 12 , align: . right) ,
1025
- Column ( " Percentile " <- " %.12f " . format ( $0. percentile / 100.0 ) , width: 14 , align: . right) ,
1026
- Column ( " TotalCount " <- $0. totalCountToThisValue, width: 10 , align: . right) ,
1027
- Column ( " 1/(1-Percentile) " <- ( lastLine ? " " : " %.2f " . format ( 1.0 / ( 1.0 - ( $0. percentile / 100.0 ) ) ) ) , align: . right)
1028
- ]
1029
- }
1030
-
1031
- let data = [ IterationValue] ( percentiles ( ticksPerHalfDistance: ticks) )
1032
- stream. write ( table. string ( for: data) ?? " unable to render percentile table " )
1033
-
1034
- // Calculate and output mean and std. deviation.
1035
- // Note: mean/std. deviation numbers are very often completely irrelevant when
1036
- // data is extremely non-normal in distribution (e.g. in cases of strong multi-modal
1037
- // response time distribution associated with GC pauses). However, reporting these numbers
1038
- // can be very useful for contrasting with the detailed percentile distribution
1039
- // reported by outputPercentileDistribution(). It is not at all surprising to find
1040
- // percentile distributions where results fall many tens or even hundreds of standard
1041
- // deviations away from the mean - such results simply indicate that the data sampled
1042
- // exhibits a very non-normal distribution, highlighting situations for which the std.
1043
- // deviation metric is a useless indicator.
1044
-
1045
- let mean = self . mean / outputValueUnitScalingRatio
1046
- let stdDeviation = self . stdDeviation / outputValueUnitScalingRatio
1047
-
1048
- stream. write ( ( " #[Mean = %12. \( numberOfSignificantValueDigits. rawValue) f, " +
1049
- " StdDeviation = %12. \( numberOfSignificantValueDigits. rawValue) f] \n " ) . format ( mean, stdDeviation) )
1050
- stream. write ( ( " #[Max = %12. \( numberOfSignificantValueDigits. rawValue) f, " +
1051
- " Total count = %12d] \n " ) . format ( Double ( max) / outputValueUnitScalingRatio, totalCount) )
1052
- stream. write ( " #[Buckets = %12d, SubBuckets = %12d] \n " . format ( bucketCount, subBucketCount) )
1053
- }
1054
-
1055
- private func outputPercentileDistributionCsv< Stream: TextOutputStream > (
1056
- to stream: inout Stream ,
1057
- outputValueUnitScalingRatio: Double ,
1058
- percentileTicksPerHalfDistance ticks: Int = 5 ) {
1059
- stream. write ( " \" Value \" , \" Percentile \" , \" TotalCount \" , \" 1/(1-Percentile) \" \n " )
1060
-
1061
- let percentileFormatString = " %. \( numberOfSignificantValueDigits) f,%.12f,%d,%.2f \n "
1062
- let lastLinePercentileFormatString = " %. \( numberOfSignificantValueDigits) f,%.12f,%d,Infinity \n "
1046
+ let lastLinePercentileFormatString = format == . csv ?
1047
+ " %. \( numberOfSignificantValueDigits. rawValue) f,%.12f,%d,Infinity \n " :
1048
+ " %12. \( numberOfSignificantValueDigits. rawValue) f %2.12f %10d \n "
1063
1049
1064
1050
for iv in percentiles ( ticksPerHalfDistance: ticks) {
1065
- if iv. percentile != 100.0 {
1066
- stream. write ( percentileFormatString. format (
1051
+ if iv. percentileLevelIteratedTo != 100.0 {
1052
+ stream. write ( String (
1053
+ format: percentileFormatString,
1067
1054
Double ( iv. value) / outputValueUnitScalingRatio,
1068
- iv. percentile / 100.0 ,
1055
+ iv. percentileLevelIteratedTo / 100.0 ,
1069
1056
iv. totalCountToThisValue,
1070
- 1.0 / ( 1.0 - ( iv. percentile / 100.0 ) ) ) )
1057
+ 1.0 / ( 1.0 - ( iv. percentileLevelIteratedTo / 100.0 ) ) ) )
1071
1058
} else {
1072
- stream. write ( lastLinePercentileFormatString. format (
1059
+ stream. write ( String (
1060
+ format: lastLinePercentileFormatString,
1073
1061
Double ( iv. value) / outputValueUnitScalingRatio,
1074
- iv. percentile / 100.0 ,
1062
+ iv. percentileLevelIteratedTo / 100.0 ,
1075
1063
iv. totalCountToThisValue) )
1076
1064
}
1077
1065
}
1066
+
1067
+ if format != . csv {
1068
+ // Calculate and output mean and std. deviation.
1069
+ // Note: mean/std. deviation numbers are very often completely irrelevant when
1070
+ // data is extremely non-normal in distribution (e.g. in cases of strong multi-modal
1071
+ // response time distribution associated with GC pauses). However, reporting these numbers
1072
+ // can be very useful for contrasting with the detailed percentile distribution
1073
+ // reported by outputPercentileDistribution(). It is not at all surprising to find
1074
+ // percentile distributions where results fall many tens or even hundreds of standard
1075
+ // deviations away from the mean - such results simply indicate that the data sampled
1076
+ // exhibits a very non-normal distribution, highlighting situations for which the std.
1077
+ // deviation metric is a useless indicator.
1078
+ //
1079
+
1080
+ let mean = self . mean / outputValueUnitScalingRatio
1081
+ let stdDeviation = self . stdDeviation / outputValueUnitScalingRatio
1082
+ stream. write ( String ( format: " #[Mean = %12. \( numberOfSignificantValueDigits. rawValue) f, " +
1083
+ " StdDeviation = %12. \( numberOfSignificantValueDigits. rawValue) f] \n " , mean, stdDeviation) )
1084
+ stream. write ( String ( format: " #[Max = %12. \( numberOfSignificantValueDigits. rawValue) f, " +
1085
+ " Total count = %12d] \n " , Double ( max) / outputValueUnitScalingRatio, totalCount) )
1086
+ stream. write ( String ( format: " #[Buckets = %12d, SubBuckets = %12d] \n " , bucketCount, subBucketCount) )
1087
+ }
1078
1088
}
1079
1089
1080
1090
// MARK: Structure querying support.
@@ -1257,8 +1267,8 @@ public struct Histogram<Count: FixedWidthInteger> {
1257
1267
private static func bucketsNeededToCoverValue( _ value: UInt64 , subBucketCount: Int , unitMagnitude: UInt8 ) -> Int {
1258
1268
var smallestUntrackableValue = UInt64 ( subBucketCount) << unitMagnitude
1259
1269
var bucketsNeeded = 1
1260
- while ( smallestUntrackableValue <= value) {
1261
- if ( smallestUntrackableValue > UInt64 . max / 2 ) {
1270
+ while smallestUntrackableValue <= value {
1271
+ if smallestUntrackableValue > UInt64 . max / 2 {
1262
1272
return bucketsNeeded + 1
1263
1273
}
1264
1274
smallestUntrackableValue <<= 1
@@ -1301,6 +1311,7 @@ extension Histogram: Equatable {
1301
1311
// resizing.
1302
1312
if lhs. counts. count == rhs. counts. count {
1303
1313
for i in 0 ..< lhs. counts. count {
1314
+ // swiftlint:disable:next for_where
1304
1315
if lhs. counts [ i] != rhs. counts [ i] {
1305
1316
return false
1306
1317
}
@@ -1309,6 +1320,7 @@ extension Histogram: Equatable {
1309
1320
// Comparing the values is valid here because we have already confirmed the histograms have the same total
1310
1321
// count. It would not be correct otherwise.
1311
1322
for iv in lhs. recordedValues ( ) {
1323
+ // swiftlint:disable:next for_where
1312
1324
if rhs. countForValue ( iv. value) != iv. count {
1313
1325
return false
1314
1326
}
0 commit comments