@effect/opentelemetry 0.60.0 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/LICENSE +1 -1
  2. package/dist/{dts/Logger.d.ts → Logger.d.ts} +19 -13
  3. package/dist/Logger.d.ts.map +1 -0
  4. package/dist/Logger.js +76 -0
  5. package/dist/Logger.js.map +1 -0
  6. package/dist/Metrics.d.ts +76 -0
  7. package/dist/Metrics.d.ts.map +1 -0
  8. package/dist/Metrics.js +59 -0
  9. package/dist/Metrics.js.map +1 -0
  10. package/dist/{dts/NodeSdk.d.ts → NodeSdk.d.ts} +12 -9
  11. package/dist/NodeSdk.d.ts.map +1 -0
  12. package/dist/{esm/NodeSdk.js → NodeSdk.js} +23 -14
  13. package/dist/NodeSdk.js.map +1 -0
  14. package/dist/{dts/Resource.d.ts → Resource.d.ts} +10 -13
  15. package/dist/Resource.d.ts.map +1 -0
  16. package/dist/{esm/Resource.js → Resource.js} +12 -13
  17. package/dist/Resource.js.map +1 -0
  18. package/dist/Tracer.d.ts +129 -0
  19. package/dist/Tracer.d.ts.map +1 -0
  20. package/dist/Tracer.js +391 -0
  21. package/dist/Tracer.js.map +1 -0
  22. package/dist/{dts/WebSdk.d.ts → WebSdk.d.ts} +12 -9
  23. package/dist/WebSdk.d.ts.map +1 -0
  24. package/dist/WebSdk.js +41 -0
  25. package/dist/WebSdk.js.map +1 -0
  26. package/dist/index.d.ts +28 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/{esm/index.js → index.js} +4 -20
  29. package/dist/index.js.map +1 -0
  30. package/dist/internal/attributes.d.ts +2 -0
  31. package/dist/internal/attributes.d.ts.map +1 -0
  32. package/dist/internal/attributes.js +19 -0
  33. package/dist/internal/attributes.js.map +1 -0
  34. package/dist/{dts/internal → internal}/metrics.d.ts.map +1 -1
  35. package/dist/internal/metrics.js +406 -0
  36. package/dist/internal/metrics.js.map +1 -0
  37. package/dist/internal/utilities.d.ts +2 -0
  38. package/dist/internal/utilities.d.ts.map +1 -0
  39. package/dist/internal/utilities.js +3 -0
  40. package/dist/internal/utilities.js.map +1 -0
  41. package/package.json +70 -118
  42. package/src/Logger.ts +52 -55
  43. package/src/Metrics.ts +92 -18
  44. package/src/NodeSdk.ts +67 -64
  45. package/src/Resource.ts +16 -24
  46. package/src/Tracer.ts +469 -78
  47. package/src/WebSdk.ts +59 -51
  48. package/src/index.ts +7 -26
  49. package/src/internal/attributes.ts +21 -0
  50. package/src/internal/metrics.ts +381 -250
  51. package/src/internal/utilities.ts +5 -0
  52. package/Logger/package.json +0 -6
  53. package/Metrics/package.json +0 -6
  54. package/NodeSdk/package.json +0 -6
  55. package/Otlp/package.json +0 -6
  56. package/OtlpLogger/package.json +0 -6
  57. package/OtlpMetrics/package.json +0 -6
  58. package/OtlpResource/package.json +0 -6
  59. package/OtlpTracer/package.json +0 -6
  60. package/Resource/package.json +0 -6
  61. package/Tracer/package.json +0 -6
  62. package/WebSdk/package.json +0 -6
  63. package/dist/cjs/Logger.js +0 -85
  64. package/dist/cjs/Logger.js.map +0 -1
  65. package/dist/cjs/Metrics.js +0 -24
  66. package/dist/cjs/Metrics.js.map +0 -1
  67. package/dist/cjs/NodeSdk.js +0 -53
  68. package/dist/cjs/NodeSdk.js.map +0 -1
  69. package/dist/cjs/Otlp.js +0 -46
  70. package/dist/cjs/Otlp.js.map +0 -1
  71. package/dist/cjs/OtlpLogger.js +0 -158
  72. package/dist/cjs/OtlpLogger.js.map +0 -1
  73. package/dist/cjs/OtlpMetrics.js +0 -354
  74. package/dist/cjs/OtlpMetrics.js.map +0 -1
  75. package/dist/cjs/OtlpResource.js +0 -136
  76. package/dist/cjs/OtlpResource.js.map +0 -1
  77. package/dist/cjs/OtlpTracer.js +0 -229
  78. package/dist/cjs/OtlpTracer.js.map +0 -1
  79. package/dist/cjs/Resource.js +0 -75
  80. package/dist/cjs/Resource.js.map +0 -1
  81. package/dist/cjs/Tracer.js +0 -87
  82. package/dist/cjs/Tracer.js.map +0 -1
  83. package/dist/cjs/WebSdk.js +0 -42
  84. package/dist/cjs/WebSdk.js.map +0 -1
  85. package/dist/cjs/index.js +0 -30
  86. package/dist/cjs/index.js.map +0 -1
  87. package/dist/cjs/internal/metrics.js +0 -288
  88. package/dist/cjs/internal/metrics.js.map +0 -1
  89. package/dist/cjs/internal/otlpExporter.js +0 -81
  90. package/dist/cjs/internal/otlpExporter.js.map +0 -1
  91. package/dist/cjs/internal/tracer.js +0 -299
  92. package/dist/cjs/internal/tracer.js.map +0 -1
  93. package/dist/cjs/internal/utils.js +0 -34
  94. package/dist/cjs/internal/utils.js.map +0 -1
  95. package/dist/dts/Logger.d.ts.map +0 -1
  96. package/dist/dts/Metrics.d.ts +0 -29
  97. package/dist/dts/Metrics.d.ts.map +0 -1
  98. package/dist/dts/NodeSdk.d.ts.map +0 -1
  99. package/dist/dts/Otlp.d.ts +0 -31
  100. package/dist/dts/Otlp.d.ts.map +0 -1
  101. package/dist/dts/OtlpLogger.d.ts +0 -46
  102. package/dist/dts/OtlpLogger.d.ts.map +0 -1
  103. package/dist/dts/OtlpMetrics.d.ts +0 -40
  104. package/dist/dts/OtlpMetrics.d.ts.map +0 -1
  105. package/dist/dts/OtlpResource.d.ts +0 -104
  106. package/dist/dts/OtlpResource.d.ts.map +0 -1
  107. package/dist/dts/OtlpTracer.d.ts +0 -49
  108. package/dist/dts/OtlpTracer.d.ts.map +0 -1
  109. package/dist/dts/Resource.d.ts.map +0 -1
  110. package/dist/dts/Tracer.d.ts +0 -143
  111. package/dist/dts/Tracer.d.ts.map +0 -1
  112. package/dist/dts/WebSdk.d.ts.map +0 -1
  113. package/dist/dts/index.d.ts +0 -45
  114. package/dist/dts/index.d.ts.map +0 -1
  115. package/dist/dts/internal/otlpExporter.d.ts +0 -2
  116. package/dist/dts/internal/otlpExporter.d.ts.map +0 -1
  117. package/dist/dts/internal/tracer.d.ts +0 -2
  118. package/dist/dts/internal/tracer.d.ts.map +0 -1
  119. package/dist/dts/internal/utils.d.ts +0 -2
  120. package/dist/dts/internal/utils.d.ts.map +0 -1
  121. package/dist/esm/Logger.js +0 -75
  122. package/dist/esm/Logger.js.map +0 -1
  123. package/dist/esm/Metrics.js +0 -17
  124. package/dist/esm/Metrics.js.map +0 -1
  125. package/dist/esm/NodeSdk.js.map +0 -1
  126. package/dist/esm/Otlp.js +0 -38
  127. package/dist/esm/Otlp.js.map +0 -1
  128. package/dist/esm/OtlpLogger.js +0 -150
  129. package/dist/esm/OtlpLogger.js.map +0 -1
  130. package/dist/esm/OtlpMetrics.js +0 -346
  131. package/dist/esm/OtlpMetrics.js.map +0 -1
  132. package/dist/esm/OtlpResource.js +0 -124
  133. package/dist/esm/OtlpResource.js.map +0 -1
  134. package/dist/esm/OtlpTracer.js +0 -221
  135. package/dist/esm/OtlpTracer.js.map +0 -1
  136. package/dist/esm/Resource.js.map +0 -1
  137. package/dist/esm/Tracer.js +0 -80
  138. package/dist/esm/Tracer.js.map +0 -1
  139. package/dist/esm/WebSdk.js +0 -33
  140. package/dist/esm/WebSdk.js.map +0 -1
  141. package/dist/esm/index.js.map +0 -1
  142. package/dist/esm/internal/metrics.js +0 -278
  143. package/dist/esm/internal/metrics.js.map +0 -1
  144. package/dist/esm/internal/otlpExporter.js +0 -74
  145. package/dist/esm/internal/otlpExporter.js.map +0 -1
  146. package/dist/esm/internal/tracer.js +0 -290
  147. package/dist/esm/internal/tracer.js.map +0 -1
  148. package/dist/esm/internal/utils.js +0 -23
  149. package/dist/esm/internal/utils.js.map +0 -1
  150. package/dist/esm/package.json +0 -4
  151. package/index/package.json +0 -6
  152. package/src/Otlp.ts +0 -66
  153. package/src/OtlpLogger.ts +0 -258
  154. package/src/OtlpMetrics.ts +0 -571
  155. package/src/OtlpResource.ts +0 -232
  156. package/src/OtlpTracer.ts +0 -349
  157. package/src/internal/otlpExporter.ts +0 -124
  158. package/src/internal/tracer.ts +0 -437
  159. package/src/internal/utils.ts +0 -31
  160. /package/dist/{dts/internal → internal}/metrics.d.ts +0 -0
@@ -1,5 +1,4 @@
1
- import type { HrTime } from "@opentelemetry/api"
2
- import { ValueType } from "@opentelemetry/api"
1
+ import { type HrTime, ValueType } from "@opentelemetry/api"
3
2
  import type * as Resources from "@opentelemetry/resources"
4
3
  import type {
5
4
  CollectionResult,
@@ -7,22 +6,14 @@ import type {
7
6
  Histogram,
8
7
  MetricCollectOptions,
9
8
  MetricData,
10
- MetricProducer,
11
- MetricReader
9
+ MetricProducer
12
10
  } from "@opentelemetry/sdk-metrics"
13
11
  import { AggregationTemporality, DataPointType, InstrumentType } from "@opentelemetry/sdk-metrics"
14
12
  import type { InstrumentDescriptor } from "@opentelemetry/sdk-metrics/build/src/InstrumentDescriptor.js"
15
13
  import * as Arr from "effect/Array"
16
- import type { DurationInput } from "effect/Duration"
17
- import * as Effect from "effect/Effect"
18
- import type { LazyArg } from "effect/Function"
19
- import * as Layer from "effect/Layer"
20
14
  import * as Metric from "effect/Metric"
21
- import type * as MetricKey from "effect/MetricKey"
22
- import * as MetricKeyType from "effect/MetricKeyType"
23
- import * as MetricState from "effect/MetricState"
24
- import * as Option from "effect/Option"
25
- import * as Resource from "../Resource.js"
15
+ import type * as ServiceMap from "effect/ServiceMap"
16
+ import type * as Metrics from "../Metrics.ts"
26
17
 
27
18
  const sdkName = "@effect/opentelemetry/Metrics"
28
19
 
@@ -30,11 +21,48 @@ type MetricDataWithInstrumentDescriptor = MetricData & {
30
21
  readonly descriptor: InstrumentDescriptor
31
22
  }
32
23
 
24
+ interface PreviousHistogramState {
25
+ readonly count: number
26
+ readonly sum: number
27
+ readonly bucketCounts: ReadonlyArray<number>
28
+ readonly min: number
29
+ readonly max: number
30
+ }
31
+
32
+ interface PreviousSummaryState {
33
+ readonly count: number
34
+ readonly sum: number
35
+ }
36
+
33
37
  /** @internal */
34
38
  export class MetricProducerImpl implements MetricProducer {
35
- constructor(readonly resource: Resources.Resource) {}
39
+ resource: Resources.Resource
40
+ services: ServiceMap.ServiceMap<never>
41
+ temporality: Metrics.TemporalityPreference
42
+ startTimes: Map<string, HrTime>
43
+ startTimeNanos: HrTime
44
+ previousExportTimeNanos: HrTime
45
+ previousCounterState: Map<string, number | bigint>
46
+ previousHistogramState: Map<string, PreviousHistogramState>
47
+ previousFrequencyState: Map<string, Map<string, number>>
48
+ previousSummaryState: Map<string, PreviousSummaryState>
36
49
 
37
- startTimes = new Map<string, HrTime>()
50
+ constructor(
51
+ resource: Resources.Resource,
52
+ services: ServiceMap.ServiceMap<never>,
53
+ temporality: Metrics.TemporalityPreference = "cumulative"
54
+ ) {
55
+ this.resource = resource
56
+ this.services = services
57
+ this.temporality = temporality
58
+ this.startTimes = new Map()
59
+ this.startTimeNanos = currentHrTime()
60
+ this.previousExportTimeNanos = this.startTimeNanos
61
+ this.previousCounterState = new Map()
62
+ this.previousHistogramState = new Map()
63
+ this.previousFrequencyState = new Map()
64
+ this.previousSummaryState = new Map()
65
+ }
38
66
 
39
67
  startTimeFor(name: string, hrTime: HrTime) {
40
68
  if (this.startTimes.has(name)) {
@@ -45,7 +73,7 @@ export class MetricProducerImpl implements MetricProducer {
45
73
  }
46
74
 
47
75
  collect(_options?: MetricCollectOptions): Promise<CollectionResult> {
48
- const snapshot = Metric.unsafeSnapshot()
76
+ const snapshot = Metric.snapshotUnsafe(this.services)
49
77
  const hrTimeNow = currentHrTime()
50
78
  const metricData: Array<MetricData> = []
51
79
  const metricDataByName = new Map<string, MetricData>()
@@ -54,189 +82,320 @@ export class MetricProducerImpl implements MetricProducer {
54
82
  metricDataByName.set(data.descriptor.name, data)
55
83
  }
56
84
 
85
+ const isDelta = this.temporality === "delta"
86
+ const aggregationTemporality = isDelta
87
+ ? AggregationTemporality.DELTA
88
+ : AggregationTemporality.CUMULATIVE
89
+ const intervalStartTime = isDelta
90
+ ? this.previousExportTimeNanos
91
+ : this.startTimeNanos
92
+
57
93
  for (let i = 0, len = snapshot.length; i < len; i++) {
58
- const { metricKey, metricState } = snapshot[i]
59
- const attributes = Arr.reduce(metricKey.tags, {}, (acc: Record<string, string>, label) => {
60
- acc[label.key] = label.value
61
- return acc
62
- })
63
- const descriptor = descriptorFromKey(metricKey, attributes)
64
- const startTime = this.startTimeFor(descriptor.name, hrTimeNow)
65
-
66
- if (MetricState.isCounterState(metricState)) {
67
- const dataPoint: DataPoint<number> = {
68
- startTime,
69
- endTime: hrTimeNow,
70
- attributes,
71
- value: Number(metricState.count)
72
- }
73
- if (metricDataByName.has(descriptor.name)) {
74
- metricDataByName.get(descriptor.name)!.dataPoints.push(dataPoint as any)
75
- } else {
76
- addMetricData({
77
- dataPointType: DataPointType.SUM,
78
- descriptor,
79
- isMonotonic: descriptor.type === InstrumentType.COUNTER,
80
- aggregationTemporality: AggregationTemporality.CUMULATIVE,
81
- dataPoints: [dataPoint]
82
- })
83
- }
84
- } else if (MetricState.isGaugeState(metricState)) {
85
- const dataPoint: DataPoint<number> = {
86
- startTime,
87
- endTime: hrTimeNow,
88
- attributes,
89
- value: Number(metricState.value)
90
- }
91
- if (metricDataByName.has(descriptor.name)) {
92
- metricDataByName.get(descriptor.name)!.dataPoints.push(dataPoint as any)
93
- } else {
94
- addMetricData({
95
- dataPointType: DataPointType.GAUGE,
96
- descriptor,
97
- aggregationTemporality: AggregationTemporality.CUMULATIVE,
98
- dataPoints: [dataPoint]
99
- })
100
- }
101
- } else if (MetricState.isHistogramState(metricState)) {
102
- const size = metricState.buckets.length
103
- const buckets = {
104
- boundaries: Arr.allocate(size - 1) as Array<number>,
105
- counts: Arr.allocate(size) as Array<number>
106
- }
107
- let i = 0
108
- let prev = 0
109
- for (const [boundary, value] of metricState.buckets) {
110
- if (i < size - 1) {
111
- buckets.boundaries[i] = boundary
94
+ const state = snapshot[i]
95
+ const attributes = state.attributes
96
+ ? Arr.reduce(Object.entries(state.attributes), {} as Record<string, string>, (acc, [key, value]) => {
97
+ acc[key] = String(value)
98
+ return acc
99
+ })
100
+ : {}
101
+ const metricKey = makeMetricKey(state.id, state.attributes)
102
+
103
+ switch (state.type) {
104
+ case "Counter": {
105
+ const currentCount = state.state.count
106
+ let reportValue: number | bigint = currentCount
107
+
108
+ if (isDelta) {
109
+ const previousCount = this.previousCounterState.get(metricKey)
110
+ if (previousCount !== undefined) {
111
+ if (typeof currentCount === "bigint" && typeof previousCount === "bigint") {
112
+ reportValue = currentCount - previousCount
113
+ // Handle reset: if current < previous, report current value
114
+ if (reportValue < 0n) {
115
+ reportValue = currentCount
116
+ }
117
+ } else {
118
+ const curr = Number(currentCount)
119
+ const prev = Number(previousCount)
120
+ reportValue = curr - prev
121
+ // Handle reset
122
+ if (reportValue < 0) {
123
+ reportValue = curr
124
+ }
125
+ }
126
+ }
127
+ this.previousCounterState.set(metricKey, currentCount)
128
+ }
129
+
130
+ const descriptor = descriptorFromState(state, attributes)
131
+ const startTime = this.startTimeFor(descriptor.name, intervalStartTime)
132
+ const dataPoint: DataPoint<number> = {
133
+ startTime,
134
+ endTime: hrTimeNow,
135
+ attributes,
136
+ value: Number(reportValue)
137
+ }
138
+ if (metricDataByName.has(state.id)) {
139
+ metricDataByName.get(state.id)!.dataPoints.push(dataPoint as any)
140
+ } else {
141
+ addMetricData({
142
+ dataPointType: DataPointType.SUM,
143
+ descriptor,
144
+ isMonotonic: state.state.incremental,
145
+ aggregationTemporality,
146
+ dataPoints: [dataPoint]
147
+ })
112
148
  }
113
- buckets.counts[i] = value - prev
114
- prev = value
115
- i++
149
+ break
116
150
  }
117
- const dataPoint: DataPoint<Histogram> = {
118
- startTime,
119
- endTime: hrTimeNow,
120
- attributes,
121
- value: {
122
- buckets,
123
- count: metricState.count,
124
- min: metricState.min,
125
- max: metricState.max,
126
- sum: metricState.sum
151
+ case "Gauge": {
152
+ // Gauges don't have temporality - they always report current value
153
+ const descriptor = descriptorFromState(state, attributes)
154
+ const startTime = this.startTimeFor(descriptor.name, this.startTimeNanos)
155
+ const dataPoint: DataPoint<number> = {
156
+ startTime,
157
+ endTime: hrTimeNow,
158
+ attributes,
159
+ value: Number(state.state.value)
160
+ }
161
+ if (metricDataByName.has(state.id)) {
162
+ metricDataByName.get(state.id)!.dataPoints.push(dataPoint as any)
163
+ } else {
164
+ addMetricData({
165
+ dataPointType: DataPointType.GAUGE,
166
+ descriptor,
167
+ aggregationTemporality: AggregationTemporality.CUMULATIVE,
168
+ dataPoints: [dataPoint]
169
+ })
127
170
  }
171
+ break
128
172
  }
173
+ case "Histogram": {
174
+ const size = state.state.buckets.length
175
+ const currentBuckets = {
176
+ boundaries: Arr.allocate(size - 1) as Array<number>,
177
+ counts: Arr.allocate(size) as Array<number>
178
+ }
179
+ let idx = 0
180
+ let prev = 0
181
+ for (const [boundary, value] of state.state.buckets) {
182
+ if (idx < size - 1) {
183
+ currentBuckets.boundaries[idx] = boundary
184
+ }
185
+ currentBuckets.counts[idx] = value - prev
186
+ prev = value
187
+ idx++
188
+ }
129
189
 
130
- if (metricDataByName.has(descriptor.name)) {
131
- metricDataByName.get(descriptor.name)!.dataPoints.push(dataPoint as any)
132
- } else {
133
- addMetricData({
134
- dataPointType: DataPointType.HISTOGRAM,
135
- descriptor,
136
- aggregationTemporality: AggregationTemporality.CUMULATIVE,
137
- dataPoints: [dataPoint]
138
- })
139
- }
140
- } else if (MetricState.isFrequencyState(metricState)) {
141
- const dataPoints: Array<DataPoint<number>> = []
142
- for (const [freqKey, value] of metricState.occurrences) {
143
- dataPoints.push({
190
+ let reportCount = state.state.count
191
+ let reportSum = state.state.sum
192
+ let reportBucketCounts = currentBuckets.counts
193
+ const reportMin = state.state.min
194
+ const reportMax = state.state.max
195
+
196
+ if (isDelta) {
197
+ const previousState = this.previousHistogramState.get(metricKey)
198
+ if (previousState !== undefined) {
199
+ reportCount = state.state.count - previousState.count
200
+ reportSum = state.state.sum - previousState.sum
201
+ reportBucketCounts = currentBuckets.counts.map((c, i) =>
202
+ Math.max(0, c - (previousState.bucketCounts[i] ?? 0))
203
+ )
204
+ }
205
+ this.previousHistogramState.set(metricKey, {
206
+ count: state.state.count,
207
+ sum: state.state.sum,
208
+ bucketCounts: currentBuckets.counts.slice(),
209
+ min: state.state.min,
210
+ max: state.state.max
211
+ })
212
+ }
213
+
214
+ const descriptor = descriptorFromState(state, attributes)
215
+ const startTime = this.startTimeFor(descriptor.name, intervalStartTime)
216
+ const dataPoint: DataPoint<Histogram> = {
144
217
  startTime,
145
218
  endTime: hrTimeNow,
146
- attributes: {
147
- ...attributes,
148
- key: freqKey
149
- },
150
- value
151
- })
219
+ attributes,
220
+ value: {
221
+ buckets: {
222
+ boundaries: currentBuckets.boundaries,
223
+ counts: reportBucketCounts
224
+ },
225
+ count: reportCount,
226
+ min: reportMin,
227
+ max: reportMax,
228
+ sum: reportSum
229
+ }
230
+ }
231
+
232
+ if (metricDataByName.has(state.id)) {
233
+ metricDataByName.get(state.id)!.dataPoints.push(dataPoint as any)
234
+ } else {
235
+ addMetricData({
236
+ dataPointType: DataPointType.HISTOGRAM,
237
+ descriptor,
238
+ aggregationTemporality,
239
+ dataPoints: [dataPoint]
240
+ })
241
+ }
242
+ break
152
243
  }
153
- if (metricDataByName.has(descriptor.name)) {
154
- // eslint-disable-next-line no-restricted-syntax
155
- metricDataByName.get(descriptor.name)!.dataPoints.push(...dataPoints as any)
156
- } else {
157
- addMetricData({
158
- dataPointType: DataPointType.SUM,
159
- descriptor: descriptorFromKey(metricKey, attributes),
160
- aggregationTemporality: AggregationTemporality.CUMULATIVE,
161
- isMonotonic: true,
162
- dataPoints
163
- })
244
+ case "Frequency": {
245
+ const dataPoints: Array<DataPoint<number>> = []
246
+ const currentOccurrences = new Map<string, number>()
247
+
248
+ for (const [freqKey, value] of state.state.occurrences) {
249
+ currentOccurrences.set(freqKey, value)
250
+ let reportValue = value
251
+
252
+ if (isDelta) {
253
+ const previousOccurrences = this.previousFrequencyState.get(metricKey)
254
+ if (previousOccurrences !== undefined) {
255
+ const previousValue = previousOccurrences.get(freqKey) ?? 0
256
+ reportValue = Math.max(0, value - previousValue)
257
+ }
258
+ }
259
+
260
+ const descriptor = descriptorFromState(state, attributes)
261
+ const startTime = this.startTimeFor(descriptor.name, intervalStartTime)
262
+ dataPoints.push({
263
+ startTime,
264
+ endTime: hrTimeNow,
265
+ attributes: {
266
+ ...attributes,
267
+ key: freqKey
268
+ },
269
+ value: reportValue
270
+ })
271
+ }
272
+
273
+ if (isDelta) {
274
+ this.previousFrequencyState.set(metricKey, currentOccurrences)
275
+ }
276
+
277
+ if (metricDataByName.has(state.id)) {
278
+ // oxlint-disable-next-line no-restricted-syntax
279
+ metricDataByName.get(state.id)!.dataPoints.push(...dataPoints as any)
280
+ } else {
281
+ const descriptor = descriptorFromState(state, attributes)
282
+ addMetricData({
283
+ dataPointType: DataPointType.SUM,
284
+ descriptor,
285
+ aggregationTemporality,
286
+ isMonotonic: true,
287
+ dataPoints
288
+ })
289
+ }
290
+ break
164
291
  }
165
- } else if (MetricState.isSummaryState(metricState)) {
166
- const dataPoints: Array<DataPoint<number>> = [{
167
- startTime,
168
- endTime: hrTimeNow,
169
- attributes: { ...attributes, quantile: "min" },
170
- value: metricState.min
171
- }]
172
- for (const [quantile, value] of metricState.quantiles) {
292
+ case "Summary": {
293
+ // Quantiles are always computed fresh from the sliding window
294
+ const dataPoints: Array<DataPoint<number>> = [{
295
+ startTime: intervalStartTime,
296
+ endTime: hrTimeNow,
297
+ attributes: { ...attributes, quantile: "min" },
298
+ value: state.state.min
299
+ }]
300
+ for (const [quantile, value] of state.state.quantiles) {
301
+ dataPoints.push({
302
+ startTime: intervalStartTime,
303
+ endTime: hrTimeNow,
304
+ attributes: { ...attributes, quantile: quantile.toString() },
305
+ value: value ?? 0
306
+ })
307
+ }
173
308
  dataPoints.push({
174
- startTime,
309
+ startTime: intervalStartTime,
175
310
  endTime: hrTimeNow,
176
- attributes: { ...attributes, quantile: quantile.toString() },
177
- value: value._tag === "Some" ? value.value : 0
311
+ attributes: { ...attributes, quantile: "max" },
312
+ value: state.state.max
178
313
  })
179
- }
180
- dataPoints.push({
181
- startTime,
182
- endTime: hrTimeNow,
183
- attributes: { ...attributes, quantile: "max" },
184
- value: metricState.max
185
- })
186
- const countDataPoint: DataPoint<number> = {
187
- startTime,
188
- endTime: hrTimeNow,
189
- attributes,
190
- value: metricState.count
191
- }
192
- const sumDataPoint: DataPoint<number> = {
193
- startTime,
194
- endTime: hrTimeNow,
195
- attributes,
196
- value: metricState.sum
197
- }
198
314
 
199
- if (metricDataByName.has(`${descriptor.name}_quantiles`)) {
200
- // eslint-disable-next-line no-restricted-syntax
201
- metricDataByName.get(`${descriptor.name}_quantiles`)!.dataPoints.push(...dataPoints as any)
202
- metricDataByName.get(`${descriptor.name}_count`)!.dataPoints.push(countDataPoint as any)
203
- metricDataByName.get(`${descriptor.name}_sum`)!.dataPoints.push(sumDataPoint as any)
204
- } else {
205
- addMetricData({
206
- dataPointType: DataPointType.SUM,
207
- descriptor: descriptorFromKey(metricKey, attributes, "quantiles"),
208
- aggregationTemporality: AggregationTemporality.CUMULATIVE,
209
- isMonotonic: false,
210
- dataPoints
211
- })
212
- addMetricData({
213
- dataPointType: DataPointType.SUM,
214
- descriptor: {
215
- ...descriptorMeta(metricKey, "count"),
216
- unit: "1",
217
- type: InstrumentType.COUNTER,
218
- valueType: ValueType.INT
219
- },
220
- aggregationTemporality: AggregationTemporality.CUMULATIVE,
221
- isMonotonic: true,
222
- dataPoints: [countDataPoint]
223
- })
224
- addMetricData({
225
- dataPointType: DataPointType.SUM,
226
- descriptor: {
227
- ...descriptorMeta(metricKey, "sum"),
228
- unit: "1",
229
- type: InstrumentType.COUNTER,
230
- valueType: ValueType.DOUBLE
231
- },
232
- aggregationTemporality: AggregationTemporality.CUMULATIVE,
233
- isMonotonic: true,
234
- dataPoints: [sumDataPoint]
235
- })
315
+ let reportCount = state.state.count
316
+ let reportSum = state.state.sum
317
+
318
+ if (isDelta) {
319
+ const previousState = this.previousSummaryState.get(metricKey)
320
+ if (previousState !== undefined) {
321
+ reportCount = state.state.count - previousState.count
322
+ reportSum = state.state.sum - previousState.sum
323
+ }
324
+ this.previousSummaryState.set(metricKey, {
325
+ count: state.state.count,
326
+ sum: state.state.sum
327
+ })
328
+ }
329
+
330
+ const countDataPoint: DataPoint<number> = {
331
+ startTime: intervalStartTime,
332
+ endTime: hrTimeNow,
333
+ attributes,
334
+ value: reportCount
335
+ }
336
+ const sumDataPoint: DataPoint<number> = {
337
+ startTime: intervalStartTime,
338
+ endTime: hrTimeNow,
339
+ attributes,
340
+ value: reportSum
341
+ }
342
+
343
+ if (metricDataByName.has(`${state.id}_quantiles`)) {
344
+ // oxlint-disable-next-line no-restricted-syntax
345
+ metricDataByName.get(`${state.id}_quantiles`)!.dataPoints.push(...dataPoints as any)
346
+ metricDataByName.get(`${state.id}_count`)!.dataPoints.push(countDataPoint as any)
347
+ metricDataByName.get(`${state.id}_sum`)!.dataPoints.push(sumDataPoint as any)
348
+ } else {
349
+ const descriptor = descriptorFromState(state, attributes)
350
+ addMetricData({
351
+ dataPointType: DataPointType.SUM,
352
+ descriptor: {
353
+ ...descriptor,
354
+ name: `${descriptor.name}_quantiles`
355
+ },
356
+ aggregationTemporality,
357
+ isMonotonic: false,
358
+ dataPoints
359
+ })
360
+ addMetricData({
361
+ dataPointType: DataPointType.SUM,
362
+ descriptor: {
363
+ name: `${state.id}_count`,
364
+ description: state.description ?? "",
365
+ unit: "1",
366
+ type: InstrumentType.COUNTER,
367
+ valueType: ValueType.INT,
368
+ advice: {}
369
+ },
370
+ aggregationTemporality,
371
+ isMonotonic: true,
372
+ dataPoints: [countDataPoint]
373
+ })
374
+ addMetricData({
375
+ dataPointType: DataPointType.SUM,
376
+ descriptor: {
377
+ name: `${state.id}_sum`,
378
+ description: state.description ?? "",
379
+ unit: "1",
380
+ type: InstrumentType.COUNTER,
381
+ valueType: ValueType.DOUBLE,
382
+ advice: {}
383
+ },
384
+ aggregationTemporality,
385
+ isMonotonic: true,
386
+ dataPoints: [sumDataPoint]
387
+ })
388
+ }
389
+ break
236
390
  }
237
391
  }
238
392
  }
239
393
 
394
+ // Update the previous export time for delta calculations
395
+ if (isDelta) {
396
+ this.previousExportTimeNanos = hrTimeNow
397
+ }
398
+
240
399
  return Promise.resolve({
241
400
  resourceMetrics: {
242
401
  resource: this.resource,
@@ -250,38 +409,13 @@ export class MetricProducerImpl implements MetricProducer {
250
409
  }
251
410
  }
252
411
 
253
- const descriptorMeta = (
254
- metricKey: MetricKey.MetricKey.Untyped,
255
- suffix?: string
256
- ) => ({
257
- name: suffix ? `${metricKey.name}_${suffix}` : metricKey.name,
258
- description: Option.getOrElse(metricKey.description, () => ""),
259
- advice: {}
260
- })
261
-
262
- const descriptorFromKey = (
263
- metricKey: MetricKey.MetricKey.Untyped,
264
- tags: Record<string, string>,
265
- suffix?: string
266
- ): InstrumentDescriptor => ({
267
- ...descriptorMeta(metricKey, suffix),
268
- unit: tags.unit ?? tags.time_unit ?? "1",
269
- type: instrumentTypeFromKey(metricKey),
270
- valueType: "bigint" in metricKey.keyType && metricKey.keyType.bigint === true ? ValueType.INT : ValueType.DOUBLE
271
- })
272
-
273
- const instrumentTypeFromKey = (key: MetricKey.MetricKey.Untyped): InstrumentType => {
274
- if (MetricKeyType.isHistogramKey(key.keyType)) {
275
- return InstrumentType.HISTOGRAM
276
- } else if (MetricKeyType.isGaugeKey(key.keyType)) {
277
- return InstrumentType.OBSERVABLE_GAUGE
278
- } else if (MetricKeyType.isFrequencyKey(key.keyType)) {
279
- return InstrumentType.COUNTER
280
- } else if (MetricKeyType.isCounterKey(key.keyType) && key.keyType.incremental) {
281
- return InstrumentType.COUNTER
412
+ /** Creates a unique key for a metric including its attributes */
413
+ const makeMetricKey = (id: string, attributes: Metric.Metric.AttributeSet | undefined): string => {
414
+ if (attributes === undefined || Object.keys(attributes).length === 0) {
415
+ return id
282
416
  }
283
-
284
- return InstrumentType.UP_DOWN_COUNTER
417
+ const sortedEntries = Object.entries(attributes).sort((a, b) => a[0].localeCompare(b[0]))
418
+ return `${id}:${JSON.stringify(sortedEntries)}`
285
419
  }
286
420
 
287
421
  const currentHrTime = (): HrTime => {
@@ -289,44 +423,41 @@ const currentHrTime = (): HrTime => {
289
423
  return [Math.floor(now / 1000), (now % 1000) * 1000000]
290
424
  }
291
425
 
292
- /** @internal */
293
- export const makeProducer = Effect.map(
294
- Resource.Resource,
295
- (resource): MetricProducer => new MetricProducerImpl(resource)
296
- )
426
+ const descriptorFromState = (
427
+ state: Metric.Metric.Snapshot,
428
+ attributes: Record<string, string>
429
+ ): InstrumentDescriptor => {
430
+ const unit = attributes.unit ?? attributes.time_unit ?? "1"
431
+ return {
432
+ name: state.id,
433
+ description: state.description ?? "",
434
+ unit,
435
+ type: instrumentTypeFromSnapshot(state),
436
+ valueType: determineValueType(state),
437
+ advice: {}
438
+ }
439
+ }
297
440
 
298
- /** @internal */
299
- export const registerProducer = (
300
- self: MetricProducer,
301
- metricReader: LazyArg<MetricReader | Arr.NonEmptyReadonlyArray<MetricReader>>,
302
- options?: {
303
- readonly shutdownTimeout?: DurationInput | undefined
441
+ const instrumentTypeFromSnapshot = (state: Metric.Metric.Snapshot): InstrumentType => {
442
+ switch (state.type) {
443
+ case "Histogram":
444
+ return InstrumentType.HISTOGRAM
445
+ case "Gauge":
446
+ return InstrumentType.OBSERVABLE_GAUGE
447
+ case "Frequency":
448
+ return InstrumentType.COUNTER
449
+ case "Counter":
450
+ return state.state.incremental ? InstrumentType.COUNTER : InstrumentType.UP_DOWN_COUNTER
451
+ case "Summary":
452
+ return InstrumentType.COUNTER
304
453
  }
305
- ) =>
306
- Effect.acquireRelease(
307
- Effect.sync(() => {
308
- const reader = metricReader()
309
- const readers: Array<MetricReader> = Array.isArray(reader) ? reader : [reader] as any
310
- readers.forEach((reader) => reader.setMetricProducer(self))
311
- return readers
312
- }),
313
- (readers) =>
314
- Effect.promise(() =>
315
- Promise.all(
316
- readers.map((reader) => reader.shutdown())
317
- )
318
- ).pipe(
319
- Effect.ignoreLogged,
320
- Effect.interruptible,
321
- Effect.timeoutOption(options?.shutdownTimeout ?? 3000)
322
- )
323
- )
454
+ }
324
455
 
325
- /** @internal */
326
- export const layer = (evaluate: LazyArg<MetricReader | Arr.NonEmptyReadonlyArray<MetricReader>>, options?: {
327
- readonly shutdownTimeout?: DurationInput | undefined
328
- }) =>
329
- Layer.scopedDiscard(Effect.flatMap(
330
- makeProducer,
331
- (producer) => registerProducer(producer, evaluate, options)
332
- ))
456
+ const determineValueType = (state: Metric.Metric.Snapshot): ValueType => {
457
+ if (state.type === "Counter") {
458
+ return typeof state.state.count === "bigint" ? ValueType.INT : ValueType.DOUBLE
459
+ } else if (state.type === "Gauge") {
460
+ return typeof state.state.value === "bigint" ? ValueType.INT : ValueType.DOUBLE
461
+ }
462
+ return ValueType.DOUBLE
463
+ }