2
0

Histogram.swift 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. /*
  2. * Copyright 2020, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import Foundation
  17. struct HistorgramShapeMismatch: Error {}
  18. /// Histograms are stored with exponentially increasing bucket sizes.
  19. /// The first bucket is [0, `multiplier`) where `multiplier` = 1 + resolution
  20. /// Bucket n (n>=1) contains [`multiplier`**n, `multiplier`**(n+1))
  21. /// There are sufficient buckets to reach max_bucket_start
  22. public struct Histogram {
  23. public private(set) var sum: Double
  24. public private(set) var sumOfSquares: Double
  25. public private(set) var countOfValuesSeen: Double
  26. private var multiplier: Double
  27. private var oneOnLogMultiplier: Double
  28. public private(set) var minSeen: Double
  29. public private(set) var maxSeen: Double
  30. private var maxPossible: Double
  31. public private(set) var buckets: [UInt32]
  32. /// Initialise a histogram.
  33. /// - parameters:
  34. /// - resolution: Defines the width of the buckets - see the description of this structure.
  35. /// - maxBucketStart: Defines the start of the greatest valued bucket.
  36. public init(resolution: Double = 0.01, maxBucketStart: Double = 60e9) {
  37. precondition(resolution > 0.0)
  38. precondition(maxBucketStart > resolution)
  39. self.sum = 0.0
  40. self.sumOfSquares = 0.0
  41. self.multiplier = 1.0 + resolution
  42. self.oneOnLogMultiplier = 1.0 / log(1.0 + resolution)
  43. self.maxPossible = maxBucketStart
  44. self.countOfValuesSeen = 0.0
  45. self.minSeen = maxBucketStart
  46. self.maxSeen = 0.0
  47. let numBuckets = Histogram.bucketForUnchecked(
  48. value: maxBucketStart,
  49. oneOnLogMultiplier: self.oneOnLogMultiplier
  50. ) + 1
  51. precondition(numBuckets > 1)
  52. precondition(numBuckets < 100_000_000)
  53. self.buckets = .init(repeating: 0, count: numBuckets)
  54. }
  55. /// Determine a bucket index given a value - does no bounds checking
  56. private static func bucketForUnchecked(value: Double, oneOnLogMultiplier: Double) -> Int {
  57. return Int(log(value) * oneOnLogMultiplier)
  58. }
  59. private func bucketFor(value: Double) -> Int {
  60. let bucket = Histogram.bucketForUnchecked(
  61. value: self.clamp(value: value),
  62. oneOnLogMultiplier: self.oneOnLogMultiplier
  63. )
  64. assert(bucket < self.buckets.count)
  65. assert(bucket >= 0)
  66. return bucket
  67. }
  68. /// Force a value to be within the bounds of 0 and `self.maxPossible`
  69. /// - parameters:
  70. /// - value: The value to force within bounds
  71. /// - returns: The value forced into the bounds for buckets.
  72. private func clamp(value: Double) -> Double {
  73. return min(self.maxPossible, max(0, value))
  74. }
  75. /// Add a value to this histogram, updating buckets and stats
  76. /// - parameters:
  77. /// - value: The value to add.
  78. public mutating func add(value: Double) {
  79. self.sum += value
  80. self.sumOfSquares += value * value
  81. self.countOfValuesSeen += 1
  82. if value < self.minSeen {
  83. self.minSeen = value
  84. }
  85. if value > self.maxSeen {
  86. self.maxSeen = value
  87. }
  88. self.buckets[self.bucketFor(value: value)] += 1
  89. }
  90. /// Merge two histograms together updating `self`
  91. /// - parameters:
  92. /// - source: the other histogram to merge into this.
  93. public mutating func merge(source: Histogram) throws {
  94. guard (self.buckets.count == source.buckets.count) ||
  95. (self.multiplier == source.multiplier) else {
  96. // Fail because these histograms don't match.
  97. throw HistorgramShapeMismatch()
  98. }
  99. self.sum += source.sum
  100. self.sumOfSquares += source.sumOfSquares
  101. self.countOfValuesSeen += source.countOfValuesSeen
  102. if source.minSeen < self.minSeen {
  103. self.minSeen = source.minSeen
  104. }
  105. if source.maxSeen > self.maxSeen {
  106. self.maxSeen = source.maxSeen
  107. }
  108. for bucket in 0 ..< self.buckets.count {
  109. self.buckets[bucket] += source.buckets[bucket]
  110. }
  111. }
  112. }