@oas-tools/oas-telemetry 0.7.1 → 0.8.0-alpha.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 (194) hide show
  1. package/.env.example +17 -3
  2. package/README.md +1 -2
  3. package/dist/cjs/config/bootConfig.cjs +16 -14
  4. package/dist/cjs/config/config.cjs +120 -125
  5. package/dist/cjs/config/config.types.cjs +1 -4
  6. package/dist/cjs/docs/openapi.yaml +158 -4
  7. package/dist/cjs/index.cjs +27 -30
  8. package/dist/cjs/routesManager.cjs +62 -70
  9. package/dist/cjs/telemetry/custom-implementations/exporters/DiskLogExporter.cjs +121 -0
  10. package/dist/cjs/telemetry/custom-implementations/exporters/DiskMetricExporter.cjs +101 -0
  11. package/dist/cjs/telemetry/custom-implementations/exporters/DiskTraceExporter.cjs +103 -0
  12. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.cjs +194 -190
  13. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.cjs +147 -99
  14. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.cjs +143 -116
  15. package/dist/cjs/telemetry/custom-implementations/exporters/MultiMetricExporter.cjs +57 -0
  16. package/dist/cjs/telemetry/custom-implementations/instrumentations/logsInstrumentation.cjs +92 -0
  17. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/Chunk.cjs +159 -0
  18. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/Series.cjs +168 -0
  19. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.cjs +392 -0
  20. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/types.cjs +2 -0
  21. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/utils.cjs +77 -0
  22. package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.cjs +65 -63
  23. package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.cjs +63 -62
  24. package/dist/cjs/telemetry/custom-implementations/utils/circular.cjs +47 -47
  25. package/dist/cjs/telemetry/custom-implementations/wrappers.cjs +209 -138
  26. package/dist/cjs/telemetry/initializeTelemetry.cjs +35 -91
  27. package/dist/cjs/telemetry/persistence/DiskImporter.cjs +85 -0
  28. package/dist/cjs/telemetry/persistence/DiskUtils.cjs +61 -0
  29. package/dist/cjs/telemetry/persistence/DiskWriter.cjs +66 -0
  30. package/dist/cjs/telemetry/telemetryConfigurator.cjs +139 -72
  31. package/dist/cjs/telemetry/telemetryRegistry.cjs +45 -31
  32. package/dist/cjs/tlm-ai/agent.cjs +49 -64
  33. package/dist/cjs/tlm-ai/aiController.cjs +54 -76
  34. package/dist/cjs/tlm-ai/aiRoutes.cjs +17 -20
  35. package/dist/cjs/tlm-ai/aiService.cjs +91 -95
  36. package/dist/cjs/tlm-ai/tools.cjs +177 -174
  37. package/dist/cjs/tlm-auth/authController.cjs +80 -123
  38. package/dist/cjs/tlm-auth/authMiddleware.cjs +25 -30
  39. package/dist/cjs/tlm-auth/authRoutes.cjs +11 -14
  40. package/dist/cjs/tlm-log/logController.cjs +135 -116
  41. package/dist/cjs/tlm-log/logRoutes.cjs +19 -20
  42. package/dist/cjs/tlm-log/logService.cjs +29 -0
  43. package/dist/cjs/tlm-metric/metricsController.cjs +154 -122
  44. package/dist/cjs/tlm-metric/metricsRoutes.cjs +22 -20
  45. package/dist/cjs/tlm-metric/metricsService.cjs +26 -0
  46. package/dist/cjs/tlm-plugin/pluginController.cjs +128 -140
  47. package/dist/cjs/tlm-plugin/pluginProcess.cjs +89 -94
  48. package/dist/cjs/tlm-plugin/pluginRoutes.cjs +11 -14
  49. package/dist/cjs/tlm-plugin/pluginService.cjs +73 -74
  50. package/dist/cjs/tlm-trace/traceController.cjs +140 -123
  51. package/dist/cjs/tlm-trace/traceRoutes.cjs +19 -20
  52. package/dist/cjs/tlm-trace/traceService.cjs +29 -0
  53. package/dist/cjs/tlm-ui/uiRoutes.cjs +63 -32
  54. package/dist/cjs/tlm-util/utilController.cjs +68 -70
  55. package/dist/cjs/tlm-util/utilRoutes.cjs +51 -63
  56. package/dist/cjs/types/index.cjs +2 -5
  57. package/dist/cjs/utils/logger.cjs +38 -43
  58. package/dist/cjs/utils/regexUtils.cjs +22 -22
  59. package/dist/esm/config/bootConfig.js +5 -2
  60. package/dist/esm/config/config.js +9 -2
  61. package/dist/esm/docs/openapi.yaml +158 -4
  62. package/dist/esm/index.js +9 -8
  63. package/dist/esm/routesManager.js +6 -10
  64. package/dist/esm/telemetry/custom-implementations/exporters/DiskLogExporter.js +114 -0
  65. package/dist/esm/telemetry/custom-implementations/exporters/DiskMetricExporter.js +94 -0
  66. package/dist/esm/telemetry/custom-implementations/exporters/DiskTraceExporter.js +96 -0
  67. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.js +38 -7
  68. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.js +107 -48
  69. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.js +60 -29
  70. package/dist/esm/telemetry/custom-implementations/exporters/MultiMetricExporter.js +53 -0
  71. package/dist/esm/telemetry/custom-implementations/instrumentations/logsInstrumentation.js +85 -0
  72. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/Chunk.js +155 -0
  73. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/Series.js +164 -0
  74. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.js +385 -0
  75. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/types.js +1 -0
  76. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/utils.js +74 -0
  77. package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.js +2 -1
  78. package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.js +1 -1
  79. package/dist/esm/telemetry/custom-implementations/wrappers.js +77 -6
  80. package/dist/esm/telemetry/initializeTelemetry.js +27 -69
  81. package/dist/esm/telemetry/persistence/DiskImporter.js +78 -0
  82. package/dist/esm/telemetry/persistence/DiskUtils.js +51 -0
  83. package/dist/esm/telemetry/persistence/DiskWriter.js +59 -0
  84. package/dist/esm/telemetry/telemetryConfigurator.js +110 -39
  85. package/dist/esm/telemetry/telemetryRegistry.js +12 -1
  86. package/dist/esm/tlm-ai/agent.js +5 -3
  87. package/dist/esm/tlm-ai/aiController.js +3 -3
  88. package/dist/esm/tlm-ai/aiService.js +6 -2
  89. package/dist/esm/tlm-ai/tools.js +5 -9
  90. package/dist/esm/tlm-auth/authController.js +3 -2
  91. package/dist/esm/tlm-log/logController.js +62 -18
  92. package/dist/esm/tlm-log/logRoutes.js +3 -1
  93. package/dist/esm/tlm-log/logService.js +25 -0
  94. package/dist/esm/tlm-metric/metricsController.js +116 -50
  95. package/dist/esm/tlm-metric/metricsRoutes.js +8 -3
  96. package/dist/esm/tlm-metric/metricsService.js +22 -0
  97. package/dist/esm/tlm-plugin/pluginController.js +6 -11
  98. package/dist/esm/tlm-plugin/pluginService.js +2 -4
  99. package/dist/esm/tlm-trace/traceController.js +87 -36
  100. package/dist/esm/tlm-trace/traceRoutes.js +3 -1
  101. package/dist/esm/tlm-trace/traceService.js +25 -0
  102. package/dist/esm/tlm-ui/uiRoutes.js +5 -5
  103. package/dist/esm/tlm-util/utilController.js +3 -9
  104. package/dist/esm/tlm-util/utilRoutes.js +2 -2
  105. package/dist/types/config/bootConfig.d.ts +3 -0
  106. package/dist/types/config/config.d.ts +48 -7
  107. package/dist/types/config/config.types.d.ts +7 -0
  108. package/dist/types/index.d.ts +2 -3
  109. package/dist/types/telemetry/custom-implementations/exporters/DiskLogExporter.d.ts +24 -0
  110. package/dist/types/telemetry/custom-implementations/exporters/DiskMetricExporter.d.ts +23 -0
  111. package/dist/types/telemetry/custom-implementations/exporters/DiskTraceExporter.d.ts +23 -0
  112. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.d.ts +3 -1
  113. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.d.ts +56 -15
  114. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.d.ts +8 -4
  115. package/dist/types/telemetry/custom-implementations/exporters/MultiMetricExporter.d.ts +9 -0
  116. package/dist/types/telemetry/custom-implementations/instrumentations/logsInstrumentation.d.ts +23 -0
  117. package/dist/types/telemetry/custom-implementations/metrics/tsdb/Chunk.d.ts +49 -0
  118. package/dist/types/telemetry/custom-implementations/metrics/tsdb/Series.d.ts +67 -0
  119. package/dist/types/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.d.ts +69 -0
  120. package/dist/types/telemetry/custom-implementations/metrics/tsdb/types.d.ts +68 -0
  121. package/dist/types/telemetry/custom-implementations/metrics/tsdb/utils.d.ts +21 -0
  122. package/dist/types/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.d.ts +2 -2
  123. package/dist/types/telemetry/custom-implementations/wrappers.d.ts +2 -1
  124. package/dist/types/telemetry/persistence/DiskImporter.d.ts +17 -0
  125. package/dist/types/telemetry/persistence/DiskUtils.d.ts +11 -0
  126. package/dist/types/telemetry/persistence/DiskWriter.d.ts +21 -0
  127. package/dist/types/telemetry/telemetryConfigurator.d.ts +1 -1
  128. package/dist/types/telemetry/telemetryRegistry.d.ts +8 -0
  129. package/dist/types/tlm-ai/agent.d.ts +1 -1
  130. package/dist/types/tlm-ai/aiService.d.ts +1 -1
  131. package/dist/types/tlm-log/logController.d.ts +2 -0
  132. package/dist/types/tlm-log/logService.d.ts +4 -0
  133. package/dist/types/tlm-metric/metricsController.d.ts +11 -2
  134. package/dist/types/tlm-metric/metricsService.d.ts +6 -0
  135. package/dist/types/tlm-trace/traceController.d.ts +9 -7
  136. package/dist/types/tlm-trace/traceService.d.ts +4 -0
  137. package/dist/types/types/index.d.ts +2 -2
  138. package/dist/ui/assets/{ApiDocsPage-C_VVPPHa.js → ApiDocsPage-DTCgVbW2.js} +2 -2
  139. package/dist/ui/assets/CollapsibleCard-lWgfsaAn.js +1 -0
  140. package/dist/ui/assets/DevToolsPage-DEhf8CBy.js +1 -0
  141. package/dist/ui/assets/LandingPage-CfEHCDxY.js +6 -0
  142. package/dist/ui/assets/LogsPage-DFDKRuGH.js +1 -0
  143. package/dist/ui/assets/{NotFoundPage-B3quk3P1.js → NotFoundPage-DCy0DcV7.js} +1 -1
  144. package/dist/ui/assets/PluginCreatePage-BawZ5_-h.js +50 -0
  145. package/dist/ui/assets/PluginPage-D3FmgU7d.js +27 -0
  146. package/dist/ui/assets/TraceSpansPage-D0_L45Rb.js +6 -0
  147. package/dist/ui/assets/VirtualizedListPanel-q605n9He.js +16 -0
  148. package/dist/ui/assets/alert-DBAFshSi.js +1133 -0
  149. package/dist/ui/assets/badge-DGNBtnxU.js +1 -0
  150. package/dist/ui/assets/{chevron-down-CPsvsmqj.js → chevron-down-CFEqYzGC.js} +1 -1
  151. package/dist/ui/assets/{chevron-up-Df9jMo1X.js → chevron-up-lDnFwAJq.js} +1 -1
  152. package/dist/ui/assets/{circle-alert-DOPQPvU8.js → circle-alert-BpYUuRs7.js} +1 -1
  153. package/dist/ui/assets/dialog-1dRyI6SC.js +15 -0
  154. package/dist/ui/assets/index-C7RfU6hR.js +1 -0
  155. package/dist/ui/assets/index-C9dDYIpd.js +305 -0
  156. package/dist/ui/assets/index-D6f1KjWV.css +1 -0
  157. package/dist/ui/assets/info-CuJQWoBU.js +6 -0
  158. package/dist/ui/assets/{input-Dzvg_ZEZ.js → input-BLXaar0X.js} +1 -1
  159. package/dist/ui/assets/label-DfAcltsl.js +1 -0
  160. package/dist/ui/assets/{loader-circle-CrvlRy5o.js → loader-circle-B7oLyPsi.js} +1 -1
  161. package/dist/ui/assets/{loginPage-qa4V-B70.js → loginPage-DswZvOJ-.js} +1 -1
  162. package/dist/ui/assets/metrics-page-BhtXrfUW.js +31 -0
  163. package/dist/ui/assets/metrics-page-D1GxaB_c.css +1 -0
  164. package/dist/ui/assets/popover-IDker85U.js +11 -0
  165. package/dist/ui/assets/select-B8y5IidE.js +6 -0
  166. package/dist/ui/assets/separator-B6EzrxYY.js +6 -0
  167. package/dist/ui/assets/severityOptions-DtCsaAZK.js +11 -0
  168. package/dist/ui/assets/square-pen-D_oecB1x.js +6 -0
  169. package/dist/ui/assets/switch-Dqo0XkRD.js +1 -0
  170. package/dist/ui/assets/trace-DJq1miYa.js +1 -0
  171. package/dist/ui/assets/upload-prIohEdY.js +11 -0
  172. package/dist/ui/assets/{utilService-DNyqzwj0.js → utilService-C8TJKLqs.js} +1 -1
  173. package/dist/ui/assets/wand-sparkles-OgXuzsSx.js +6 -0
  174. package/dist/ui/index.html +2 -2
  175. package/package.json +44 -49
  176. package/dist/ui/assets/CollapsibleCard-B3KR_8mL.js +0 -1
  177. package/dist/ui/assets/DevToolsPage-OyZcDcmw.js +0 -1
  178. package/dist/ui/assets/LandingPage-CppFBA6K.js +0 -6
  179. package/dist/ui/assets/LogsPage-9Fq8GArS.js +0 -26
  180. package/dist/ui/assets/PluginCreatePage-X_aCH4t4.js +0 -50
  181. package/dist/ui/assets/PluginPage-DMDSihrZ.js +0 -27
  182. package/dist/ui/assets/alert-jQ9HCPIf.js +0 -1133
  183. package/dist/ui/assets/badge-CNq0-mH5.js +0 -1
  184. package/dist/ui/assets/card-DFAwwhN3.js +0 -1
  185. package/dist/ui/assets/index-BkD6DijD.js +0 -15
  186. package/dist/ui/assets/index-CERGVYZK.js +0 -292
  187. package/dist/ui/assets/index-CSIPf9qw.css +0 -1
  188. package/dist/ui/assets/label-DuVnkZ4q.js +0 -1
  189. package/dist/ui/assets/select-DhS8YUtJ.js +0 -1
  190. package/dist/ui/assets/separator-isK4chBP.js +0 -6
  191. package/dist/ui/assets/severityOptions-O38dSOfk.js +0 -11
  192. package/dist/ui/assets/switch-Z3mImG9n.js +0 -1
  193. package/dist/ui/assets/tabs-_77MUUQe.js +0 -16
  194. package/dist/ui/assets/upload-C1LT4Gkb.js +0 -16
@@ -0,0 +1,155 @@
1
+ export class Chunk {
2
+ maxSamples;
3
+ startTimes;
4
+ endTimes;
5
+ values;
6
+ histograms;
7
+ cursor = 0;
8
+ minEndTime = 0;
9
+ maxEndTime = 0;
10
+ isHistogram;
11
+ constructor(maxSamples = 120, isHistogram = false) {
12
+ this.maxSamples = maxSamples;
13
+ this.startTimes = new Float64Array(maxSamples);
14
+ this.endTimes = new Float64Array(maxSamples);
15
+ this.values = new Float64Array(maxSamples);
16
+ this.histograms = new Array(maxSamples).fill(null);
17
+ this.isHistogram = isHistogram;
18
+ }
19
+ /**
20
+ * Append a sample to the chunk.
21
+ * Returns true if successful or false if the chunk is full.
22
+ */
23
+ append(startTime, endTime, value) {
24
+ if (this.cursor >= this.maxSamples) {
25
+ return false;
26
+ }
27
+ if (this.cursor === 0) {
28
+ this.minEndTime = endTime;
29
+ }
30
+ this.startTimes[this.cursor] = startTime;
31
+ this.endTimes[this.cursor] = endTime;
32
+ if (this.isHistogram && typeof value === 'object') {
33
+ this.values[this.cursor] = value.count;
34
+ this.histograms[this.cursor] = value;
35
+ }
36
+ else if (typeof value === 'number') {
37
+ this.values[this.cursor] = value;
38
+ this.histograms[this.cursor] = null;
39
+ }
40
+ else {
41
+ const hv = value;
42
+ this.values[this.cursor] = hv.count;
43
+ this.histograms[this.cursor] = hv;
44
+ }
45
+ this.maxEndTime = endTime;
46
+ this.cursor++;
47
+ return true;
48
+ }
49
+ /**
50
+ * Binary search for first index with endTime >= targetTime
51
+ */
52
+ _binarySearchStart(targetTime) {
53
+ let left = 0;
54
+ let right = this.cursor - 1;
55
+ let result = this.cursor;
56
+ while (left <= right) {
57
+ const mid = (left + right) >> 1;
58
+ if (this.endTimes[mid] >= targetTime) {
59
+ result = mid;
60
+ right = mid - 1;
61
+ }
62
+ else {
63
+ left = mid + 1;
64
+ }
65
+ }
66
+ return result;
67
+ }
68
+ /**
69
+ * Binary search for last index with endTime <= targetTime
70
+ */
71
+ _binarySearchEnd(targetTime, startIdx = 0) {
72
+ let left = startIdx;
73
+ let right = this.cursor - 1;
74
+ let result = startIdx - 1;
75
+ while (left <= right) {
76
+ const mid = (left + right) >> 1;
77
+ if (this.endTimes[mid] <= targetTime) {
78
+ result = mid;
79
+ left = mid + 1;
80
+ }
81
+ else {
82
+ right = mid - 1;
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ /**
88
+ * Efficient slicing method for bulk operations.
89
+ * Always returns slices. Values can be either a Float64Array (numeric)
90
+ * or a JS array of HistogramValue objects (histogram series).
91
+ */
92
+ getSlices(startTime, endTime) {
93
+ const emptyResult = {
94
+ startTimes: new Float64Array(0),
95
+ endTimes: new Float64Array(0),
96
+ values: this.isHistogram ? [] : new Float64Array(0),
97
+ };
98
+ const start = startTime ?? 0;
99
+ const end = endTime ?? Number.MAX_SAFE_INTEGER;
100
+ if (this.cursor === 0 || this.maxEndTime < start || this.minEndTime > end) {
101
+ return emptyResult;
102
+ }
103
+ const startIdx = this._binarySearchStart(start);
104
+ const endIdx = this._binarySearchEnd(end, startIdx);
105
+ if (startIdx > endIdx) {
106
+ return emptyResult;
107
+ }
108
+ const startTimesNew = this.startTimes.subarray(startIdx, endIdx + 1);
109
+ const endTimesNew = this.endTimes.subarray(startIdx, endIdx + 1);
110
+ if (this.isHistogram) {
111
+ // Return histogram objects as a simple JS array slice
112
+ return {
113
+ startTimes: startTimesNew,
114
+ endTimes: endTimesNew,
115
+ values: this.histograms.slice(startIdx, endIdx + 1),
116
+ };
117
+ }
118
+ return {
119
+ startTimes: startTimesNew,
120
+ endTimes: endTimesNew,
121
+ values: this.values.subarray(startIdx, endIdx + 1),
122
+ };
123
+ }
124
+ isFull() {
125
+ return this.cursor >= this.maxSamples;
126
+ }
127
+ overlaps(startTime, endTime) {
128
+ if (this.cursor === 0)
129
+ return false;
130
+ return !(this.maxEndTime < startTime || this.minEndTime > endTime);
131
+ }
132
+ getStats() {
133
+ return {
134
+ samples: this.cursor,
135
+ maxSamples: this.maxSamples,
136
+ minEndTime: this.minEndTime,
137
+ maxEndTime: this.maxEndTime,
138
+ memoryBytes: this.getMemoryUsage(),
139
+ };
140
+ }
141
+ getMemoryUsage() {
142
+ const arrayMemory = this.maxSamples * 8 * 3;
143
+ const histogramMemory = this.histograms.filter(h => h !== null).length * 200;
144
+ return arrayMemory + histogramMemory;
145
+ }
146
+ getMinTime() {
147
+ return this.minEndTime;
148
+ }
149
+ getMaxTime() {
150
+ return this.maxEndTime;
151
+ }
152
+ size() {
153
+ return this.cursor;
154
+ }
155
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Series: Time-series storage with metadata and chunks
3
+ * One series per unique metric+labels combination
4
+ */
5
+ import { Chunk } from './Chunk.js';
6
+ export class Series {
7
+ labelSet;
8
+ metadata;
9
+ chunks = [];
10
+ maxChunks;
11
+ chunkSize;
12
+ isHistogram;
13
+ constructor(labelSet, metadata, chunkSize = 120, maxChunks = 60 // ~1 hour at 1min intervals with 120 samples/chunk
14
+ ) {
15
+ this.labelSet = labelSet;
16
+ this.metadata = metadata;
17
+ this.chunkSize = chunkSize;
18
+ this.maxChunks = maxChunks;
19
+ this.isHistogram = metadata.dataPointType === 0; // HISTOGRAM = 0
20
+ }
21
+ /**
22
+ * Append a sample to the series
23
+ */
24
+ append(startTime, endTime, value) {
25
+ // Get or create current chunk
26
+ let currentChunk = this.chunks[this.chunks.length - 1];
27
+ if (!currentChunk || currentChunk.isFull()) {
28
+ // Create new chunk
29
+ currentChunk = new Chunk(this.chunkSize, this.isHistogram);
30
+ this.chunks.push(currentChunk);
31
+ // Enforce max chunks limit (circular buffer behavior)
32
+ if (this.chunks.length > this.maxChunks) {
33
+ this.chunks.shift(); // Remove oldest chunk
34
+ }
35
+ }
36
+ currentChunk.append(startTime, endTime, value);
37
+ }
38
+ /**
39
+ * Query slices with options object. Example:
40
+ * querySlices({ startTime, endTime, includeStartTimes: true })
41
+ * includeStartTimes defaults to false.
42
+ * If both startTime and endTime are undefined, returns all data (no time filtering).
43
+ */
44
+ querySlices(options) {
45
+ const start = options?.startTime ?? 0;
46
+ const end = options?.endTime ?? Number.MAX_VALUE;
47
+ const includeStartTimes = options?.includeStartTimes ?? false;
48
+ let totalLength = 0;
49
+ for (const chunk of this.chunks) {
50
+ if (!chunk.overlaps(start, end))
51
+ continue;
52
+ const slice = chunk.getSlices(start, end);
53
+ totalLength += slice.startTimes.length;
54
+ }
55
+ if (totalLength === 0) {
56
+ const empty = {
57
+ endTimes: new Float64Array(0),
58
+ values: this.isHistogram ? [] : new Float64Array(0),
59
+ };
60
+ if (includeStartTimes) {
61
+ empty.startTimes = new Float64Array(0);
62
+ }
63
+ return empty;
64
+ }
65
+ let resultStart;
66
+ if (includeStartTimes) {
67
+ resultStart = new Float64Array(totalLength);
68
+ }
69
+ const resultEnd = new Float64Array(totalLength);
70
+ const resultValues = this.isHistogram
71
+ ? new Array(totalLength)
72
+ : new Float64Array(totalLength);
73
+ let offset = 0;
74
+ for (const chunk of this.chunks) {
75
+ if (!chunk.overlaps(start, end))
76
+ continue;
77
+ const slice = chunk.getSlices(start, end);
78
+ const len = slice.startTimes.length;
79
+ if (len === 0)
80
+ continue;
81
+ // Copy start and end times
82
+ if (includeStartTimes && resultStart) {
83
+ resultStart.set(slice.startTimes, offset);
84
+ }
85
+ resultEnd.set(slice.endTimes, offset);
86
+ // Copy values (different type depending on metric)
87
+ if (this.isHistogram) {
88
+ resultValues.splice(offset, len, ...slice.values);
89
+ }
90
+ else {
91
+ resultValues.set(slice.values, offset);
92
+ }
93
+ offset += len;
94
+ }
95
+ const result = {
96
+ endTimes: resultEnd,
97
+ values: resultValues
98
+ };
99
+ if (includeStartTimes && resultStart) {
100
+ result.startTimes = resultStart;
101
+ }
102
+ return result;
103
+ }
104
+ /**
105
+ * Remove chunks older than threshold
106
+ */
107
+ evictOldChunks(thresholdTime) {
108
+ let evicted = 0;
109
+ while (this.chunks.length > 0 && this.chunks[0].getMaxTime() < thresholdTime) {
110
+ this.chunks.shift();
111
+ evicted++;
112
+ }
113
+ return evicted;
114
+ }
115
+ /**
116
+ * Get series metadata
117
+ */
118
+ getMetadata() {
119
+ return this.metadata;
120
+ }
121
+ getLabels() {
122
+ return this.labelSet.labels;
123
+ }
124
+ getOriginalAttributes() {
125
+ return this.labelSet.originalAttributes;
126
+ }
127
+ /**
128
+ * Get label hash
129
+ */
130
+ getLabelHash() {
131
+ return this.labelSet.hash;
132
+ }
133
+ getStats() {
134
+ const totalSamples = this.chunks.reduce((sum, chunk) => sum + chunk.size(), 0);
135
+ const memoryBytes = this.chunks.reduce((sum, chunk) => sum + chunk.getMemoryUsage(), 0);
136
+ return {
137
+ metricName: this.metadata?.descriptor?.name ?? 'unknown',
138
+ labels: this.labelSet?.labels ?? {},
139
+ chunks: this.chunks.length,
140
+ samples: totalSamples,
141
+ memoryBytes,
142
+ oldestTime: this.chunks[0]?.getMinTime() ?? 0,
143
+ newestTime: this.chunks[this.chunks.length - 1]?.getMaxTime() ?? 0
144
+ };
145
+ }
146
+ /**
147
+ * Check if series has any samples
148
+ */
149
+ isEmpty() {
150
+ return this.chunks.length === 0 || this.chunks.every(c => c.size() === 0);
151
+ }
152
+ /**
153
+ * Get time range covered by this series
154
+ */
155
+ getTimeRange() {
156
+ if (this.chunks.length === 0) {
157
+ return { min: 0, max: 0 };
158
+ }
159
+ return {
160
+ min: this.chunks[0].getMinTime(),
161
+ max: this.chunks[this.chunks.length - 1].getMaxTime()
162
+ };
163
+ }
164
+ }
@@ -0,0 +1,385 @@
1
+ import { Series } from './Series.js';
2
+ import { Chunk } from './Chunk.js';
3
+ /*
4
+ OTEL export = Resource (unchanged) + SCOPE METRICS
5
+ scopeMetrics: {
6
+ scope: {name: library, version: 1.0.0},; //repeated for each metric
7
+ metrics: [
8
+ {
9
+ //Metadata (Repeated for each dataPoint)
10
+ descriptor: {name: metricName, unit, description, ...},
11
+ aggregationTemporality,
12
+ dataPointType,
13
+ // Actual dataPoints are stored in Series
14
+ dataPoints: []
15
+ }
16
+ ]
17
+ }
18
+ */
19
+ // Import fs at top level for disk operations
20
+ import fs from 'fs';
21
+ import logger from '../../../../utils/logger.js';
22
+ export class SeriesRegistry {
23
+ // Main storage
24
+ series = new Map(); // seriesId (scopeId:metricName:attributesId) -> Series
25
+ // Cached repeated data
26
+ scopes = new Map(); // scopeId -> scope
27
+ metricMetadataMap = new Map(); // "scopeId:metricName" -> MetricMetadata
28
+ // Faster lookup index
29
+ metricIdIndex = new Map(); // "scopeId:metricName" -> Set<seriesId>
30
+ chunkSize;
31
+ maxChunks;
32
+ constructor(chunkSize = 120, maxChunks = 60) {
33
+ this.chunkSize = chunkSize;
34
+ this.maxChunks = maxChunks;
35
+ }
36
+ /**
37
+ * Store entire ScopeMetrics array efficiently
38
+ * Replaces addSample - processes all metrics in one batch
39
+ */
40
+ storeScopeMetrics(scopeMetrics) {
41
+ for (const scopeMetric of scopeMetrics) {
42
+ const scope = scopeMetric.scope;
43
+ const scopeId = scopeToId(scope);
44
+ this.scopes.set(scopeId, scope);
45
+ for (const metricData of scopeMetric.metrics) {
46
+ const metricName = metricData.descriptor.name;
47
+ const metricId = makeMetricId(scopeId, metricName);
48
+ if (!this.metricMetadataMap.has(metricId)) {
49
+ const { dataPoints: _dataPoints, ...metadata } = metricData;
50
+ this.metricMetadataMap.set(metricId, metadata);
51
+ }
52
+ for (const dp of metricData.dataPoints) {
53
+ const startTime = dp.startTime[0] * 1_000_000_000 + dp.startTime[1];
54
+ const endTime = dp.endTime[0] * 1_000_000_000 + dp.endTime[1];
55
+ const attributes = dp.attributes;
56
+ const attributesId = attributesToId(attributes);
57
+ const seriesId = makeSeriesId(metricId, attributesId);
58
+ if (!this.series.has(seriesId)) {
59
+ this.series.set(seriesId, new Series({ hash: 0, labels: attributes, originalAttributes: attributes }, this.metricMetadataMap.get(metricId), this.chunkSize, this.maxChunks));
60
+ (this.metricIdIndex.get(metricId) ?? this.metricIdIndex.set(metricId, new Set()).get(metricId)).add(seriesId);
61
+ }
62
+ this.series.get(seriesId).append(startTime, endTime, dp.value);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ query(scopeMetrics, startTime, endTime) {
68
+ // If no queries provided, get all scopeMetrics
69
+ if (!scopeMetrics || scopeMetrics.length === 0) {
70
+ const queries = Array.from(this.metricIdIndex.keys()).map(indexKey => {
71
+ const [scopeId, metricName] = indexKey.split(':').slice(0, 2);
72
+ const scope = this.scopes.get(scopeId);
73
+ return {
74
+ scope,
75
+ descriptor: { name: metricName },
76
+ filters: undefined
77
+ };
78
+ });
79
+ return queries
80
+ .map(query => this._querySingle(query, startTime, endTime))
81
+ .filter((result) => result !== null);
82
+ }
83
+ return scopeMetrics
84
+ .map(query => this._querySingle(query, startTime, endTime))
85
+ .filter((result) => result !== null);
86
+ }
87
+ /**
88
+ * Query single metric with attribute filters
89
+ */
90
+ _querySingle(query, startTime, endTime) {
91
+ const scopeId = scopeToId(query.scope);
92
+ const indexKey = makeMetricId(scopeId, query.descriptor.name);
93
+ const seriesKeysInMetric = this.metricIdIndex.get(indexKey);
94
+ if (!seriesKeysInMetric || seriesKeysInMetric.size === 0) {
95
+ return null;
96
+ }
97
+ const scope = this.scopes.get(scopeId);
98
+ const metricData = this.metricMetadataMap.get(indexKey);
99
+ if (!scope || !metricData) {
100
+ return null;
101
+ }
102
+ // Filter by attributes if provided
103
+ const filteredSeriesKeys = query.filters
104
+ ? Array.from(seriesKeysInMetric).filter(key => {
105
+ const series = this.series.get(key);
106
+ return this._matchesAttributeFilters(series, query.filters);
107
+ })
108
+ : Array.from(seriesKeysInMetric);
109
+ if (filteredSeriesKeys.length === 0) {
110
+ return null;
111
+ }
112
+ const matchingSeries = filteredSeriesKeys
113
+ .map(key => {
114
+ const series = this.series.get(key);
115
+ const { startTimes, endTimes, values } = series.querySlices({ startTime, endTime, includeStartTimes: true });
116
+ return {
117
+ id: key,
118
+ attributes: series.getOriginalAttributes(),
119
+ startTimes: startTimes ? Array.from(startTimes) : undefined,
120
+ endTimes: Array.from(endTimes),
121
+ values: Array.isArray(values) ? values : Array.from(values)
122
+ };
123
+ })
124
+ .filter(s => s.endTimes.length > 0);
125
+ return {
126
+ scope,
127
+ descriptor: metricData.descriptor,
128
+ series: matchingSeries
129
+ };
130
+ }
131
+ /**
132
+ * Match series attributes against filters
133
+ * Supports exact match, negation (!), and regex (~)
134
+ */
135
+ _matchesAttributeFilters(series, filters) {
136
+ if (!filters || Object.keys(filters).length === 0) {
137
+ return true;
138
+ }
139
+ const attrs = series.getOriginalAttributes();
140
+ for (const [key, filterValue] of Object.entries(filters)) {
141
+ const attrValue = String(attrs[key] ?? '');
142
+ // Regex match: status=~4.*
143
+ if (filterValue.startsWith('~')) {
144
+ const pattern = filterValue.slice(1);
145
+ try {
146
+ const regex = new RegExp(pattern);
147
+ if (!regex.test(attrValue)) {
148
+ return false;
149
+ }
150
+ }
151
+ catch {
152
+ if (attrValue !== filterValue) {
153
+ return false;
154
+ }
155
+ }
156
+ }
157
+ // Negation: status=!200
158
+ else if (filterValue.startsWith('!')) {
159
+ const negatedValue = filterValue.slice(1);
160
+ if (attrValue === negatedValue) {
161
+ return false;
162
+ }
163
+ }
164
+ // Exact match: method=GET
165
+ else {
166
+ if (attrValue !== filterValue) {
167
+ return false;
168
+ }
169
+ }
170
+ }
171
+ return true;
172
+ }
173
+ evictOldData(retentionTimeNs) {
174
+ const thresholdTime = Date.now() * 1_000_000 - retentionTimeNs;
175
+ let evictedChunks = 0;
176
+ let evictedSeries = 0;
177
+ for (const [key, series] of this.series.entries()) {
178
+ evictedChunks += series.evictOldChunks(thresholdTime);
179
+ if (series.isEmpty()) {
180
+ this.series.delete(key);
181
+ evictedSeries++;
182
+ }
183
+ }
184
+ return { evictedChunks, evictedSeries };
185
+ }
186
+ getStats() {
187
+ const seriesStats = Array.from(this.series.values()).map(s => s.getStats());
188
+ const totalSamples = seriesStats.reduce((sum, s) => sum + s.samples, 0);
189
+ const totalMemory = seriesStats.reduce((sum, s) => sum + s.memoryBytes, 0);
190
+ return {
191
+ totalMetrics: this.metricMetadataMap.size,
192
+ totalScopes: this.scopes.size,
193
+ totalSeries: this.series.size,
194
+ totalSamples,
195
+ memoryUsageBytes: totalMemory
196
+ };
197
+ }
198
+ reset() {
199
+ this.series.clear();
200
+ this.scopes.clear();
201
+ this.metricMetadataMap.clear();
202
+ this.metricIdIndex.clear();
203
+ }
204
+ size() {
205
+ return this.series.size;
206
+ }
207
+ /**
208
+ * Serialize registry to line-delimited JSON format (newline-delimited JSON)
209
+ * Each line is a complete record: metadata, scope, metric, or data point
210
+ * Format:
211
+ * {"type":"header","version":1,"timestamp":"...","stats":{...}}
212
+ * {"type":"scope","id":"...","data":{...}}
213
+ * {"type":"metric","id":"...","data":{...}}
214
+ * {"type":"series","id":"...","labelSet":{...},"chunks":[...]}
215
+ */
216
+ serializeToLineDelimitedJson() {
217
+ const lines = [];
218
+ // Header
219
+ lines.push(JSON.stringify({
220
+ type: 'header',
221
+ version: 1,
222
+ timestamp: new Date().toISOString(),
223
+ stats: this.getStats()
224
+ }));
225
+ // Scopes
226
+ for (const [scopeId, scope] of this.scopes.entries()) {
227
+ lines.push(JSON.stringify({
228
+ type: 'scope',
229
+ id: scopeId,
230
+ data: scope
231
+ }));
232
+ }
233
+ // Metric metadata
234
+ for (const [metricId, metadata] of this.metricMetadataMap.entries()) {
235
+ lines.push(JSON.stringify({
236
+ type: 'metric',
237
+ id: metricId,
238
+ data: metadata
239
+ }));
240
+ }
241
+ // Series headers + chunks (one chunk per line for streaming efficiency)
242
+ const serializedSeriesHeaders = new Set();
243
+ for (const [seriesId, series] of this.series.entries()) {
244
+ const seriesPrivate = series;
245
+ // Emit series header once per series
246
+ if (!serializedSeriesHeaders.has(seriesId)) {
247
+ lines.push(JSON.stringify({
248
+ type: 'series',
249
+ seriesId: seriesId,
250
+ labelSet: seriesPrivate.labelSet,
251
+ metadata: seriesPrivate.metadata
252
+ }));
253
+ serializedSeriesHeaders.add(seriesId);
254
+ }
255
+ (seriesPrivate.chunks || []).forEach((chunk, chunkIndex) => {
256
+ const slicedStartTimes = chunk.startTimes.slice(0, chunk.cursor);
257
+ const slicedEndTimes = chunk.endTimes.slice(0, chunk.cursor);
258
+ const slicedValues = chunk.values.slice(0, chunk.cursor);
259
+ const slicedHistograms = chunk.histograms.slice(0, chunk.cursor);
260
+ lines.push(JSON.stringify({
261
+ type: 'chunk',
262
+ seriesId: seriesId,
263
+ chunkIndex: chunkIndex,
264
+ startTimes: Array.from(slicedStartTimes),
265
+ endTimes: Array.from(slicedEndTimes),
266
+ values: Array.from(slicedValues),
267
+ histograms: Array.from(slicedHistograms),
268
+ cursor: chunk.cursor,
269
+ minEndTime: chunk.minEndTime,
270
+ maxEndTime: chunk.maxEndTime,
271
+ isHistogram: chunk.isHistogram
272
+ }));
273
+ });
274
+ }
275
+ // Metric ID index for fast lookup
276
+ for (const [metricId, seriesIdSet] of this.metricIdIndex.entries()) {
277
+ lines.push(JSON.stringify({
278
+ type: 'index',
279
+ id: metricId,
280
+ seriesIds: Array.from(seriesIdSet)
281
+ }));
282
+ }
283
+ return lines.join('\n');
284
+ }
285
+ /**
286
+ * Deserialize from line-delimited JSON format - restore from chunk lines
287
+ */
288
+ deserializeFromLineDelimitedJson(lineDelimitedJsonData) {
289
+ try {
290
+ const lines = lineDelimitedJsonData.trim().split('\n');
291
+ for (const line of lines) {
292
+ if (!line.trim())
293
+ continue;
294
+ const record = JSON.parse(line);
295
+ switch (record.type) {
296
+ case 'header':
297
+ // Just metadata
298
+ break;
299
+ case 'scope':
300
+ this.scopes.set(record.id, record.data);
301
+ break;
302
+ case 'metric':
303
+ this.metricMetadataMap.set(record.id, record.data);
304
+ break;
305
+ case 'index':
306
+ this.metricIdIndex.set(record.id, new Set(record.seriesIds));
307
+ break;
308
+ case 'series': {
309
+ // Create series stub - will be populated by subsequent chunk lines
310
+ const metadata = this.metricMetadataMap.get(record.seriesId.substring(0, record.seriesId.lastIndexOf('$'))) || record.metadata;
311
+ if (metadata) {
312
+ const series = new Series(record.labelSet, metadata, this.chunkSize, this.maxChunks);
313
+ this.series.set(record.seriesId, series);
314
+ }
315
+ break;
316
+ }
317
+ case 'chunk': {
318
+ // Restore chunk to series
319
+ const series = this.series.get(record.seriesId);
320
+ if (series) {
321
+ const chunk = new Chunk(record.cursor || record.startTimes.length, record.isHistogram);
322
+ const chunkPrivate = chunk;
323
+ chunkPrivate.startTimes = new Float64Array(record.startTimes);
324
+ chunkPrivate.endTimes = new Float64Array(record.endTimes);
325
+ chunkPrivate.values = new Float64Array(record.values);
326
+ chunkPrivate.histograms = record.histograms;
327
+ chunkPrivate.cursor = record.cursor;
328
+ chunkPrivate.minEndTime = record.minEndTime;
329
+ chunkPrivate.maxEndTime = record.maxEndTime;
330
+ chunkPrivate.isHistogram = record.isHistogram;
331
+ const seriesPrivate = series;
332
+ seriesPrivate.chunks.push(chunk);
333
+ }
334
+ break;
335
+ }
336
+ }
337
+ }
338
+ }
339
+ catch {
340
+ logger.error(`Failed to deserialize metrics from line-delimited JSON`);
341
+ }
342
+ }
343
+ /**
344
+ * Save registry to disk as line-delimited JSON (one chunk per line)
345
+ */
346
+ saveToDisk(filePath) {
347
+ try {
348
+ const lineDelimitedJsonData = this.serializeToLineDelimitedJson();
349
+ fs.writeFileSync(filePath, lineDelimitedJsonData);
350
+ }
351
+ catch {
352
+ logger.error(`Failed to save metrics to disk at ${filePath}`);
353
+ }
354
+ }
355
+ /**
356
+ * Load registry from disk (line-delimited JSON format)
357
+ */
358
+ loadFromDisk(filePath) {
359
+ try {
360
+ if (!fs.existsSync(filePath)) {
361
+ return;
362
+ }
363
+ const lineDelimitedJsonData = fs.readFileSync(filePath, 'utf-8');
364
+ this.deserializeFromLineDelimitedJson(lineDelimitedJsonData);
365
+ }
366
+ catch {
367
+ logger.error(`Failed to load metrics from disk at ${filePath}`);
368
+ // Fallback to memory.
369
+ this.reset();
370
+ }
371
+ }
372
+ }
373
+ // Utility: Create deterministic ID from attributes
374
+ function attributesToId(attributes) {
375
+ return Object.keys(attributes).sort().map(key => `${key}=${attributes[key]}`).join(',') || 'no_attrs';
376
+ }
377
+ function scopeToId(scope) {
378
+ return `${scope.name}@${scope.version ?? 'no_version'}`;
379
+ }
380
+ function makeMetricId(scopeId, metricName) {
381
+ return `${scopeId}:${metricName}`;
382
+ }
383
+ function makeSeriesId(metricId, attributesId) {
384
+ return `${metricId}$${attributesId}`;
385
+ }