|
7 | 7 | import Foundation
|
8 | 8 | import DatadogInternal
|
9 | 9 |
|
| 10 | +/// A class that aggregates metric telemetry data before sending it to the Datadog. |
| 11 | +/// |
| 12 | +/// This aggregator supports two types of metrics: |
| 13 | +/// - Counter metrics: Values that can only increase (e.g., number of events) |
| 14 | +/// - Gauge metrics: Values that can go up and down (e.g., current memory usage) |
| 15 | +/// |
| 16 | +/// Metrics can be aggregated along different dimensions using cardinalities, allowing for |
| 17 | +/// detailed analysis of metric data across various contexts. |
10 | 18 | internal final class MetricTelemetryAggregator {
|
11 |
| - private struct AggregationKey: Hashable { |
12 |
| - let metric: String |
13 |
| - let cardinalities: MetricTelemetry.Cardinalities |
14 |
| - } |
| 19 | + private typealias AggregationKey = MetricTelemetry.Cardinalities |
| 20 | + private typealias AggregationValue = [String: Double] |
15 | 21 |
|
| 22 | + /// The sample rate to apply to aggregated metrics. |
16 | 23 | let sampleRate: SampleRate
|
17 | 24 |
|
| 25 | + /// Thread-safe storage for metric aggregations. |
18 | 26 | @ReadWriteLock
|
19 |
| - private var aggregations: [AggregationKey: Double] = [:] |
| 27 | + private var aggregations: [AggregationKey: AggregationValue] = [:] |
20 | 28 |
|
| 29 | + /// Creates a new metric telemetry aggregator. |
| 30 | + /// |
| 31 | + /// - Parameter sampleRate: The sample rate to apply to aggregated metrics. |
| 32 | + /// Defaults to maximum sample rate (100%). |
21 | 33 | init(sampleRate: SampleRate = .maxSampleRate) {
|
22 | 34 | self.sampleRate = sampleRate
|
23 | 35 | }
|
24 | 36 |
|
| 37 | + /// Increments a counter metric by a specified value. |
| 38 | + /// |
| 39 | + /// This method adds the specified value to the current value of the metric. |
| 40 | + /// If the metric doesn't exist, it will be initialized with the specified value. |
| 41 | + /// |
| 42 | + /// - Parameters: |
| 43 | + /// - metric: The name of the metric to increment. |
| 44 | + /// - value: The amount to increment the metric by. |
| 45 | + /// - cardinalities: The dimensions along which the metric will be aggregated. |
25 | 46 | func increment(_ metric: String, by value: Double, cardinalities: MetricTelemetry.Cardinalities) {
|
26 |
| - _aggregations.mutate { $0[AggregationKey(metric: metric, cardinalities: cardinalities), default: 0] += value } |
| 47 | + _aggregations.mutate { aggregations in |
| 48 | + var aggregation = aggregations[cardinalities, default: [metric: 0]] |
| 49 | + aggregation[metric, default: 0] += value |
| 50 | + aggregations[cardinalities] = aggregation |
| 51 | + } |
27 | 52 | }
|
28 | 53 |
|
| 54 | + /// Records a gauge metric with a specified value. |
| 55 | + /// |
| 56 | + /// This method sets the metric to the specified value, replacing any previous value. |
| 57 | + /// Gauge metrics are used for values that can fluctuate up and down. |
| 58 | + /// |
| 59 | + /// - Parameters: |
| 60 | + /// - metric: The name of the metric to record. |
| 61 | + /// - value: The value to record for the metric. |
| 62 | + /// - cardinalities: The dimensions along which the metric will be aggregated. |
29 | 63 | func record(_ metric: String, value: Double, cardinalities: MetricTelemetry.Cardinalities) {
|
30 |
| - _aggregations.mutate { $0[AggregationKey(metric: metric, cardinalities: cardinalities), default: 0] = value } |
| 64 | + _aggregations.mutate { aggregations in |
| 65 | + var aggregation = aggregations[cardinalities, default: [metric: 0]] |
| 66 | + aggregation[metric, default: 0] = value |
| 67 | + aggregations[cardinalities] = aggregation |
| 68 | + } |
31 | 69 | }
|
32 | 70 |
|
| 71 | + /// Flushes all aggregated metrics and returns them as telemetry events. |
| 72 | + /// |
| 73 | + /// This method: |
| 74 | + /// 1. Converts all aggregated metrics into telemetry events |
| 75 | + /// 2. Clears the internal aggregation storage |
| 76 | + /// 3. Returns the generated events |
| 77 | + /// |
| 78 | + /// - Returns: An array of metric telemetry events ready to be sent to the backend. |
33 | 79 | func flush() -> [MetricTelemetry.Event] {
|
34 |
| - _aggregations.mutate { counters in |
35 |
| - defer { counters = [:] } |
| 80 | + _aggregations.mutate { aggregations in |
| 81 | + defer { aggregations = [:] } |
36 | 82 |
|
37 |
| - return counters.map { key, value in |
38 |
| - var attributes: [String: Encodable] = key.cardinalities |
39 |
| - attributes[SDKMetricFields.typeKey] = key.metric |
40 |
| - attributes[SDKMetricFields.valueKey] = value |
| 83 | + return aggregations.map { key, value in |
| 84 | + // Group metrics with same cardinality in the same |
| 85 | + // telemetry event |
| 86 | + var attributes: [String: Encodable] = key |
| 87 | + attributes.merge(value, uniquingKeysWith: { $1 }) |
41 | 88 | return MetricTelemetry.Event(
|
42 |
| - name: key.metric, |
| 89 | + name: value.keys.joined(separator: ","), |
43 | 90 | attributes: attributes,
|
44 | 91 | sampleRate: sampleRate
|
45 | 92 | )
|
|
0 commit comments