swift-metrics/Sources/Examples/ExampleMetricsLibrary.swift

289 lines
8.9 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Metrics API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// THIS IS NOT PART OF THE PITCH, JUST AN EXAMPLE
//
import Metrics
class ExampleMetricsLibrary: MetricsFactory {
private let config: Config
private let lock = NSLock()
var counters = [ExampleCounter]()
var recorders = [ExampleRecorder]()
var gauges = [ExampleGauge]()
var timers = [ExampleTimer]()
init(config: Config = Config()) {
self.config = config
}
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
return self.register(label: label, dimensions: dimensions, registry: &self.counters, maker: ExampleCounter.init)
}
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
let options = aggregate ? self.config.recorder.aggregationOptions : nil
return self.makeRecorder(label: label, dimensions: dimensions, options: options)
}
func makeRecorder(label: String, dimensions: [(String, String)], options: [AggregationOption]?) -> RecorderHandler {
guard let options = options else {
return self.register(label: label, dimensions: dimensions, registry: &self.gauges, maker: ExampleGauge.init)
}
let maker = { (label: String, dimensions: [(String, String)]) -> ExampleRecorder in
ExampleRecorder(label: label, dimensions: dimensions, options: options)
}
return self.register(label: label, dimensions: dimensions, registry: &self.recorders, maker: maker)
}
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
return self.makeTimer(label: label, dimensions: dimensions, options: self.config.timer.aggregationOptions)
}
func makeTimer(label: String, dimensions: [(String, String)], options: [AggregationOption]) -> TimerHandler {
let maker = { (label: String, dimensions: [(String, String)]) -> ExampleTimer in
ExampleTimer(label: label, dimensions: dimensions, options: options)
}
return self.register(label: label, dimensions: dimensions, registry: &self.timers, maker: maker)
}
func register<Item>(label: String, dimensions: [(String, String)], registry: inout [Item], maker: (String, [(String, String)]) -> Item) -> Item {
let item = maker(label, dimensions)
lock.withLock {
registry.append(item)
}
return item
}
class Config {
let recorder: RecorderConfig
let timer: TimerConfig
init(recorder: RecorderConfig = RecorderConfig(), timer: TimerConfig = TimerConfig()) {
self.recorder = recorder
self.timer = timer
}
}
class RecorderConfig {
let aggregationOptions: [AggregationOption]
init(aggregationOptions: [AggregationOption]) {
self.aggregationOptions = aggregationOptions
}
init() {
self.aggregationOptions = AggregationOption.defaults
}
}
class TimerConfig {
let aggregationOptions: [AggregationOption]
init(aggregationOptions: [AggregationOption]) {
self.aggregationOptions = aggregationOptions
}
init() {
self.aggregationOptions = AggregationOption.defaults
}
}
}
class ExampleCounter: CounterHandler, CustomStringConvertible {
let label: String
let dimensions: [(String, String)]
init(label: String, dimensions: [(String, String)]) {
self.label = label
self.dimensions = dimensions
}
let lock = NSLock()
var value: Int64 = 0
func increment<DataType: BinaryInteger>(_ value: DataType) {
self.lock.withLock {
self.value += Int64(value)
}
}
var description: String {
return "counter [label: \(self.label) dimensions:\(self.dimensions) values:\(self.value)]"
}
}
class ExampleRecorder: RecorderHandler, CustomStringConvertible {
let label: String
let dimensions: [(String, String)]
let options: [AggregationOption]
init(label: String, dimensions: [(String, String)], options: [AggregationOption]) {
self.label = label
self.dimensions = dimensions
self.options = options
}
private let lock = NSLock()
var values = [(Int64, Double)]()
func record<DataType: BinaryInteger>(_ value: DataType) {
self.record(Double(value))
}
func record<DataType: BinaryFloatingPoint>(_ value: DataType) {
// this may loose percision, but good enough as an example
let v = Double(value)
// TODO: sliding window
lock.withLock {
values.append((Date().nanoSince1970, v))
}
options.forEach { option in
switch option {
case .count:
self.count += 1
case .sum:
self.sum += v
case .min:
if 0 == self.min || v < self.min { self.min = v }
case .max:
if 0 == self.max || v > self.max { self.max = v }
case .quantiles(let items):
self.computeQuantiles(items)
}
}
}
var _sum: Double = 0
var sum: Double {
get {
return self.lock.withLock { _sum }
}
set {
self.lock.withLock { _sum = newValue }
}
}
private var _count: Int = 0
var count: Int {
get {
return self.lock.withLock { _count }
}
set {
self.lock.withLock { _count = newValue }
}
}
private var _min: Double = 0
var min: Double {
get {
return self.lock.withLock { _min }
}
set {
self.lock.withLock { _min = newValue }
}
}
private var _max: Double = 0
var max: Double {
get {
return self.lock.withLock { _max }
}
set {
self.lock.withLock { _max = newValue }
}
}
private var _quantiels = [Float: Double]()
var quantiels: [Float: Double] {
get {
return self.lock.withLock { _quantiels }
}
set {
self.lock.withLock { _quantiels = newValue }
}
}
var description: String {
return "recorder [label: \(self.label) dimensions:\(self.dimensions) count:\(self.count) sum:\(self.sum) min:\(self.min) max:\(self.max) quantiels:\(self.quantiels) values:\(self.values)]"
}
// TODO: offload calcs to queue
private func computeQuantiles(_ items: [Float]) {
self.lock.withLock {
self._quantiels.removeAll()
items.forEach { item in
if let result = Sigma.quantiles.method1(self.values.map { Double($0.1) }, probability: Double(item)) {
self._quantiels[item] = result
}
}
}
}
}
class ExampleGauge: RecorderHandler, CustomStringConvertible {
let label: String
let dimensions: [(String, String)]
init(label: String, dimensions: [(String, String)]) {
self.label = label
self.dimensions = dimensions
}
let lock = NSLock()
var _value: Double = 0
func record<DataType: BinaryInteger>(_ value: DataType) {
self.record(Double(value))
}
func record<DataType: BinaryFloatingPoint>(_ value: DataType) {
// this may loose percision but good enough as an example
self.lock.withLock { _value = Double(value) }
}
var description: String {
return "gauge [label: \(self.label) dimensions:\(self.dimensions) value:\(self._value)]"
}
}
class ExampleTimer: ExampleRecorder, TimerHandler {
func recordNanoseconds(_ duration: Int64) {
super.record(duration)
}
override var description: String {
return "timer [label: \(self.label) dimensions:\(self.dimensions) count:\(self.count) sum:\(self.sum) min:\(self.min) max:\(self.max) quantiels:\(self.quantiels) values:\(self.values)]"
}
}
enum AggregationOption {
case count
case sum
case min
case max
case quantiles(_ items: [Float])
public static let defaults: [AggregationOption] = [.count, .sum, .min, .max, .quantiles(defaultQuantiles)]
public static let defaultQuantiles: [Float] = [0.25, 0.5, 0.75, 0.9, 0.95, 0.99]
}
private extension Foundation.Date {
var nanoSince1970: Int64 {
return Int64(self.timeIntervalSince1970 * 1_000_000_000)
}
}
private extension Foundation.NSLock {
func withLock<T>(_ body: () -> T) -> T {
self.lock()
defer {
self.unlock()
}
return body()
}
}