@oas-tools/oas-telemetry 0.5.1 → 0.5.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/dist/ui.cjs DELETED
@@ -1,1200 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = ui;
7
- function ui() {
8
- return {
9
- main: `<!DOCTYPE html>
10
- <html lang="en">
11
-
12
- <head>
13
- <meta charset="UTF-8">
14
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
15
- <title>OAS - Telemetry</title>
16
- <style>
17
- body {
18
- font-family: Arial, sans-serif;
19
- margin: 0;
20
- padding: 0;
21
- background-color: #f5f5f5;
22
- }
23
-
24
- .header {
25
- background-color: #333;
26
- color: #fff;
27
- padding: 20px;
28
- text-align: left;
29
- display: flex;
30
- justify-content: space-between;
31
- }
32
-
33
- .header h1 {
34
- display: inline-block;
35
- margin: 0;
36
- font-size: 24px;
37
- }
38
-
39
- .header .links {
40
- display: flex;
41
- align-items: center;
42
- }
43
- .links a {
44
- color: #fff;
45
- text-decoration: none;
46
- margin-left: 30px;
47
- }
48
-
49
- .page {
50
- margin: 0;
51
- padding: 0;
52
- }
53
-
54
- .panel-conainer {
55
- min-width: 60%;
56
- width: fit-content;
57
- margin: 20px auto;
58
- }
59
- .panel {
60
- background-color: #fff;
61
- border: 1px solid #ddd;
62
- margin: 10px;
63
- border-radius: 4px;
64
- }
65
-
66
- .panel-header {
67
- background-color: #f1f1f1;
68
- padding: 10px;
69
- font-weight: bold;
70
- cursor: pointer;
71
- }
72
-
73
- .panel-content {
74
- display: none;
75
- padding: 15px;
76
-
77
- /* items margin */
78
- >* {
79
- margin: 5px 0;
80
- }
81
-
82
- }
83
-
84
- .panel.open .panel-content {
85
- display: flex;
86
- flex-direction: column;
87
- padding: 15px;
88
- justify-content: center;
89
- align-items: center;
90
-
91
- }
92
-
93
-
94
- button {
95
- background-color: #007bff;
96
- color: white;
97
- border: none;
98
- padding: 10px 15px;
99
- cursor: pointer;
100
- border-radius: 4px;
101
- }
102
-
103
- button:hover {
104
- background-color: #0056b3;
105
- }
106
-
107
- table {
108
- width: fit-content;
109
- border-collapse: collapse;
110
- margin: 100%;
111
- }
112
-
113
- th,
114
- td {
115
- border: 1px solid #dddddd;
116
- padding: 8px;
117
- text-align: left;
118
- }
119
-
120
- th {
121
- background-color: #f2f2f2;
122
- }
123
-
124
-
125
-
126
- .spaced-row {
127
- display: flex;
128
- align-items: center;
129
- justify-content: space-between;
130
- width: 100%;
131
- }
132
-
133
- .row {
134
- display: flex;
135
- align-items: center;
136
- }
137
- </style>
138
- <style>
139
- .toggle-container {
140
- display: flex;
141
- flex: none;
142
- width: max-content;
143
- align-items: center;
144
- margin: 10px 0;
145
- }
146
-
147
- .toggle {
148
- display: flex;
149
- align-items: center;
150
- min-width: 40px;
151
- min-height: 20px;
152
- border-radius: 12px;
153
- background-color: gray;
154
- position: relative;
155
- cursor: pointer;
156
- transition: background-color 0.3s;
157
- margin: 0 10px;
158
- }
159
-
160
- .toggle.circle {
161
- border-radius: 50%;
162
- }
163
-
164
- .toggle.active {
165
- background-color: green;
166
- }
167
-
168
- .circle-indicator {
169
- width: 16px;
170
- height: 16px;
171
- margin: 0px 2px;
172
- background-color: white;
173
- border-radius: 50%;
174
- transition: transform 0.3s;
175
- }
176
-
177
- .toggle.active .circle-indicator {
178
- transform: translateX(20px);
179
- /* Move the circle to the right when active */
180
- }
181
-
182
- .option-text {
183
- transition: color 0.3s;
184
- margin: 0 5px;
185
- }
186
- </style>
187
- </head>
188
-
189
- <body>
190
-
191
- <div class="header">
192
- <h1>OAS-Telemetry</h1>
193
- <div class="links">
194
- <a target="_blank" href="https://github.com/oas-tools/oas-telemetry">Documentation</a>
195
- <a target="_blank" href="https://www.npmjs.com/package/@oas-tools/oas-telemetry">NPM</a>
196
- <a target="_blank" href="https://github.com/oas-tools/oas-telemetry">GitHub</a>
197
- </div>
198
- </div>
199
- <div class="page">
200
- <div class="panel-conainer">
201
- <!-- Panel 1 -->
202
- <div class="panel open" id="panel1">
203
- <div class="panel-header" onclick="togglePanel('panel1')">Telemetry Management</div>
204
- <div class="panel-content">
205
- <div class="spaced-row">
206
- <div id="toggleTelemetry"></div>
207
- <button onclick="fetch('/telemetry/reset');fetchTelemetryStatus();">Reset Telemetry
208
- Data</button>
209
- </div>
210
- </div>
211
- </div>
212
-
213
- <!-- Panel 2 -->
214
- <div class="panel open" id="panel2">
215
- <div class="panel-header" onclick="togglePanel('panel2')">Heap Stats</div>
216
- <div class="panel-content">
217
- <div class="spaced-row">
218
- <div id="heapAutoUpdate"></div>
219
- <button onclick="populateHeapStats()">Update</button>
220
- </div>
221
- <table id="heapStatsTable">
222
- <thead>
223
- <tr>
224
- <th>Stat Name</th>
225
- <th>Value</th>
226
- </tr>
227
- </thead>
228
- <tbody>
229
- </tbody>
230
- </table>
231
-
232
- </div>
233
- </div>
234
-
235
- <!-- Panel 3 -->
236
- <div class="panel no-user-select open" id="panel3">
237
- <div class="panel-header" onclick="togglePanel('panel3')">Telemetry Endpoints</div>
238
- <div class="panel-content">
239
- <div class="spaced-row">
240
- <div id="autoUpdateApiTable"></div>
241
- </div>
242
-
243
- <table id="apiTable">
244
- <thead>
245
- <tr>
246
- <th onclick="sortTable(0)">Path</th>
247
- <th onclick="sortTable(1)">Method</th>
248
- <th onclick="sortTable(2)">Status</th>
249
- <th onclick="sortTable(3)">Description</th>
250
- <th onclick="sortTable(4)" style="text-align: center;">Request Count</th>
251
- <th onclick="sortTable(5)" style="text-align: center;">Average response time (sec)
252
- </th>
253
- <th style="text-align: center;">Options</th>
254
- </tr>
255
- </thead>
256
- <tbody>
257
- </tbody>
258
- </table>
259
- </table>
260
- </div>
261
- </div>
262
- </div>
263
- </div>
264
- <!-- Scripts -->
265
- <script>
266
- // Open Close Panel
267
- function togglePanel(panelId) {
268
- const panel = document.getElementById(panelId);
269
- panel.classList.toggle('open');
270
- }
271
- /**
272
- * Create a toggle component
273
- * @param {string} title - Title of the toggle
274
- * @param {string} falseText - Text to display when false
275
- * @param {string} falseColor - Color of the text when false
276
- * @param {string} trueText - Text to display when true
277
- * @param {string} trueColor - Color of the text when true
278
- * @param {function} handler - Function to call when the toggle is clicked
279
- * @param {boolean} defaultValue - Default value of the toggle
280
- * @returns {HTMLDivElement} - The toggle component
281
- */
282
- function createToggle(title, falseText, falseColor, trueText, trueColor, handler, defaultValue = false) {
283
- const container = document.createElement('div');
284
- container.className = 'toggle-container';
285
-
286
- const label = document.createElement('span');
287
- label.textContent = title + ':';
288
- label.style.marginRight = '10px';
289
-
290
- const falseTextSpan = document.createElement('span');
291
- falseTextSpan.className = 'option-text';
292
- falseTextSpan.textContent = falseText;
293
- falseTextSpan.style.color = defaultValue ? 'gray' : falseColor;
294
-
295
- const toggle = document.createElement('div');
296
- toggle.className = 'toggle';
297
- const circleIndicator = document.createElement('div');
298
- circleIndicator.className = 'circle-indicator';
299
- toggle.appendChild(circleIndicator);
300
-
301
- toggle.addEventListener('click', () => {
302
- toggle.classList.toggle('active');
303
- const isActive = toggle.classList.contains('active');
304
-
305
- // Update colors. Not selected option to default color, selected option to active color
306
- falseTextSpan.style.color = isActive ? 'gray' : falseColor;
307
- trueTextSpan.style.color = isActive ? trueColor : 'gray';
308
-
309
- handler(isActive);
310
- });
311
- toggle.classList.toggle('active', defaultValue);
312
-
313
- const trueTextSpan = document.createElement('span');
314
- trueTextSpan.className = 'option-text';
315
- trueTextSpan.textContent = trueText; // Always display trueText
316
- trueTextSpan.style.color = defaultValue ? trueColor : 'gray';
317
-
318
- container.appendChild(label);
319
- container.appendChild(falseTextSpan);
320
- container.appendChild(toggle);
321
- container.appendChild(trueTextSpan);
322
-
323
- return container;
324
- }
325
-
326
- const localStorageManager = {
327
- get: (key) => {
328
- const value = localStorage.getItem(key);
329
- return value ? JSON.parse(value) : null;
330
- },
331
- set: (key, value) => {
332
- localStorage.setItem(key, JSON.stringify(value));
333
- }
334
- };
335
-
336
- </script>
337
- <script>
338
- let LOG = true;
339
-
340
-
341
- let intervalTimer = {
342
- disabled: true,
343
- period: 2000,
344
- subscribers: [],
345
- start: function () {
346
- this.disabled = false;
347
- this.interval = setInterval(() => this.tick(), this.period);
348
- log("interval started with period: " + this.period);
349
- },
350
- stop: function () {
351
- this.disabled = true;
352
- clearInterval(this.interval);
353
- log("interval stopped");
354
- },
355
- tick: function () {
356
- if (this.disabled) return;
357
- log("tick");
358
- this.subscribers.forEach(callback => callback());//execute all the callbacks
359
- },
360
- subscribe: function (callback) {
361
- this.subscribers.push(callback);
362
- },
363
- unsubscribe: function (callback) {
364
- this.subscribers.pop(callback)
365
- }
366
- }
367
-
368
- function log(s) {
369
- if (LOG) console.log(s);
370
- }
371
-
372
- async function fetchSpec() {
373
- try {
374
- const response = await fetch("/telemetry/spec");
375
- if (!response.ok) {
376
- throw new Error("ERROR getting the Spec");
377
- }
378
- apiSpec = await response.json();
379
- return apiSpec;
380
-
381
- } catch (error) {
382
- console.error("ERROR getting the Spec :", error);
383
- return null;
384
- }
385
- }
386
-
387
- async function fetchTelemetryStatus() {
388
- const response = await fetch("/telemetry/status");
389
- if (!response.ok) {
390
- throw new Error("ERROR getting the Status");
391
- return false;
392
- }
393
- tStatus = await response.json();
394
-
395
- log("tStatus: " + JSON.stringify(tStatus, null, 2));
396
- return tStatus.active;
397
- }
398
-
399
- function getPathRegEx(path) {
400
- let pathComponents = path.split("/");
401
- let pathRegExpStr = "^"
402
-
403
- pathComponents.forEach(c => {
404
- if (c != "") {
405
- pathRegExpStr += "/";
406
- if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
407
- // Ensure it matches at least one character (.+)
408
- pathRegExpStr += "(.+)";
409
- } else {
410
- pathRegExpStr += c;
411
- }
412
- }
413
- });
414
-
415
- // Allow an optional trailing slash
416
- pathRegExpStr += "/?\$";
417
-
418
- return pathRegExpStr;
419
- }
420
-
421
- async function fetchTracesByFind(path, method, status) {
422
- try {
423
- log(\`Fetching traces for <\${path}> - \${method} - \${status} \`);
424
- const body = {
425
- "flags": { "containsRegex": true },
426
- "config": { "regexIds": ["attributes.http.target"] },
427
- "search": {
428
- "attributes.http.target": getPathRegEx(path),
429
- "attributes.http.method": method.toUpperCase(),
430
- "attributes.http.status_code": parseInt(status)
431
- }
432
- };
433
- log("body: " + JSON.stringify(body, null, 2));
434
- //response is to the post at /telemetry/find
435
- const response = await fetch("/telemetry/find", {
436
- method: "POST",
437
- headers: {
438
- "Content-Type": "application/json"
439
- },
440
- body: JSON.stringify(body)
441
- });
442
-
443
- if (!response.ok) {
444
- throw new Error("ERROR getting the Traces.");
445
- }
446
-
447
- const responseJSON = await response.json();
448
- const traces = responseJSON.spans;
449
-
450
- log(\`Fetched \${traces.length} traces.\`);
451
- return traces;
452
-
453
- } catch (error) {
454
- console.error("ERROR getting the Traces :", error);
455
- }
456
- }
457
-
458
- function calculateTiming(startSecInput, startNanoSecInput, endSecInput, endNanoSecInput, precision = 3) {
459
- let startSec = parseFloat(startSecInput);
460
- let startNanoSec = parseFloat(startNanoSecInput);
461
- let endSec = parseFloat(endSecInput);
462
- let endNanoSec = parseFloat(endNanoSecInput);
463
-
464
- let startNanoSecParsed = parseFloat("0." + startNanoSec);
465
- let endNanoSecParsed = parseFloat("0." + endNanoSec);
466
-
467
- let preciseStart = parseFloat(startSec + startNanoSecParsed);
468
- let preciseEnd = parseFloat(endSec + endNanoSecParsed);
469
- let preciseDuration = parseFloat(preciseEnd - preciseStart);
470
-
471
- let startDate = new Date(preciseStart.toFixed(precision) * 1000);
472
- let startTS = startDate.toISOString();
473
-
474
- let endDate = new Date(preciseEnd.toFixed(precision) * 1000);
475
- let endTS = endDate.toISOString();
476
-
477
- return {
478
- preciseStart: preciseStart,
479
- preciseEnd: preciseEnd,
480
- preciseDuration: preciseDuration,
481
- start: parseFloat(preciseStart.toFixed(precision)),
482
- end: parseFloat(preciseEnd.toFixed(precision)),
483
- duration: parseFloat(preciseDuration.toFixed(precision)),
484
- startDate: startDate,
485
- endDate: endDate,
486
- startTS: startTS,
487
- endTS: endTS
488
- };
489
- }
490
-
491
- function parseTraceInfo(t) {
492
- const ep = t.attributes.http.target;
493
- const method = t.attributes.http.method.toLowerCase();
494
- const status = t.attributes.http.status_code;
495
-
496
- const timing = calculateTiming(t.startTime[0], t.startTime[1], t.endTime[0], t.endTime[1]);
497
-
498
- log(\`\${timing.startTS} - \${timing.endTS} - \${t._spanContext.traceId} - \${t.name} - \${ep} - \${status} - \${timing.duration}\`);
499
- return {
500
- ts: timing.startTS,
501
- ep: ep,
502
- method: method,
503
- status: status,
504
- duration: timing.duration
505
- };
506
- }
507
-
508
- async function loadStats(path, method, status, cellRequestCount, cellAverageResponseTime) {
509
- log(\`loadStats(\${path}, \${method}, \${status}, \${cellRequestCount}, \${cellAverageResponseTime})\`);
510
- let traces = await fetchTracesByFind(path, method, status);
511
- let requestCount = traces.length;
512
- let averageResponseTime = 0;
513
-
514
- traces.forEach(trace => {
515
- t = parseTraceInfo(trace);
516
- log(JSON.stringify(t, null, 2));
517
- averageResponseTime += parseFloat(t.duration);
518
- log(\`averageResponseTime += t.duration --> \${averageResponseTime} += \${t.duration}\`);
519
- });
520
-
521
- averageResponseTime = averageResponseTime / requestCount;
522
-
523
- log(\`averageResponseTime = averageResponseTime / requestCount --> \${averageResponseTime} = \${averageResponseTime} / \${requestCount}\`);
524
-
525
- cellRequestCount.textContent = requestCount;
526
- cellAverageResponseTime.textContent = requestCount ? averageResponseTime.toFixed(3) : "--";
527
-
528
- }
529
-
530
- async function populateApiTable() {
531
- const apiSpec = await fetchSpec()
532
- const tableBody = document.getElementById('apiTable').getElementsByTagName('tbody')[0];
533
- tableBody.innerHTML = "";
534
- Object.keys(apiSpec.paths).forEach(path => {
535
- Object.keys(apiSpec.paths[path]).forEach(method => {
536
- Object.keys(apiSpec.paths[path][method].responses).forEach(responseType => {
537
- if (!Number.isNaN(parseInt(responseType))) {
538
- const row = tableBody.insertRow();
539
- const cellPath = row.insertCell(0);
540
- const cellMethod = row.insertCell(1);
541
- const cellStatus = row.insertCell(2);
542
- const cellDescription = row.insertCell(3);
543
- const cellRequestCount = row.insertCell(4);
544
- cellRequestCount.style = "text-align: center;";
545
- cellRequestCount.textContent = "--";
546
- const cellAverageResponseTime = row.insertCell(5);
547
- cellAverageResponseTime.style.textAlign = "center";
548
- cellAverageResponseTime.textContent = "--";
549
-
550
- const basePath = apiSpec.basePath ? apiSpec.basePath : "";
551
- const fullPath = basePath + path;
552
- cellPath.textContent = path;
553
- cellPath.style.cursor = 'pointer';
554
- cellPath.onclick = function () {
555
- window.location.href = row.detailPath;
556
- };
557
- cellMethod.textContent = method.toUpperCase();
558
- cellStatus.textContent = responseType;
559
- cellDescription.textContent = apiSpec.paths[path][method].summary
560
- + " - "
561
- + apiSpec.paths[path][method].responses[responseType].description;
562
-
563
- row.detailPath = \`/telemetry/detail/\${responseType}/\${method.toLowerCase()}\${fullPath}\`;
564
- const cellOptions = row.insertCell(6);
565
-
566
-
567
- // Create a button for updating the endpoint spaced row but centered
568
- const updateButton = document.createElement('button');
569
- updateButton.textContent = "Update";
570
- updateButton.onclick = () => loadStats(path, method, responseType, cellRequestCount, cellAverageResponseTime);
571
- cellOptions.appendChild(updateButton);
572
- cellOptions.style.display = "flex";
573
- cellOptions.style.justifyContent = "center";
574
- loadStats(path, method, responseType, cellRequestCount, cellAverageResponseTime);
575
-
576
- }
577
- });
578
- });
579
- });
580
- }
581
-
582
- function populateHeapStats() {
583
- // heapstats at /telemetry/heapstats
584
- const tableBody = document.getElementById('heapStatsTable').getElementsByTagName('tbody')[0];
585
- fetch('/telemetry/heapstats').then(response => response.json()).then(heapStats => {
586
- tableBody.innerHTML = "";
587
- Object.keys(heapStats).forEach(statName => {
588
- const row = tableBody.insertRow();
589
- const cellStatName = row.insertCell(0);
590
- const cellValue = row.insertCell(1);
591
- cellStatName.textContent = statName;
592
- //format always to 3 decimals (if number)
593
- const formattedValue = typeof heapStats[statName] === 'number' ? heapStats[statName].toFixed(3) : heapStats[statName];
594
- cellValue.textContent = heapStats[statName];
595
- cellValue.style.textAlign = "right";
596
- });
597
- });
598
- }
599
-
600
- function sortTable(column) {
601
- const table = document.getElementById('apiTable');
602
- let rows, switching, i, x, y, shouldSwitch;
603
- switching = true;
604
- while (switching) {
605
- switching = false;
606
- rows = table.rows;
607
- for (i = 1; i < (rows.length - 1); i++) {
608
- shouldSwitch = false;
609
- x = rows[i].getElementsByTagName("TD")[column];
610
- y = rows[i + 1].getElementsByTagName("TD")[column];
611
- if (x.textContent.toLowerCase() > y.textContent.toLowerCase()) {
612
- shouldSwitch = true;
613
- break;
614
- }
615
- }
616
- if (shouldSwitch) {
617
- rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
618
- switching = true;
619
- }
620
- }
621
- }
622
-
623
- document.getElementById('toggleTelemetry').appendChild(createToggle(
624
- 'Telemetry status',
625
- 'Stopped', 'red', // false state
626
- 'Active', 'green', // true state
627
- async (status) => {
628
- const response = await fetch('/telemetry/' + (status ? 'start' : 'stop'));
629
- if (!response.ok) {
630
- throw new Error("ERROR setting the Telemetry status");
631
- }
632
- }
633
- ));
634
-
635
- document.getElementById('autoUpdateApiTable').appendChild(createToggle(
636
- 'Auto Update',
637
- 'Manual', 'orange', // false state
638
- 'Auto', 'green', // true state
639
- (status) => {
640
- const callback = () => { populateApiTable(); };
641
- if (status) {
642
- intervalTimer.subscribe(callback);
643
- } else {
644
- intervalTimer.unsubscribe(callback);
645
- }
646
- }
647
- ));
648
-
649
- document.getElementById('heapAutoUpdate').appendChild(createToggle(
650
- 'Auto Update',
651
- 'Manual', 'orange', // false state
652
- 'Auto', 'green', // true state
653
- (status) => {
654
- if (status) {
655
- intervalTimer.subscribe(populateHeapStats);
656
- } else {
657
- intervalTimer.unsubscribe(populateHeapStats);
658
- }
659
- }
660
- ));
661
-
662
- window.onload = async function () {
663
- document.getElementById('autoUpdateApiTable').querySelector('.toggle').classList.toggle('active', localStorageManager.get('autoUpdateApiTable'));
664
- const activeTelemetry = await fetchTelemetryStatus();
665
- document.getElementById('toggleTelemetry').querySelector('.toggle').classList.toggle('active', activeTelemetry);
666
- populateApiTable();
667
- populateHeapStats();
668
- intervalTimer.start();
669
- };
670
- </script>
671
- </body>
672
-
673
- </html>`,
674
- detail: `<!DOCTYPE html>
675
- <html lang="en">
676
-
677
- <head>
678
- <meta charset="UTF-8">
679
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
680
- <title>OAS - Telemetry</title>
681
- <style>
682
- body {
683
- font-family: Arial, sans-serif;
684
- margin: 20px;
685
- }
686
-
687
- table {
688
- width: 100%;
689
- border-collapse: collapse;
690
- }
691
-
692
- th,
693
- td {
694
- border: 1px solid #dddddd;
695
- padding: 8px;
696
- text-align: left;
697
- cursor: pointer;
698
- }
699
-
700
- th {
701
- background-color: #f2f2f2;
702
- }
703
-
704
- .box {
705
- width: 100%;
706
- margin: 0 auto;
707
- background: rgba(255, 255, 255, 0.2);
708
- padding: 35px;
709
- border: 2px solid #fff;
710
- border-radius: 20px/50px;
711
- background-clip: padding-box;
712
- text-align: center;
713
- }
714
-
715
- .overlay {
716
- position: fixed;
717
- top: 0;
718
- bottom: 0;
719
- left: 0;
720
- right: 0;
721
- background: rgba(0, 0, 0, 0.7);
722
- transition: opacity 500ms;
723
- visibility: hidden;
724
- opacity: 0;
725
- overflow: scroll;
726
- }
727
-
728
- .overlay:target {
729
- visibility: visible;
730
- opacity: 1;
731
- }
732
-
733
- .popup {
734
- margin: 70px auto;
735
- padding: 20px;
736
- background: #fff;
737
- border-radius: 5px;
738
- width: 70%;
739
- position: relative;
740
- transition: all 5s ease-in-out;
741
- font-size: small;
742
- overflow: scroll;
743
- }
744
-
745
- .popup .close {
746
- position: absolute;
747
- top: 20px;
748
- right: 30px;
749
- transition: all 200ms;
750
- font-size: 30px;
751
- font-weight: bold;
752
- text-decoration: none;
753
- color: #333;
754
- }
755
- </style>
756
- </head>
757
-
758
- <body>
759
- <h1><span id="heading">Telemetry for...</span></h1>
760
- <a href="/telemetry/">Back</a><br><br>
761
- <table id="apiTable">
762
- <thead>
763
- <tr>
764
- <th onclick="sortTable(0)">TimeStamp</th>
765
- <th onclick="sortTable(1)">End point</th>
766
- <th onclick="sortTable(2)">Method</th>
767
- <th onclick="sortTable(3)">Status</th>
768
- <th onclick="sortTable(4)" style="text-align: center;">Response time<br> (sec)</th>
769
- </tr>
770
- </thead>
771
- <tbody>
772
- </tbody>
773
- </table>
774
- <script>
775
-
776
- let traceCount = -1;
777
- let LOG = true;
778
-
779
- function log(s) {
780
- if (LOG)
781
- console.log(s);
782
- }
783
-
784
- function parsePath() {
785
-
786
- let detailPath = window.location.pathname.split("/");
787
-
788
- if (detailPath.length < 6 || detailPath[5] == "") {
789
- alert("Wrong invocation params");
790
- return;
791
- }
792
-
793
- let status = parseInt(detailPath[3]);
794
-
795
- if (Number.isNaN(status))
796
- status = -1;
797
-
798
- const method = detailPath[4];
799
-
800
-
801
- const path = decodeURI("/" + detailPath
802
- .splice(5, detailPath.length - 5)
803
- .filter(c => (c != ""))
804
- .join("/"));
805
-
806
- headingObj = document.getElementById('heading');
807
- headingObj.textContent = \`Telemetry for \${path} - \${method} - \${status} \`;
808
- fetchTracesByParsing(path, method, status);
809
- }
810
-
811
- function getSearchQuery(path, method, status) {
812
- let pathComponents = path.split("/");
813
- let pathRegex = "^"
814
-
815
- pathComponents.forEach(c => {
816
- if (c != "") {
817
- pathRegex += "/";
818
- if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
819
- pathRegex += "(.*)";
820
- } else {
821
- pathRegex += c;
822
- }
823
- }
824
- });
825
-
826
- pathRegex += "\$";
827
-
828
- return {
829
- "attributes.http.target": { \$regex: new RegExp(pathRegex) },
830
- "name": method.toUpperCase(),
831
- "attributes.http.status_code": status
832
- };
833
- }
834
-
835
- async function fetchTracesByFinding(path, method, status) {
836
- try {
837
- log(\`Fetching traces for <\${path}> - \${method} - \${status} \`);
838
- const body = {
839
- "flags": { "containsRegex": true },
840
- "config": { "regexIds": ["attributes.http.target"] },
841
- "search": {
842
- "attributes.http.target": getPathRegEx(path),
843
- "attributes.http.method": method.toUpperCase(),
844
- "attributes.http.status_code": parseInt(status)
845
- }
846
- };
847
- log("body: " + JSON.stringify(body, null, 2));
848
- //response is to the post at /telemetry/find
849
- const response = await fetch("/telemetry/find", {
850
- method: "POST",
851
- headers: {
852
- "Content-Type": "application/json"
853
- },
854
- body: JSON.stringify(body)
855
- });
856
-
857
- if (!response.ok) {
858
- throw new Error("ERROR getting the Traces.");
859
- }
860
-
861
- const traces = await response.json();
862
- loadTraces(traces);
863
-
864
- } catch (error) {
865
- console.error("ERROR getting the Traces :", error);
866
- }
867
- }
868
-
869
- function getPathRegEx(path) {
870
- let pathComponents = path.split("/");
871
- let pathRegExpStr = "^"
872
-
873
- pathComponents.forEach(c => {
874
- if (c != "") {
875
- pathRegExpStr += "/";
876
- if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
877
- // Ensure it matches at least one character (.+)
878
- pathRegExpStr += "(.+)";
879
- } else {
880
- pathRegExpStr += c;
881
- }
882
- }
883
- });
884
-
885
- // Allow an optional trailing slash
886
- pathRegExpStr += "/?\$";
887
-
888
- return pathRegExpStr;
889
- }
890
-
891
- async function fetchTracesByParsing(path, method, status) {
892
- try {
893
- log(\`Fetchig traces for <\${path}> - \${method} - \${status},.. \`);
894
-
895
- const body = {
896
- "flags": { "containsRegex": true },
897
- "config": { "regexIds": ["attributes.http.target"] },
898
- "search": {
899
- "attributes.http.target": getPathRegEx(path),
900
- "attributes.http.method": method.toUpperCase(),
901
- "attributes.http.status_code": parseInt(status)
902
- }
903
- };
904
- log("body: " + JSON.stringify(body, null, 2));
905
- //response is to the post at /telemetry/find
906
- const response = await fetch("/telemetry/find", {
907
- method: "POST",
908
- headers: {
909
- "Content-Type": "application/json"
910
- },
911
- body: JSON.stringify(body)
912
- });
913
-
914
- if (!response.ok) {
915
- throw new Error("ERROR getting the Traces.");
916
- }
917
-
918
- const responseJSON = await response.json();
919
- const traces = responseJSON.spans;
920
-
921
- log(\`Fetched \${traces.length} traces.\`);
922
-
923
- if (traces.length != traceCount) {
924
- loadTraces(traces);
925
- traceCount = traces.length;
926
- }
927
-
928
- setTimeout(fetchTracesByParsing, 1000, path, method, status);
929
-
930
- } catch (error) {
931
- console.error("ERROR getting the Traces :", error);
932
- }
933
- }
934
-
935
- function calculateTiming(startSecInput, startNanoSecInput, endSecInput, endNanoSecInput, precision = 3) {
936
- // Default precision 3 = miliseconds
937
-
938
- let startSec = parseFloat(startSecInput)
939
- let startNanoSec = parseFloat(startNanoSecInput)
940
- let endSec = parseFloat(endSecInput)
941
- let endNanoSec = parseFloat(endNanoSecInput)
942
-
943
- let startNanoSecParsed = parseFloat("0." + startNanoSec);
944
- let endNanoSecParsed = parseFloat("0." + endNanoSec);
945
-
946
- let preciseStart = parseFloat(startSec + startNanoSecParsed);
947
- let preciseEnd = parseFloat(endSec + endNanoSecParsed);
948
- let preciseDuration = parseFloat(preciseEnd - preciseStart);
949
-
950
- let startDate = new Date(preciseStart.toFixed(precision) * 1000);
951
- let startTS = startDate.toISOString();
952
-
953
- let endDate = new Date(preciseEnd.toFixed(precision) * 1000);
954
- let endTS = endDate.toISOString();
955
-
956
- return {
957
- preciseStart: preciseStart,
958
- preciseEnd: preciseEnd,
959
- preciseDuration: preciseDuration,
960
- start: parseFloat(preciseStart.toFixed(precision)),
961
- end: parseFloat(preciseEnd.toFixed(precision)),
962
- duration: parseFloat(preciseDuration.toFixed(precision)),
963
- startDate: startDate,
964
- endDate: endDate,
965
- startTS: startTS,
966
- endTS: endTS
967
- };
968
-
969
- }
970
-
971
- function parseTraceInfo(t) {
972
- const ep = t.attributes.http.target;
973
- const method = t.attributes.http.method.toLowerCase();
974
- const status = t.attributes.http.status_code;
975
-
976
- const timing = calculateTiming(t.startTime[0], t.startTime[1], t.endTime[0], t.endTime[1]);
977
-
978
- log(\`\${timing.startTS} - \${timing.endTS} - \${t._spanContext.traceId} - \${t.name} - \${ep} - \${status} - \${timing.duration}\`);
979
- return {
980
- ts: timing.startTS,
981
- ep: ep,
982
- method: method,
983
- status: status,
984
- duration: timing.duration
985
- };
986
- }
987
-
988
- function loadTraces(traces) {
989
-
990
- const tableBody = document.getElementById('apiTable').getElementsByTagName('tbody')[0];
991
- while (tableBody.hasChildNodes()) {
992
- tableBody.removeChild(tableBody.lastChild);
993
- }
994
-
995
- traces.forEach(trace => {
996
- const row = tableBody.insertRow();
997
- const cellTS = row.insertCell(0);
998
- const cellEP = row.insertCell(1);
999
- const cellMethod = row.insertCell(2);
1000
- cellMethod.style.textAlign = "center";
1001
- const cellStatus = row.insertCell(3);
1002
- cellStatus.style.textAlign = "center";
1003
- const cellDuration = row.insertCell(4);
1004
- cellDuration.style.textAlign = "center";
1005
-
1006
- let t = parseTraceInfo(trace);
1007
-
1008
- cellTS.textContent = t.ts;
1009
- cellEP.textContent = t.ep;
1010
- cellMethod.textContent = t.method;
1011
- cellStatus.textContent = t.status;
1012
- cellDuration.textContent = t.duration.toFixed(3);
1013
-
1014
- row.trace = trace;
1015
- row.onclick = function () {
1016
- const popup = document.getElementById("tracePopup");
1017
- popup.firstChild.nodeValue = JSON.stringify(this.trace, null, 2);
1018
- const popupOverlay = document.getElementById("popupOverlay");
1019
- popupOverlay.style.visibility = "visible";
1020
- popupOverlay.style.opacity = 1;
1021
- };
1022
- });
1023
- }
1024
-
1025
- function sortTable(column) {
1026
- const table = document.getElementById('apiTable');
1027
- let rows, switching, i, x, y, shouldSwitch;
1028
- switching = true;
1029
- // Loop until no switching has been done:
1030
- while (switching) {
1031
- switching = false;
1032
- rows = table.rows;
1033
- // Loop through all table rows (except the first, which contains table headers):
1034
- for (i = 1; i < (rows.length - 1); i++) {
1035
- shouldSwitch = false;
1036
- // Get the two elements you want to compare, one from current row and one from the next:
1037
- x = rows[i].getElementsByTagName("TD")[column];
1038
- y = rows[i + 1].getElementsByTagName("TD")[column];
1039
- // Check if the two rows should switch place:
1040
- if (x.textContent.toLowerCase() > y.textContent.toLowerCase()) {
1041
- shouldSwitch = true;
1042
- break;
1043
- }
1044
- }
1045
- if (shouldSwitch) {
1046
- // If a switch has been marked, make the switch and mark that a switch has been done:
1047
- rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
1048
- switching = true;
1049
- }
1050
- }
1051
- }
1052
-
1053
- function hidePopup() {
1054
- const popupOverlay = document.getElementById("popupOverlay");
1055
- popupOverlay.style.visibility = "hidden";
1056
- popupOverlay.style.opacity = 0;
1057
- }
1058
-
1059
- window.onload = parsePath();
1060
- </script>
1061
-
1062
-
1063
-
1064
- <div id="popupOverlay" class="overlay">
1065
- <div class="popup">
1066
- <pre id="tracePopup">
1067
- "attributes": {
1068
- "http": {
1069
- "url": "http://localhost:3000/api/v1/test",
1070
- "host": "localhost:3000",
1071
- "method": "GET",
1072
- "scheme": "http",
1073
- "target": "/api/v1/test",
1074
- "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
1075
- "flavor": "1.1",
1076
- "status_code": 304,
1077
- "status_text": "NOT MODIFIED"
1078
- },
1079
- "net": {
1080
- "host": {
1081
- "name": "localhost",
1082
- "ip": "::1",
1083
- "port": 3000
1084
- },
1085
- "transport": "ip_tcp",
1086
- "peer": {
1087
- "ip": "::1",
1088
- "port": 58101
1089
- }
1090
- }
1091
- },
1092
- "links": {
1093
-
1094
- },
1095
- "events": {
1096
-
1097
- },
1098
- "_droppedAttributesCount": 0,
1099
- "_droppedEventsCount": 0,
1100
- "_droppedLinksCount": 0,
1101
- "status": {
1102
- "code": 0
1103
- },
1104
- "endTime": {
1105
- "0": 1724019619,
1106
- "1": 414126300
1107
- },
1108
- "_ended": true,
1109
- "_duration": {
1110
- "0": 0,
1111
- "1": 2126300
1112
- },
1113
- "name": "GET",
1114
- "_spanContext": {
1115
- "traceId": "ee70c9a937bbf95940a8971dc96077b3",
1116
- "spanId": "4fe34ee075253ecb",
1117
- "traceFlags": 1
1118
- },
1119
- "kind": 1,
1120
- "_performanceStartTime": 40561.097599983215,
1121
- "_performanceOffset": -8.425537109375,
1122
- "_startTimeProvided": false,
1123
- "startTime": {
1124
- "0": 1724019619,
1125
- "1": 412000000
1126
- },
1127
- "resource": {
1128
- "_attributes": {
1129
- "service": {
1130
- "name": "unknown_service:node"
1131
- },
1132
- "telemetry": {
1133
- "sdk": {
1134
- "language": "nodejs",
1135
- "name": "opentelemetry",
1136
- "version": "1.22.0"
1137
- }
1138
- },
1139
- "process": {
1140
- "pid": 23128,
1141
- "executable": {
1142
- "name": "npm",
1143
- "path": "C:\\Program Files\\nodejs\\node.exe"
1144
- },
1145
- "command_args": {
1146
- "0": "C:\\Program Files\\nodejs\\node.exe",
1147
- "1": "C:\\Personal\\ISA\\telemetry\\ot-ui-poc\\index"
1148
- },
1149
- "runtime": {
1150
- "version": "14.21.3",
1151
- "name": "nodejs",
1152
- "description": "Node.js"
1153
- },
1154
- "command": "C:\\Personal\\ISA\\telemetry\\ot-ui-poc\\index",
1155
- "owner": "manol"
1156
- }
1157
- },
1158
- "asyncAttributesPending": false,
1159
- "_syncAttributes": {
1160
- "service": {
1161
- "name": "unknown_service:node"
1162
- },
1163
- "telemetry": {
1164
- "sdk": {
1165
- "language": "nodejs",
1166
- "name": "opentelemetry",
1167
- "version": "1.22.0"
1168
- }
1169
- }
1170
- },
1171
- "_asyncAttributesPromise": {
1172
-
1173
- }
1174
- },
1175
- "instrumentationLibrary": {
1176
- "name": "@opentelemetry/instrumentation-http",
1177
- "version": "0.51.0"
1178
- },
1179
- "_spanLimits": {
1180
- "attributeValueLengthLimit": null,
1181
- "attributeCountLimit": 128,
1182
- "linkCountLimit": 128,
1183
- "eventCountLimit": 128,
1184
- "attributePerEventCountLimit": 128,
1185
- "attributePerLinkCountLimit": 128
1186
- },
1187
- "_attributeValueLengthLimit": null,
1188
- "_spanProcessor": "oas-telemetry skips this field to avoid circular reference",
1189
- "_id": "f2989F2IDm3uSfml"
1190
- </pre>
1191
- <a class="close" href="#" onclick="hidePopup()">&times;</a>
1192
- </div>
1193
- </div>
1194
-
1195
- </body>
1196
-
1197
- </html>`
1198
- };
1199
- }
1200
- module.exports = exports.default;