@effect/opentelemetry 0.61.0 → 4.0.0-beta.1

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 (183) 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/index.js +29 -0
  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 +86 -119
  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 -36
  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/OtlpSerialization/package.json +0 -6
  60. package/OtlpTracer/package.json +0 -6
  61. package/Resource/package.json +0 -6
  62. package/Tracer/package.json +0 -6
  63. package/WebSdk/package.json +0 -6
  64. package/dist/cjs/Logger.js +0 -85
  65. package/dist/cjs/Logger.js.map +0 -1
  66. package/dist/cjs/Metrics.js +0 -24
  67. package/dist/cjs/Metrics.js.map +0 -1
  68. package/dist/cjs/NodeSdk.js +0 -53
  69. package/dist/cjs/NodeSdk.js.map +0 -1
  70. package/dist/cjs/Otlp.js +0 -64
  71. package/dist/cjs/Otlp.js.map +0 -1
  72. package/dist/cjs/OtlpLogger.js +0 -163
  73. package/dist/cjs/OtlpLogger.js.map +0 -1
  74. package/dist/cjs/OtlpMetrics.js +0 -357
  75. package/dist/cjs/OtlpMetrics.js.map +0 -1
  76. package/dist/cjs/OtlpResource.js +0 -136
  77. package/dist/cjs/OtlpResource.js.map +0 -1
  78. package/dist/cjs/OtlpSerialization.js +0 -55
  79. package/dist/cjs/OtlpSerialization.js.map +0 -1
  80. package/dist/cjs/OtlpTracer.js +0 -231
  81. package/dist/cjs/OtlpTracer.js.map +0 -1
  82. package/dist/cjs/Resource.js +0 -75
  83. package/dist/cjs/Resource.js.map +0 -1
  84. package/dist/cjs/Tracer.js +0 -87
  85. package/dist/cjs/Tracer.js.map +0 -1
  86. package/dist/cjs/WebSdk.js +0 -42
  87. package/dist/cjs/WebSdk.js.map +0 -1
  88. package/dist/cjs/index.js +0 -32
  89. package/dist/cjs/index.js.map +0 -1
  90. package/dist/cjs/internal/metrics.js +0 -288
  91. package/dist/cjs/internal/metrics.js.map +0 -1
  92. package/dist/cjs/internal/otlpExporter.js +0 -83
  93. package/dist/cjs/internal/otlpExporter.js.map +0 -1
  94. package/dist/cjs/internal/otlpProtobuf.js +0 -430
  95. package/dist/cjs/internal/otlpProtobuf.js.map +0 -1
  96. package/dist/cjs/internal/protobuf.js +0 -183
  97. package/dist/cjs/internal/protobuf.js.map +0 -1
  98. package/dist/cjs/internal/tracer.js +0 -306
  99. package/dist/cjs/internal/tracer.js.map +0 -1
  100. package/dist/cjs/internal/utils.js +0 -34
  101. package/dist/cjs/internal/utils.js.map +0 -1
  102. package/dist/dts/Logger.d.ts.map +0 -1
  103. package/dist/dts/Metrics.d.ts +0 -29
  104. package/dist/dts/Metrics.d.ts.map +0 -1
  105. package/dist/dts/NodeSdk.d.ts.map +0 -1
  106. package/dist/dts/Otlp.d.ts +0 -80
  107. package/dist/dts/Otlp.d.ts.map +0 -1
  108. package/dist/dts/OtlpLogger.d.ts +0 -47
  109. package/dist/dts/OtlpLogger.d.ts.map +0 -1
  110. package/dist/dts/OtlpMetrics.d.ts +0 -41
  111. package/dist/dts/OtlpMetrics.d.ts.map +0 -1
  112. package/dist/dts/OtlpResource.d.ts +0 -104
  113. package/dist/dts/OtlpResource.d.ts.map +0 -1
  114. package/dist/dts/OtlpSerialization.d.ts +0 -53
  115. package/dist/dts/OtlpSerialization.d.ts.map +0 -1
  116. package/dist/dts/OtlpTracer.d.ts +0 -50
  117. package/dist/dts/OtlpTracer.d.ts.map +0 -1
  118. package/dist/dts/Resource.d.ts.map +0 -1
  119. package/dist/dts/Tracer.d.ts +0 -143
  120. package/dist/dts/Tracer.d.ts.map +0 -1
  121. package/dist/dts/WebSdk.d.ts.map +0 -1
  122. package/dist/dts/index.d.ts +0 -54
  123. package/dist/dts/index.d.ts.map +0 -1
  124. package/dist/dts/internal/otlpExporter.d.ts +0 -2
  125. package/dist/dts/internal/otlpExporter.d.ts.map +0 -1
  126. package/dist/dts/internal/otlpProtobuf.d.ts +0 -501
  127. package/dist/dts/internal/otlpProtobuf.d.ts.map +0 -1
  128. package/dist/dts/internal/protobuf.d.ts +0 -100
  129. package/dist/dts/internal/protobuf.d.ts.map +0 -1
  130. package/dist/dts/internal/tracer.d.ts +0 -2
  131. package/dist/dts/internal/tracer.d.ts.map +0 -1
  132. package/dist/dts/internal/utils.d.ts +0 -2
  133. package/dist/dts/internal/utils.d.ts.map +0 -1
  134. package/dist/esm/Logger.js +0 -75
  135. package/dist/esm/Logger.js.map +0 -1
  136. package/dist/esm/Metrics.js +0 -17
  137. package/dist/esm/Metrics.js.map +0 -1
  138. package/dist/esm/NodeSdk.js.map +0 -1
  139. package/dist/esm/Otlp.js +0 -56
  140. package/dist/esm/Otlp.js.map +0 -1
  141. package/dist/esm/OtlpLogger.js +0 -155
  142. package/dist/esm/OtlpLogger.js.map +0 -1
  143. package/dist/esm/OtlpMetrics.js +0 -349
  144. package/dist/esm/OtlpMetrics.js.map +0 -1
  145. package/dist/esm/OtlpResource.js +0 -124
  146. package/dist/esm/OtlpResource.js.map +0 -1
  147. package/dist/esm/OtlpSerialization.js +0 -46
  148. package/dist/esm/OtlpSerialization.js.map +0 -1
  149. package/dist/esm/OtlpTracer.js +0 -223
  150. package/dist/esm/OtlpTracer.js.map +0 -1
  151. package/dist/esm/Resource.js.map +0 -1
  152. package/dist/esm/Tracer.js +0 -80
  153. package/dist/esm/Tracer.js.map +0 -1
  154. package/dist/esm/WebSdk.js +0 -33
  155. package/dist/esm/WebSdk.js.map +0 -1
  156. package/dist/esm/index.js +0 -54
  157. package/dist/esm/index.js.map +0 -1
  158. package/dist/esm/internal/metrics.js +0 -278
  159. package/dist/esm/internal/metrics.js.map +0 -1
  160. package/dist/esm/internal/otlpExporter.js +0 -76
  161. package/dist/esm/internal/otlpExporter.js.map +0 -1
  162. package/dist/esm/internal/otlpProtobuf.js +0 -396
  163. package/dist/esm/internal/otlpProtobuf.js.map +0 -1
  164. package/dist/esm/internal/protobuf.js +0 -155
  165. package/dist/esm/internal/protobuf.js.map +0 -1
  166. package/dist/esm/internal/tracer.js +0 -297
  167. package/dist/esm/internal/tracer.js.map +0 -1
  168. package/dist/esm/internal/utils.js +0 -23
  169. package/dist/esm/internal/utils.js.map +0 -1
  170. package/dist/esm/package.json +0 -4
  171. package/index/package.json +0 -6
  172. package/src/Otlp.ts +0 -118
  173. package/src/OtlpLogger.ts +0 -263
  174. package/src/OtlpMetrics.ts +0 -575
  175. package/src/OtlpResource.ts +0 -232
  176. package/src/OtlpSerialization.ts +0 -64
  177. package/src/OtlpTracer.ts +0 -352
  178. package/src/internal/otlpExporter.ts +0 -126
  179. package/src/internal/otlpProtobuf.ts +0 -729
  180. package/src/internal/protobuf.ts +0 -219
  181. package/src/internal/tracer.ts +0 -448
  182. package/src/internal/utils.ts +0 -31
  183. /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
+ }