| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- /*
- * Copyright 2020, gRPC Authors All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import Foundation
- struct HistorgramShapeMismatch: Error {}
- /// Histograms are stored with exponentially increasing bucket sizes.
- /// The first bucket is [0, `multiplier`) where `multiplier` = 1 + resolution
- /// Bucket n (n>=1) contains [`multiplier`**n, `multiplier`**(n+1))
- /// There are sufficient buckets to reach max_bucket_start
- public struct Histogram {
- public private(set) var sum: Double
- public private(set) var sumOfSquares: Double
- public private(set) var countOfValuesSeen: Double
- private var multiplier: Double
- private var oneOnLogMultiplier: Double
- public private(set) var minSeen: Double
- public private(set) var maxSeen: Double
- private var maxPossible: Double
- public private(set) var buckets: [UInt32]
- /// Initialise a histogram.
- /// - parameters:
- /// - resolution: Defines the width of the buckets - see the description of this structure.
- /// - maxBucketStart: Defines the start of the greatest valued bucket.
- public init(resolution: Double = 0.01, maxBucketStart: Double = 60e9) {
- precondition(resolution > 0.0)
- precondition(maxBucketStart > resolution)
- self.sum = 0.0
- self.sumOfSquares = 0.0
- self.multiplier = 1.0 + resolution
- self.oneOnLogMultiplier = 1.0 / log(1.0 + resolution)
- self.maxPossible = maxBucketStart
- self.countOfValuesSeen = 0.0
- self.minSeen = maxBucketStart
- self.maxSeen = 0.0
- let numBuckets = Histogram.bucketForUnchecked(
- value: maxBucketStart,
- oneOnLogMultiplier: self.oneOnLogMultiplier
- ) + 1
- precondition(numBuckets > 1)
- precondition(numBuckets < 100_000_000)
- self.buckets = .init(repeating: 0, count: numBuckets)
- }
- /// Determine a bucket index given a value - does no bounds checking
- private static func bucketForUnchecked(value: Double, oneOnLogMultiplier: Double) -> Int {
- return Int(log(value) * oneOnLogMultiplier)
- }
- private func bucketFor(value: Double) -> Int {
- let bucket = Histogram.bucketForUnchecked(
- value: self.clamp(value: value),
- oneOnLogMultiplier: self.oneOnLogMultiplier
- )
- assert(bucket < self.buckets.count)
- assert(bucket >= 0)
- return bucket
- }
- /// Force a value to be within the bounds of 0 and `self.maxPossible`
- /// - parameters:
- /// - value: The value to force within bounds
- /// - returns: The value forced into the bounds for buckets.
- private func clamp(value: Double) -> Double {
- return min(self.maxPossible, max(0, value))
- }
- /// Add a value to this histogram, updating buckets and stats
- /// - parameters:
- /// - value: The value to add.
- public mutating func add(value: Double) {
- self.sum += value
- self.sumOfSquares += value * value
- self.countOfValuesSeen += 1
- if value < self.minSeen {
- self.minSeen = value
- }
- if value > self.maxSeen {
- self.maxSeen = value
- }
- self.buckets[self.bucketFor(value: value)] += 1
- }
- /// Merge two histograms together updating `self`
- /// - parameters:
- /// - source: the other histogram to merge into this.
- public mutating func merge(source: Histogram) throws {
- guard (self.buckets.count == source.buckets.count) ||
- (self.multiplier == source.multiplier) else {
- // Fail because these histograms don't match.
- throw HistorgramShapeMismatch()
- }
- self.sum += source.sum
- self.sumOfSquares += source.sumOfSquares
- self.countOfValuesSeen += source.countOfValuesSeen
- if source.minSeen < self.minSeen {
- self.minSeen = source.minSeen
- }
- if source.maxSeen > self.maxSeen {
- self.maxSeen = source.maxSeen
- }
- for bucket in 0 ..< self.buckets.count {
- self.buckets[bucket] += source.buckets[bucket]
- }
- }
- }
|