@aleph-ai/tinyaleph 1.4.1 → 1.4.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aleph-ai/tinyaleph",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Prime-resonant semantic computing framework - hypercomplex algebra, oscillator dynamics, and entropy-minimizing reasoning",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -25,6 +25,10 @@
25
25
  "./engine": {
26
26
  "require": "./engine/index.js",
27
27
  "import": "./engine/index.js"
28
+ },
29
+ "./telemetry": {
30
+ "require": "./telemetry/metrics.js",
31
+ "import": "./telemetry/metrics.js"
28
32
  }
29
33
  },
30
34
  "scripts": {
@@ -84,6 +88,7 @@
84
88
  "physics/",
85
89
  "backends/",
86
90
  "engine/",
91
+ "telemetry/",
87
92
  "types/",
88
93
  "docs/",
89
94
  "LICENSE",
@@ -0,0 +1,708 @@
1
+ /**
2
+ * Telemetry Metrics for TinyAleph
3
+ *
4
+ * Provides Prometheus/OpenTelemetry-compatible metric types:
5
+ * - Counter: Monotonically increasing value
6
+ * - Gauge: Value that can go up and down
7
+ * - Histogram: Distribution of values in buckets
8
+ * - Summary: Quantile distribution
9
+ *
10
+ * Browser-compatible: No Node.js dependencies (HTTP server removed).
11
+ * Server endpoints should be implemented in the application layer.
12
+ * Extracted from apps/sentient/lib/telemetry.js for library reuse.
13
+ */
14
+
15
+ const { SimpleEventEmitter } = require('../core/errors');
16
+
17
+ // ============================================================================
18
+ // METRIC TYPES
19
+ // ============================================================================
20
+
21
+ const MetricType = {
22
+ COUNTER: 'counter',
23
+ GAUGE: 'gauge',
24
+ HISTOGRAM: 'histogram',
25
+ SUMMARY: 'summary'
26
+ };
27
+
28
+ // ============================================================================
29
+ // BASE METRIC CLASS
30
+ // ============================================================================
31
+
32
+ /**
33
+ * Base metric class
34
+ */
35
+ class Metric {
36
+ constructor(name, options = {}) {
37
+ this.name = name;
38
+ this.help = options.help || '';
39
+ this.labels = options.labels || [];
40
+ this.type = MetricType.COUNTER;
41
+ this.values = new Map(); // labelKey -> value
42
+ }
43
+
44
+ /**
45
+ * Get label key for a set of labels
46
+ * @param {Object} labels - Label values
47
+ * @returns {string}
48
+ */
49
+ getLabelKey(labels = {}) {
50
+ if (this.labels.length === 0) return '';
51
+ return this.labels.map(l => labels[l] || '').join(',');
52
+ }
53
+
54
+ /**
55
+ * Format labels for Prometheus exposition
56
+ * @param {Object} labels - Label values
57
+ * @returns {string}
58
+ */
59
+ formatLabels(labels = {}) {
60
+ if (this.labels.length === 0) return '';
61
+ const pairs = this.labels
62
+ .filter(l => labels[l] !== undefined)
63
+ .map(l => `${l}="${labels[l]}"`);
64
+ return pairs.length > 0 ? `{${pairs.join(',')}}` : '';
65
+ }
66
+
67
+ /**
68
+ * Export to Prometheus format
69
+ * @returns {string}
70
+ */
71
+ toPrometheus() {
72
+ throw new Error('toPrometheus must be implemented');
73
+ }
74
+
75
+ /**
76
+ * Export to OpenTelemetry format
77
+ * @returns {Object}
78
+ */
79
+ toOTLP() {
80
+ throw new Error('toOTLP must be implemented');
81
+ }
82
+
83
+ /**
84
+ * Reset metric
85
+ */
86
+ reset() {
87
+ this.values.clear();
88
+ }
89
+ }
90
+
91
+ // ============================================================================
92
+ // COUNTER
93
+ // ============================================================================
94
+
95
+ /**
96
+ * Counter metric - monotonically increasing value
97
+ */
98
+ class Counter extends Metric {
99
+ constructor(name, options = {}) {
100
+ super(name, options);
101
+ this.type = MetricType.COUNTER;
102
+ }
103
+
104
+ /**
105
+ * Increment the counter
106
+ * @param {number} value - Amount to increment (default 1)
107
+ * @param {Object} labels - Label values
108
+ */
109
+ inc(value = 1, labels = {}) {
110
+ if (value < 0) {
111
+ throw new Error('Counter can only be incremented');
112
+ }
113
+ const key = this.getLabelKey(labels);
114
+ const current = this.values.get(key) || { value: 0, labels };
115
+ current.value += value;
116
+ this.values.set(key, current);
117
+ }
118
+
119
+ /**
120
+ * Get current value
121
+ * @param {Object} labels - Label values
122
+ * @returns {number}
123
+ */
124
+ get(labels = {}) {
125
+ const key = this.getLabelKey(labels);
126
+ return (this.values.get(key) || { value: 0 }).value;
127
+ }
128
+
129
+ toPrometheus() {
130
+ const lines = [];
131
+ lines.push(`# HELP ${this.name} ${this.help}`);
132
+ lines.push(`# TYPE ${this.name} counter`);
133
+
134
+ for (const { value, labels } of this.values.values()) {
135
+ const labelStr = this.formatLabels(labels);
136
+ lines.push(`${this.name}${labelStr} ${value}`);
137
+ }
138
+
139
+ if (this.values.size === 0) {
140
+ lines.push(`${this.name} 0`);
141
+ }
142
+
143
+ return lines.join('\n');
144
+ }
145
+
146
+ toOTLP() {
147
+ const dataPoints = [];
148
+ for (const { value, labels } of this.values.values()) {
149
+ dataPoints.push({
150
+ attributes: Object.entries(labels).map(([k, v]) => ({ key: k, value: { stringValue: String(v) } })),
151
+ asInt: value,
152
+ timeUnixNano: Date.now() * 1e6
153
+ });
154
+ }
155
+
156
+ return {
157
+ name: this.name,
158
+ description: this.help,
159
+ sum: {
160
+ dataPoints,
161
+ aggregationTemporality: 2, // CUMULATIVE
162
+ isMonotonic: true
163
+ }
164
+ };
165
+ }
166
+ }
167
+
168
+ // ============================================================================
169
+ // GAUGE
170
+ // ============================================================================
171
+
172
+ /**
173
+ * Gauge metric - value that can go up and down
174
+ */
175
+ class Gauge extends Metric {
176
+ constructor(name, options = {}) {
177
+ super(name, options);
178
+ this.type = MetricType.GAUGE;
179
+ }
180
+
181
+ /**
182
+ * Set gauge value
183
+ * @param {number} value - New value
184
+ * @param {Object} labels - Label values
185
+ */
186
+ set(value, labels = {}) {
187
+ const key = this.getLabelKey(labels);
188
+ this.values.set(key, { value, labels });
189
+ }
190
+
191
+ /**
192
+ * Increment gauge
193
+ * @param {number} value - Amount to increment
194
+ * @param {Object} labels - Label values
195
+ */
196
+ inc(value = 1, labels = {}) {
197
+ const current = this.get(labels);
198
+ this.set(current + value, labels);
199
+ }
200
+
201
+ /**
202
+ * Decrement gauge
203
+ * @param {number} value - Amount to decrement
204
+ * @param {Object} labels - Label values
205
+ */
206
+ dec(value = 1, labels = {}) {
207
+ const current = this.get(labels);
208
+ this.set(current - value, labels);
209
+ }
210
+
211
+ /**
212
+ * Get current value
213
+ * @param {Object} labels - Label values
214
+ * @returns {number}
215
+ */
216
+ get(labels = {}) {
217
+ const key = this.getLabelKey(labels);
218
+ return (this.values.get(key) || { value: 0 }).value;
219
+ }
220
+
221
+ /**
222
+ * Set to current time
223
+ * @param {Object} labels - Label values
224
+ */
225
+ setToCurrentTime(labels = {}) {
226
+ this.set(Date.now() / 1000, labels);
227
+ }
228
+
229
+ toPrometheus() {
230
+ const lines = [];
231
+ lines.push(`# HELP ${this.name} ${this.help}`);
232
+ lines.push(`# TYPE ${this.name} gauge`);
233
+
234
+ for (const { value, labels } of this.values.values()) {
235
+ const labelStr = this.formatLabels(labels);
236
+ lines.push(`${this.name}${labelStr} ${value}`);
237
+ }
238
+
239
+ if (this.values.size === 0) {
240
+ lines.push(`${this.name} 0`);
241
+ }
242
+
243
+ return lines.join('\n');
244
+ }
245
+
246
+ toOTLP() {
247
+ const dataPoints = [];
248
+ for (const { value, labels } of this.values.values()) {
249
+ dataPoints.push({
250
+ attributes: Object.entries(labels).map(([k, v]) => ({ key: k, value: { stringValue: String(v) } })),
251
+ asDouble: value,
252
+ timeUnixNano: Date.now() * 1e6
253
+ });
254
+ }
255
+
256
+ return {
257
+ name: this.name,
258
+ description: this.help,
259
+ gauge: { dataPoints }
260
+ };
261
+ }
262
+ }
263
+
264
+ // ============================================================================
265
+ // HISTOGRAM
266
+ // ============================================================================
267
+
268
+ /**
269
+ * Histogram metric - distribution of values in buckets
270
+ */
271
+ class Histogram extends Metric {
272
+ constructor(name, options = {}) {
273
+ super(name, options);
274
+ this.type = MetricType.HISTOGRAM;
275
+ this.buckets = options.buckets || [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
276
+ this.buckets.sort((a, b) => a - b);
277
+ }
278
+
279
+ /**
280
+ * Observe a value
281
+ * @param {number} value - Value to observe
282
+ * @param {Object} labels - Label values
283
+ */
284
+ observe(value, labels = {}) {
285
+ const key = this.getLabelKey(labels);
286
+ let entry = this.values.get(key);
287
+
288
+ if (!entry) {
289
+ entry = {
290
+ labels,
291
+ sum: 0,
292
+ count: 0,
293
+ buckets: new Map()
294
+ };
295
+ for (const bucket of this.buckets) {
296
+ entry.buckets.set(bucket, 0);
297
+ }
298
+ entry.buckets.set(Infinity, 0);
299
+ this.values.set(key, entry);
300
+ }
301
+
302
+ entry.sum += value;
303
+ entry.count++;
304
+
305
+ for (const bucket of this.buckets) {
306
+ if (value <= bucket) {
307
+ entry.buckets.set(bucket, entry.buckets.get(bucket) + 1);
308
+ }
309
+ }
310
+ entry.buckets.set(Infinity, entry.buckets.get(Infinity) + 1);
311
+ }
312
+
313
+ /**
314
+ * Time a function and observe duration
315
+ * @param {Function} fn - Function to time
316
+ * @param {Object} labels - Label values
317
+ * @returns {*} Function result
318
+ */
319
+ time(fn, labels = {}) {
320
+ const start = typeof performance !== 'undefined' ? performance.now() : Date.now();
321
+ try {
322
+ return fn();
323
+ } finally {
324
+ const end = typeof performance !== 'undefined' ? performance.now() : Date.now();
325
+ const duration = (end - start) / 1000; // Convert to seconds
326
+ this.observe(duration, labels);
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Time an async function
332
+ * @param {Function} fn - Async function to time
333
+ * @param {Object} labels - Label values
334
+ * @returns {Promise<*>} Function result
335
+ */
336
+ async timeAsync(fn, labels = {}) {
337
+ const start = typeof performance !== 'undefined' ? performance.now() : Date.now();
338
+ try {
339
+ return await fn();
340
+ } finally {
341
+ const end = typeof performance !== 'undefined' ? performance.now() : Date.now();
342
+ const duration = (end - start) / 1000;
343
+ this.observe(duration, labels);
344
+ }
345
+ }
346
+
347
+ toPrometheus() {
348
+ const lines = [];
349
+ lines.push(`# HELP ${this.name} ${this.help}`);
350
+ lines.push(`# TYPE ${this.name} histogram`);
351
+
352
+ for (const entry of this.values.values()) {
353
+ const labelStr = this.formatLabels(entry.labels);
354
+ const baseName = this.name;
355
+
356
+ // Bucket lines
357
+ for (const bucket of this.buckets) {
358
+ const bucketLabel = labelStr
359
+ ? labelStr.slice(0, -1) + `,le="${bucket}"}`
360
+ : `{le="${bucket}"}`;
361
+ lines.push(`${baseName}_bucket${bucketLabel} ${entry.buckets.get(bucket)}`);
362
+ }
363
+
364
+ // +Inf bucket
365
+ const infLabel = labelStr
366
+ ? labelStr.slice(0, -1) + `,le="+Inf"}`
367
+ : `{le="+Inf"}`;
368
+ lines.push(`${baseName}_bucket${infLabel} ${entry.buckets.get(Infinity)}`);
369
+
370
+ // Sum and count
371
+ lines.push(`${baseName}_sum${labelStr} ${entry.sum}`);
372
+ lines.push(`${baseName}_count${labelStr} ${entry.count}`);
373
+ }
374
+
375
+ return lines.join('\n');
376
+ }
377
+
378
+ toOTLP() {
379
+ const dataPoints = [];
380
+
381
+ for (const entry of this.values.values()) {
382
+ const bucketCounts = [];
383
+ let cumulative = 0;
384
+
385
+ for (const bucket of this.buckets) {
386
+ cumulative += entry.buckets.get(bucket);
387
+ bucketCounts.push(cumulative);
388
+ }
389
+
390
+ dataPoints.push({
391
+ attributes: Object.entries(entry.labels).map(([k, v]) => ({ key: k, value: { stringValue: String(v) } })),
392
+ count: entry.count,
393
+ sum: entry.sum,
394
+ bucketCounts,
395
+ explicitBounds: this.buckets,
396
+ timeUnixNano: Date.now() * 1e6
397
+ });
398
+ }
399
+
400
+ return {
401
+ name: this.name,
402
+ description: this.help,
403
+ histogram: {
404
+ dataPoints,
405
+ aggregationTemporality: 2 // CUMULATIVE
406
+ }
407
+ };
408
+ }
409
+ }
410
+
411
+ // ============================================================================
412
+ // SUMMARY
413
+ // ============================================================================
414
+
415
+ /**
416
+ * Summary metric - quantile distribution
417
+ */
418
+ class Summary extends Metric {
419
+ constructor(name, options = {}) {
420
+ super(name, options);
421
+ this.type = MetricType.SUMMARY;
422
+ this.quantiles = options.quantiles || [0.5, 0.9, 0.99];
423
+ this.maxAge = options.maxAge || 600000; // 10 minutes
424
+ }
425
+
426
+ /**
427
+ * Observe a value
428
+ * @param {number} value - Value to observe
429
+ * @param {Object} labels - Label values
430
+ */
431
+ observe(value, labels = {}) {
432
+ const key = this.getLabelKey(labels);
433
+ let entry = this.values.get(key);
434
+
435
+ if (!entry) {
436
+ entry = {
437
+ labels,
438
+ sum: 0,
439
+ count: 0,
440
+ samples: []
441
+ };
442
+ this.values.set(key, entry);
443
+ }
444
+
445
+ entry.sum += value;
446
+ entry.count++;
447
+ entry.samples.push({ value, timestamp: Date.now() });
448
+
449
+ // Age out old samples
450
+ const cutoff = Date.now() - this.maxAge;
451
+ entry.samples = entry.samples.filter(s => s.timestamp > cutoff);
452
+ }
453
+
454
+ /**
455
+ * Calculate quantile value
456
+ * @param {Array} samples - Samples
457
+ * @param {number} q - Quantile (0-1)
458
+ * @returns {number}
459
+ */
460
+ calculateQuantile(samples, q) {
461
+ if (samples.length === 0) return 0;
462
+
463
+ const sorted = samples.map(s => s.value).sort((a, b) => a - b);
464
+ const pos = q * (sorted.length - 1);
465
+ const lower = Math.floor(pos);
466
+ const upper = Math.ceil(pos);
467
+
468
+ if (lower === upper) return sorted[lower];
469
+
470
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * (pos - lower);
471
+ }
472
+
473
+ toPrometheus() {
474
+ const lines = [];
475
+ lines.push(`# HELP ${this.name} ${this.help}`);
476
+ lines.push(`# TYPE ${this.name} summary`);
477
+
478
+ for (const entry of this.values.values()) {
479
+ const labelStr = this.formatLabels(entry.labels);
480
+
481
+ // Quantile lines
482
+ for (const q of this.quantiles) {
483
+ const qValue = this.calculateQuantile(entry.samples, q);
484
+ const qLabel = labelStr
485
+ ? labelStr.slice(0, -1) + `,quantile="${q}"}`
486
+ : `{quantile="${q}"}`;
487
+ lines.push(`${this.name}${qLabel} ${qValue}`);
488
+ }
489
+
490
+ // Sum and count
491
+ lines.push(`${this.name}_sum${labelStr} ${entry.sum}`);
492
+ lines.push(`${this.name}_count${labelStr} ${entry.count}`);
493
+ }
494
+
495
+ return lines.join('\n');
496
+ }
497
+
498
+ toOTLP() {
499
+ const dataPoints = [];
500
+
501
+ for (const entry of this.values.values()) {
502
+ const quantileValues = this.quantiles.map(q => ({
503
+ quantile: q,
504
+ value: this.calculateQuantile(entry.samples, q)
505
+ }));
506
+
507
+ dataPoints.push({
508
+ attributes: Object.entries(entry.labels).map(([k, v]) => ({ key: k, value: { stringValue: String(v) } })),
509
+ count: entry.count,
510
+ sum: entry.sum,
511
+ quantileValues,
512
+ timeUnixNano: Date.now() * 1e6
513
+ });
514
+ }
515
+
516
+ return {
517
+ name: this.name,
518
+ description: this.help,
519
+ summary: { dataPoints }
520
+ };
521
+ }
522
+ }
523
+
524
+ // ============================================================================
525
+ // METRIC REGISTRY
526
+ // ============================================================================
527
+
528
+ /**
529
+ * MetricRegistry - manages all metrics
530
+ */
531
+ class MetricRegistry extends SimpleEventEmitter {
532
+ constructor(options = {}) {
533
+ super();
534
+ this.prefix = options.prefix || '';
535
+ this.metrics = new Map();
536
+ this.defaultLabels = options.defaultLabels || {};
537
+ }
538
+
539
+ /**
540
+ * Get prefixed metric name
541
+ * @param {string} name - Metric name
542
+ * @returns {string}
543
+ */
544
+ getFullName(name) {
545
+ return this.prefix + name;
546
+ }
547
+
548
+ /**
549
+ * Create and register a counter
550
+ * @param {string} name - Metric name
551
+ * @param {Object} options - Metric options
552
+ * @returns {Counter}
553
+ */
554
+ counter(name, options = {}) {
555
+ const fullName = this.getFullName(name);
556
+ if (this.metrics.has(fullName)) {
557
+ return this.metrics.get(fullName);
558
+ }
559
+
560
+ const metric = new Counter(fullName, options);
561
+ this.metrics.set(fullName, metric);
562
+ return metric;
563
+ }
564
+
565
+ /**
566
+ * Create and register a gauge
567
+ * @param {string} name - Metric name
568
+ * @param {Object} options - Metric options
569
+ * @returns {Gauge}
570
+ */
571
+ gauge(name, options = {}) {
572
+ const fullName = this.getFullName(name);
573
+ if (this.metrics.has(fullName)) {
574
+ return this.metrics.get(fullName);
575
+ }
576
+
577
+ const metric = new Gauge(fullName, options);
578
+ this.metrics.set(fullName, metric);
579
+ return metric;
580
+ }
581
+
582
+ /**
583
+ * Create and register a histogram
584
+ * @param {string} name - Metric name
585
+ * @param {Object} options - Metric options
586
+ * @returns {Histogram}
587
+ */
588
+ histogram(name, options = {}) {
589
+ const fullName = this.getFullName(name);
590
+ if (this.metrics.has(fullName)) {
591
+ return this.metrics.get(fullName);
592
+ }
593
+
594
+ const metric = new Histogram(fullName, options);
595
+ this.metrics.set(fullName, metric);
596
+ return metric;
597
+ }
598
+
599
+ /**
600
+ * Create and register a summary
601
+ * @param {string} name - Metric name
602
+ * @param {Object} options - Metric options
603
+ * @returns {Summary}
604
+ */
605
+ summary(name, options = {}) {
606
+ const fullName = this.getFullName(name);
607
+ if (this.metrics.has(fullName)) {
608
+ return this.metrics.get(fullName);
609
+ }
610
+
611
+ const metric = new Summary(fullName, options);
612
+ this.metrics.set(fullName, metric);
613
+ return metric;
614
+ }
615
+
616
+ /**
617
+ * Get a metric by name
618
+ * @param {string} name - Metric name
619
+ * @returns {Metric}
620
+ */
621
+ get(name) {
622
+ return this.metrics.get(this.getFullName(name));
623
+ }
624
+
625
+ /**
626
+ * Remove a metric
627
+ * @param {string} name - Metric name
628
+ */
629
+ remove(name) {
630
+ this.metrics.delete(this.getFullName(name));
631
+ }
632
+
633
+ /**
634
+ * Clear all metrics
635
+ */
636
+ clear() {
637
+ this.metrics.clear();
638
+ }
639
+
640
+ /**
641
+ * Export all metrics to Prometheus format
642
+ * @returns {string}
643
+ */
644
+ toPrometheus() {
645
+ const lines = [];
646
+
647
+ for (const metric of this.metrics.values()) {
648
+ lines.push(metric.toPrometheus());
649
+ lines.push('');
650
+ }
651
+
652
+ return lines.join('\n');
653
+ }
654
+
655
+ /**
656
+ * Export all metrics to OpenTelemetry format
657
+ * @returns {Object}
658
+ */
659
+ toOTLP() {
660
+ const metrics = [];
661
+
662
+ for (const metric of this.metrics.values()) {
663
+ metrics.push(metric.toOTLP());
664
+ }
665
+
666
+ return {
667
+ resourceMetrics: [{
668
+ resource: {
669
+ attributes: Object.entries(this.defaultLabels).map(([k, v]) => ({
670
+ key: k,
671
+ value: { stringValue: String(v) }
672
+ }))
673
+ },
674
+ scopeMetrics: [{
675
+ scope: { name: 'tinyaleph' },
676
+ metrics
677
+ }]
678
+ }]
679
+ };
680
+ }
681
+
682
+ /**
683
+ * Get all metric names
684
+ * @returns {Array<string>}
685
+ */
686
+ getMetricNames() {
687
+ return Array.from(this.metrics.keys());
688
+ }
689
+ }
690
+
691
+ // ============================================================================
692
+ // EXPORTS
693
+ // ============================================================================
694
+
695
+ module.exports = {
696
+ // Metric types
697
+ MetricType,
698
+
699
+ // Metric classes
700
+ Metric,
701
+ Counter,
702
+ Gauge,
703
+ Histogram,
704
+ Summary,
705
+
706
+ // Registry
707
+ MetricRegistry
708
+ };