@oas-tools/oas-telemetry 0.5.2 → 0.6.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.
package/README.md CHANGED
@@ -10,25 +10,22 @@ Overall, **OAS Telemetry** will serve as a valuable tool for developers looking
10
10
 
11
11
  The package now supports both **ES Module (ESM)** and **CommonJS (CJS)** formats, making it compatible with a wide range of applications. Furthermore, **OAS Telemetry** provides a range of plugins to extend its functionality, enabling developers to tailor telemetry data collection, alerting, and reporting to meet specific requirements. See the [Telemetry Plugins](#telemetry-plugins) section for more information.
12
12
 
13
-
14
-
15
-
16
13
  ## Usage
17
14
 
18
15
  This section provides an overview of how to install and integrate **OAS Telemetry** into your EXISTING **Express.js** application. If you want to create a new example express application with **OAS Telemetry** integrated, refer to the [Full Examples](#full-examples) section at the end of this document.
19
16
 
20
-
21
17
  First, install the package using npm:
18
+
22
19
  ```sh
23
20
  npm install @oas-tools/oas-telemetry
24
21
  ```
25
22
 
26
-
27
23
  You can integrate the middleware into your Express application. The `spec` option is the OpenAPI Specification (OAS) content in JSON or YAML format. While this configuration is optional, it is recommended for the UI to function correctly.
28
24
 
29
25
  ### Using ES Modules (ESM)
30
26
 
31
27
  Add the following lines to your `index.js` file:
28
+
32
29
  ```js
33
30
  // This import MUST be at the top of the file
34
31
  import oasTelemetry from '@oas-tools/oas-telemetry';
@@ -44,6 +41,7 @@ app.use(oasTelemetry({
44
41
  ### Using CommonJS
45
42
 
46
43
  Add the following lines to your `index.js` file:
44
+
47
45
  ```js
48
46
  // This require MUST be at the top of the file
49
47
  const oasTelemetry = require('@oas-tools/oas-telemetry');
@@ -61,6 +59,7 @@ For complete examples of a working API with OAS Telemetry enabled, refer to the
61
59
  ## Custom Configuration
62
60
 
63
61
  You can also customize the telemetry configuration by passing options to the middleware function. For example:
62
+
64
63
  ```js
65
64
  const customTelemetryConfig = {
66
65
  spec: /* OAS content in json or yaml */, // Highly recommended
@@ -82,6 +81,52 @@ app.use(oasTelemetry(customTelemetryConfig));
82
81
 
83
82
  You can access the telemetry UI in the endpoint `/telemetry` (or `/custom-telemetry` if you set the `baseURL` option). This UI provides a user-friendly interface to interact with the telemetry data collected by the middleware.
84
83
 
84
+ ## Metrics Development (Temporary)
85
+
86
+ This feature is currently in development. The following endpoints are available under <baseURL>/metrics (e.g., /telemetry/metrics):
87
+
88
+ - GET /
89
+ - POST /find
90
+ - GET /reset
91
+
92
+ Expect an array of metrics objects. Each object includes data like a timestamp, cpuUsageData, processCpuUsageData, memoryData, and processMemoryData. Example snippet for one CPU core (followed by others in the array):
93
+
94
+ ```json
95
+ {
96
+ "timestamp": 1741717005911,
97
+ "cpuUsageData": [
98
+ {
99
+ "cpuNumber": "0",
100
+ "idle": 60486.234000000004,
101
+ "user": 1364.515,
102
+ "system": 1246.796,
103
+ "interrupt": 167,
104
+ "nice": 0,
105
+ "userP": 0.009375623379214043,
106
+ "systemP": 0.002992220227408737,
107
+ "idleP": 0.9850388988629563,
108
+ "interruptP": 0,
109
+ "niceP": 0
110
+ }
111
+ ],
112
+ "processCpuUsageData": {
113
+ "user": 0.968,
114
+ "system": 0.32799999999999996,
115
+ "userP": 0,
116
+ "systemP": 0
117
+ },
118
+ "memoryData": {
119
+ "used": 15726522368,
120
+ "free": 18437427200,
121
+ "usedP": 0.4603250668280579,
122
+ "freeP": 0.539674933171942
123
+ },
124
+ "processMemoryData": 75988992,
125
+ "_id": "6mXKM8uK7xSOqJVT"
126
+ }
127
+ ```
128
+
129
+ The shape of these objects may change as development continues.
85
130
 
86
131
  ## API Telemetry Endpoints
87
132
 
@@ -104,12 +149,12 @@ OAS Telemetry supports a range of plugins to extend its functionality, allowing
104
149
  One example plugin is the **Outlier Alert Over Messaging** plugin, which can be configured to send anomaly alerts to messaging platforms like Telegram. This plugin is especially useful for monitoring abnormal response times in your API, notifying selected channels to allow rapid responses to potential issues. For setup details, refer to its [README documentation](https://github.com/oas-tools/oas-telemetry-plugin-outlier-messaging/blob/main/README.md).
105
150
 
106
151
  OAS Telemetry plugins are flexible and support both ES Modules (ESM) and CommonJS (CJS) formats, regardless of whether your application is using ESM or CJS. This compatibility ensures that plugins work seamlessly in all configurations:
152
+
107
153
  - ESM applications can use plugins in either ESM or CJS format.
108
154
  - CJS applications can use plugins in either CJS or ESM format.
109
155
 
110
156
  This flexibility makes it easy to incorporate a wide variety of plugins in your preferred module system.
111
157
 
112
-
113
158
  ## Accessing Telemetry Data
114
159
 
115
160
  Using OAS Telemetry, you can access telemetry data through the UI, the `/telemetry/list` endpoint, or the `/telemetry/find` endpoint with a POST request using a MongoDB search syntax.
@@ -117,7 +162,9 @@ Using OAS Telemetry, you can access telemetry data through the UI, the `/telemet
117
162
  Note: if authentication is enabled, you must provide the correct credentials to access the telemetry data.
118
163
 
119
164
  ### Simple Search Example
165
+
120
166
  To perform a simple search, send a POST request to the `/telemetry/find` endpoint with the following JSON payload:
167
+
121
168
  ```json
122
169
  {
123
170
  "search": {
@@ -132,7 +179,9 @@ To perform a simple search, send a POST request to the `/telemetry/find` endpoin
132
179
  ```
133
180
 
134
181
  ### Complex Search Example
182
+
135
183
  For more complex searches using regex, additional parsing on the server and extra attributes in the POST request are required. Send a POST request to the `/telemetry/find` endpoint with the following JSON payload:
184
+
136
185
  ```json
137
186
  {
138
187
  "flags": {
@@ -153,19 +202,25 @@ For more complex searches using regex, additional parsing on the server and extr
153
202
  ```
154
203
 
155
204
  ## Full Examples
205
+
156
206
  To run these examples, follow these steps:
157
207
 
158
208
  1. Create a new folder for your project.
159
209
  2. Navigate to the folder and initialize a new Node.js project:
210
+
160
211
  ```sh
161
212
  npm init -y
162
213
  ```
214
+
163
215
  3. Install the **OAS Telemetry** package:
216
+
164
217
  ```sh
165
218
  npm install @oas-tools/oas-telemetry
166
219
  ```
220
+
167
221
  4. Save the example code as `index.js` in the project folder.
168
222
  5. Run the application:
223
+
169
224
  ```sh
170
225
  node index.js
171
226
  ```
@@ -173,6 +228,7 @@ To run these examples, follow these steps:
173
228
  Your project folder should now contain the necessary files to run the example with **OAS Telemetry** integrated.
174
229
 
175
230
  ### Simple Example [ES Module](https://nodejs.org/docs/latest/api/esm.html) (*.mjs)
231
+
176
232
  ```js index.mjs
177
233
  import oasTelemetry from '@oas-tools/oas-telemetry';
178
234
  import express from 'express';
@@ -280,6 +336,7 @@ app.get("/api/v1/clinics", (req, res) => {
280
336
  ```
281
337
 
282
338
  ### Simple Example [Common.js Module](https://nodejs.org/docs/latest/api/modules.html) (*.cjs)
339
+
283
340
  ```js index.cjs
284
341
  let oasTelemetry = require('@oas-tools/oas-telemetry');
285
342
  let express = require('express');
@@ -383,5 +440,3 @@ app.get("/api/v1/clinics", (req, res) => {
383
440
  res.send(clinics);
384
441
  });
385
442
  ```
386
-
387
-
package/dist/config.cjs CHANGED
@@ -4,13 +4,18 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.globalOasTlmConfig = exports.default = void 0;
7
+ var _api = require("@opentelemetry/api");
7
8
  var _dynamicExporter = _interopRequireDefault(require("./exporters/dynamicExporter.cjs"));
9
+ var _InMemoryDBMetricsExporter = require("./exporters/InMemoryDBMetricsExporter.cjs");
8
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
11
  //Environment variables
10
12
  //OASTLM_MODULE_DISABLED = 'true' //Disables the module (empty middleware and no tracing)
11
13
 
12
14
  const globalOasTlmConfig = exports.globalOasTlmConfig = {
13
15
  dynamicExporter: new _dynamicExporter.default(),
16
+ metricsExporter: new _InMemoryDBMetricsExporter.InMemoryDBMetricsExporter(),
17
+ systemMetricsInterval: 1000 * 5,
18
+ // 5 seconds
14
19
  baseURL: "/telemetry",
15
20
  spec: null,
16
21
  specFileName: "",
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.resetMetrics = exports.listMetrics = exports.findMetrics = void 0;
7
+ var _config = require("../config.cjs");
8
+ const listMetrics = async (req, res) => {
9
+ try {
10
+ const metrics = await _config.globalOasTlmConfig.metricsExporter.getFinishedMetrics();
11
+ res.send({
12
+ metricsCount: metrics.length,
13
+ metrics: metrics
14
+ });
15
+ } catch (err) {
16
+ console.error(err);
17
+ res.status(500).send({
18
+ error: 'Failed to list metrics data'
19
+ });
20
+ }
21
+ };
22
+ exports.listMetrics = listMetrics;
23
+ const findMetrics = (req, res) => {
24
+ const body = req.body;
25
+ const search = body?.search ? body.search : {};
26
+ _config.globalOasTlmConfig.metricsExporter.find(search, (err, docs) => {
27
+ if (err) {
28
+ console.error(err);
29
+ res.status(404).send({
30
+ metricsCount: 0,
31
+ metrics: [],
32
+ error: err
33
+ });
34
+ return;
35
+ }
36
+ const metrics = docs;
37
+ res.send({
38
+ metricsCount: metrics.length,
39
+ metrics: metrics
40
+ });
41
+ });
42
+ };
43
+ exports.findMetrics = findMetrics;
44
+ const resetMetrics = (req, res) => {
45
+ _config.globalOasTlmConfig.metricsExporter.reset();
46
+ res.send('Metrics reset');
47
+ };
48
+ exports.resetMetrics = resetMetrics;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.InMemoryDBMetricsExporter = void 0;
7
+ var _core = require("@opentelemetry/core");
8
+ var _nedb = _interopRequireDefault(require("@seald-io/nedb"));
9
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
+ class InMemoryDBMetricsExporter {
11
+ constructor() {
12
+ this._metrics = new _nedb.default();
13
+ this._stopped = false;
14
+ }
15
+ export(metrics, resultCallback) {
16
+ try {
17
+ if (!this._stopped) {
18
+ // metrics = metrics?.scopeMetrics;
19
+ // const cleanMetrics = metrics.map(metric => applyNesting(metric));
20
+ this._metrics.insert(metrics, (err, newDoc) => {
21
+ if (err) {
22
+ console.error('Insertion Error:', err);
23
+ return;
24
+ }
25
+ });
26
+ }
27
+ setTimeout(() => resultCallback({
28
+ code: _core.ExportResultCode.SUCCESS
29
+ }), 0);
30
+ } catch (error) {
31
+ console.error('Error exporting metrics\n' + error.message + '\n' + error.stack);
32
+ return resultCallback({
33
+ code: _core.ExportResultCode.FAILED,
34
+ error: new Error('Error exporting metrics\n' + error.message + '\n' + error.stack)
35
+ });
36
+ }
37
+ }
38
+ start() {
39
+ this._stopped = false;
40
+ }
41
+ stop() {
42
+ this._stopped = true;
43
+ }
44
+ isRunning() {
45
+ return !this._stopped;
46
+ }
47
+ shutdown() {
48
+ this._stopped = true;
49
+ this._metrics = new _nedb.default();
50
+ return this.forceFlush();
51
+ }
52
+ forceFlush() {
53
+ return Promise.resolve();
54
+ }
55
+ find(search, callback) {
56
+ this._metrics.find(search, callback);
57
+ }
58
+ reset() {
59
+ this._metrics = new _nedb.default();
60
+ }
61
+ getFinishedMetrics() {
62
+ return this._metrics.getAllData();
63
+ }
64
+ }
65
+ exports.InMemoryDBMetricsExporter = InMemoryDBMetricsExporter;
66
+ function convertToNestedObject(obj) {
67
+ const result = {};
68
+ for (const key in obj) {
69
+ const keys = key.split('.');
70
+ let temp = result;
71
+ for (let i = 0; i < keys.length; i++) {
72
+ const currentKey = keys[i];
73
+ if (i === keys.length - 1) {
74
+ // Last key, set the value
75
+ temp[currentKey] = obj[key];
76
+ } else {
77
+ // Intermediate key, ensure the object exists
78
+ if (!temp[currentKey]) {
79
+ temp[currentKey] = {};
80
+ }
81
+ temp = temp[currentKey];
82
+ }
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+
88
+ /**
89
+ * Applies nesting to all dot-separated keys within an object.
90
+ *
91
+ * @param {Object} obj - The object to apply nesting to.
92
+ * @returns {Object} - The transformed object with nested structures.
93
+ */
94
+ function applyNesting(obj) {
95
+ for (const key in obj) {
96
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
97
+ obj[key] = applyNesting(obj[key]);
98
+ }
99
+ }
100
+ return obj;
101
+ }
package/dist/index.cjs CHANGED
@@ -12,6 +12,7 @@ var _authMiddleware = require("./middleware/authMiddleware.cjs");
12
12
  var _authRoutes = _interopRequireDefault(require("./routes/authRoutes.cjs"));
13
13
  var _telemetryRoutes = require("./routes/telemetryRoutes.cjs");
14
14
  var _InMemoryDbExporter = require("./exporters/InMemoryDbExporter.cjs");
15
+ var _metricsRoutes = _interopRequireDefault(require("./routes/metricsRoutes.cjs"));
15
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
17
  let dbglog = () => {};
17
18
  if (process.env.OTDEBUG == "true") dbglog = console.log;
@@ -56,6 +57,7 @@ function oasTelemetry(OasTlmConfig) {
56
57
  router.use((0, _express.json)());
57
58
  router.use(baseURL, allAuthMiddlewares);
58
59
  router.use(baseURL, _telemetryRoutes.telemetryRoutes);
60
+ router.use(baseURL + "/metrics", _metricsRoutes.default);
59
61
  if (_config.globalOasTlmConfig.autoActivate) {
60
62
  _config.globalOasTlmConfig.dynamicExporter.exporter?.start();
61
63
  }
@@ -4,11 +4,14 @@ var _sdkNode = require("@opentelemetry/sdk-node");
4
4
  var _resources = require("@opentelemetry/resources");
5
5
  var _instrumentationHttp = require("@opentelemetry/instrumentation-http");
6
6
  var _config = require("./config.cjs");
7
+ var _systemMetrics = require("./systemMetrics.cjs");
7
8
  // import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
8
9
 
10
+ // Import system metrics functions
11
+
9
12
  // DynamicExporter allows changing to any exporter at runtime;
10
13
  const traceExporter = _config.globalOasTlmConfig.dynamicExporter;
11
- // Alternative 1: Using NodeSDK
14
+ // Alternative 1: Using NodeSDK
12
15
  const sdk = new _sdkNode.NodeSDK({
13
16
  resource: new _resources.Resource({
14
17
  service: 'oas-telemetry-service'
@@ -16,6 +19,26 @@ const sdk = new _sdkNode.NodeSDK({
16
19
  traceExporter,
17
20
  instrumentations: [new _instrumentationHttp.HttpInstrumentation()]
18
21
  });
22
+
23
+ // Collect and export system metrics
24
+ setInterval(() => {
25
+ const cpuUsageData = (0, _systemMetrics.getCpuUsageData)();
26
+ const processCpuUsageData = (0, _systemMetrics.getProcessCpuUsageData)();
27
+ const memoryData = (0, _systemMetrics.getMemoryData)();
28
+ const processMemoryData = (0, _systemMetrics.getProcessMemoryData)();
29
+ const metrics = {
30
+ timestamp: Date.now(),
31
+ cpuUsageData,
32
+ processCpuUsageData,
33
+ memoryData,
34
+ processMemoryData
35
+ };
36
+
37
+ // Export the collected metrics using the InMemoryDBMetricsExporter
38
+ const inMemoryDbMetricExporter = _config.globalOasTlmConfig.metricsExporter;
39
+ inMemoryDbMetricExporter.export(metrics, result => {});
40
+ }, _config.globalOasTlmConfig.systemMetricsInterval);
41
+ console.log('✅ OpenTelemetry System Metrics initialized.');
19
42
  if (process.env.OASTLM_MODULE_DISABLED !== 'true') {
20
43
  sdk.start();
21
44
  }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.metricsRoutes = exports.default = void 0;
7
+ var _express = require("express");
8
+ var _metricsController = require("../controllers/metricsController.cjs");
9
+ const metricsRoutes = exports.metricsRoutes = (0, _express.Router)();
10
+
11
+ // Metrics Control
12
+ metricsRoutes.get('/', _metricsController.listMetrics);
13
+ metricsRoutes.post('/find', _metricsController.findMetrics);
14
+ metricsRoutes.get('/reset', _metricsController.resetMetrics);
15
+ var _default = exports.default = metricsRoutes;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getCpuUsageData = getCpuUsageData;
7
+ exports.getMemoryData = getMemoryData;
8
+ exports.getProcessCpuUsageData = getProcessCpuUsageData;
9
+ exports.getProcessMemoryData = getProcessMemoryData;
10
+ var _os = require("os");
11
+ const MILLISECOND = 1 / 1e3;
12
+ const MICROSECOND = 1 / 1e6;
13
+ let prevOsData = {
14
+ time: Date.now(),
15
+ cpus: (0, _os.cpus)()
16
+ };
17
+ function getCpuUsageData() {
18
+ const currentTime = Date.now();
19
+ const timeElapsed = currentTime - prevOsData.time;
20
+ const currentOsData = {
21
+ time: currentTime,
22
+ cpus: (0, _os.cpus)()
23
+ };
24
+ const usageData = currentOsData.cpus.map((cpu, cpuNumber) => {
25
+ const prevTimes = prevOsData.cpus[cpuNumber].times;
26
+ const currTimes = cpu.times;
27
+ const idle = currTimes.idle * MILLISECOND;
28
+ const user = currTimes.user * MILLISECOND;
29
+ const system = currTimes.sys * MILLISECOND;
30
+ const interrupt = currTimes.irq * MILLISECOND;
31
+ const nice = currTimes.nice * MILLISECOND;
32
+ const idleP = (currTimes.idle - prevTimes.idle) / timeElapsed;
33
+ const userP = (currTimes.user - prevTimes.user) / timeElapsed;
34
+ const systemP = (currTimes.sys - prevTimes.sys) / timeElapsed;
35
+ const interruptP = (currTimes.irq - prevTimes.irq) / timeElapsed;
36
+ const niceP = (currTimes.nice - prevTimes.nice) / timeElapsed;
37
+ return {
38
+ cpuNumber: String(cpuNumber),
39
+ idle,
40
+ user,
41
+ system,
42
+ interrupt,
43
+ nice,
44
+ userP,
45
+ systemP,
46
+ idleP,
47
+ interruptP,
48
+ niceP
49
+ };
50
+ });
51
+ prevOsData = currentOsData;
52
+ return usageData;
53
+ }
54
+ let prevProcData = {
55
+ time: Date.now(),
56
+ usage: process.cpuUsage()
57
+ };
58
+ function getProcessCpuUsageData() {
59
+ const currentTime = Date.now();
60
+ const currentUsage = process.cpuUsage();
61
+ const prevUsage = prevProcData.usage;
62
+ const timeElapsed = (currentTime - prevProcData.time) * 1000;
63
+ const cpusTimeElapsed = timeElapsed * prevOsData.cpus.length;
64
+ const user = currentUsage.user * MICROSECOND;
65
+ const system = currentUsage.system * MICROSECOND;
66
+ const userP = (currentUsage.user - prevUsage.user) / cpusTimeElapsed;
67
+ const systemP = (currentUsage.system - prevUsage.system) / cpusTimeElapsed;
68
+ prevProcData = {
69
+ time: currentTime,
70
+ usage: currentUsage
71
+ };
72
+ return {
73
+ user,
74
+ system,
75
+ userP,
76
+ systemP
77
+ };
78
+ }
79
+ function getMemoryData() {
80
+ const total = (0, _os.totalmem)();
81
+ const free = (0, _os.freemem)();
82
+ const used = total - free;
83
+ const freeP = free / total;
84
+ const usedP = used / total;
85
+ return {
86
+ used: used,
87
+ free: free,
88
+ usedP: usedP,
89
+ freeP: freeP
90
+ };
91
+ }
92
+ function getProcessMemoryData() {
93
+ if (process.memoryUsage.rss) {
94
+ return process.memoryUsage.rss();
95
+ }
96
+ return process.memoryUsage().rss;
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oas-tools/oas-telemetry",
3
- "version": "0.5.2",
3
+ "version": "0.6.1",
4
4
  "description": "This package exports an Express middleware that traces requests and responses of an Express application using OpenTelemetry.",
5
5
  "author": "Manuel Otero",
6
6
  "contributors": [
@@ -45,6 +45,7 @@
45
45
  "default": "./src/index.js"
46
46
  },
47
47
  "dependencies": {
48
+ "@opentelemetry/host-metrics": "^0.35.5",
48
49
  "@opentelemetry/instrumentation-http": "^0.51.0",
49
50
  "@opentelemetry/resources": "^1.24.0",
50
51
  "@opentelemetry/sdk-node": "^0.49.1",
package/src/config.js CHANGED
@@ -1,10 +1,14 @@
1
+ import { metrics } from "@opentelemetry/api";
1
2
  import DynamicExporter from "./exporters/dynamicExporter.js";
3
+ import { InMemoryDBMetricsExporter } from "./exporters/InMemoryDBMetricsExporter.js";
2
4
 
3
5
  //Environment variables
4
6
  //OASTLM_MODULE_DISABLED = 'true' //Disables the module (empty middleware and no tracing)
5
7
 
6
8
  export const globalOasTlmConfig = {
7
9
  dynamicExporter: new DynamicExporter(),
10
+ metricsExporter: new InMemoryDBMetricsExporter(),
11
+ systemMetricsInterval: 1000 * 5, // 5 seconds
8
12
  baseURL: "/telemetry",
9
13
  spec: null,
10
14
  specFileName: "",
@@ -0,0 +1,30 @@
1
+ import { globalOasTlmConfig } from '../config.js';
2
+
3
+ export const listMetrics = async (req, res) => {
4
+ try {
5
+ const metrics = await globalOasTlmConfig.metricsExporter.getFinishedMetrics();
6
+ res.send({ metricsCount: metrics.length, metrics: metrics });
7
+ } catch (err) {
8
+ console.error(err);
9
+ res.status(500).send({ error: 'Failed to list metrics data' });
10
+ }
11
+ }
12
+
13
+ export const findMetrics = (req, res) => {
14
+ const body = req.body;
15
+ const search = body?.search ? body.search : {};
16
+ globalOasTlmConfig.metricsExporter.find(search, (err, docs) => {
17
+ if (err) {
18
+ console.error(err);
19
+ res.status(404).send({ metricsCount: 0, metrics: [], error: err });
20
+ return;
21
+ }
22
+ const metrics = docs;
23
+ res.send({ metricsCount: metrics.length, metrics: metrics });
24
+ });
25
+ }
26
+
27
+ export const resetMetrics = (req, res) => {
28
+ globalOasTlmConfig.metricsExporter.reset();
29
+ res.send('Metrics reset');
30
+ }
@@ -0,0 +1,111 @@
1
+ import { ExportResultCode } from '@opentelemetry/core';
2
+ import dataStore from '@seald-io/nedb';
3
+
4
+ export class InMemoryDBMetricsExporter {
5
+ constructor() {
6
+ this._metrics = new dataStore();
7
+ this._stopped = false;
8
+ }
9
+
10
+ export(metrics, resultCallback) {
11
+ try {
12
+ if (!this._stopped) {
13
+ // metrics = metrics?.scopeMetrics;
14
+ // const cleanMetrics = metrics.map(metric => applyNesting(metric));
15
+ this._metrics.insert(metrics, (err, newDoc) => {
16
+ if (err) {
17
+ console.error('Insertion Error:', err);
18
+ return;
19
+ }
20
+ });
21
+ }
22
+ setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
23
+ } catch (error) {
24
+ console.error('Error exporting metrics\n' + error.message + '\n' + error.stack);
25
+ return resultCallback({
26
+ code: ExportResultCode.FAILED,
27
+ error: new Error('Error exporting metrics\n' + error.message + '\n' + error.stack),
28
+ });
29
+ }
30
+ }
31
+
32
+ start() {
33
+ this._stopped = false;
34
+ }
35
+
36
+ stop() {
37
+ this._stopped = true;
38
+ }
39
+
40
+ isRunning() {
41
+ return !this._stopped;
42
+ }
43
+
44
+ shutdown() {
45
+ this._stopped = true;
46
+ this._metrics = new dataStore();
47
+ return this.forceFlush();
48
+ }
49
+
50
+ forceFlush() {
51
+ return Promise.resolve();
52
+ }
53
+
54
+ find(search, callback) {
55
+ this._metrics.find(search, callback);
56
+ }
57
+
58
+ reset() {
59
+ this._metrics = new dataStore();
60
+ }
61
+
62
+ getFinishedMetrics() {
63
+ return this._metrics.getAllData();
64
+ }
65
+ }
66
+
67
+ function convertToNestedObject(obj) {
68
+ const result = {};
69
+
70
+ for (const key in obj) {
71
+ const keys = key.split('.');
72
+ let temp = result;
73
+
74
+ for (let i = 0; i < keys.length; i++) {
75
+ const currentKey = keys[i];
76
+
77
+ if (i === keys.length - 1) {
78
+ // Last key, set the value
79
+ temp[currentKey] = obj[key];
80
+ } else {
81
+ // Intermediate key, ensure the object exists
82
+ if (!temp[currentKey]) {
83
+ temp[currentKey] = {};
84
+ }
85
+ temp = temp[currentKey];
86
+ }
87
+ }
88
+ }
89
+
90
+ return result;
91
+ }
92
+
93
+ /**
94
+ * Applies nesting to all dot-separated keys within an object.
95
+ *
96
+ * @param {Object} obj - The object to apply nesting to.
97
+ * @returns {Object} - The transformed object with nested structures.
98
+ */
99
+ function applyNesting(obj) {
100
+
101
+
102
+ for (const key in obj) {
103
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
104
+ obj[key] = applyNesting(obj[key]);
105
+ }
106
+ }
107
+
108
+
109
+ return obj;
110
+ }
111
+
package/src/index.js CHANGED
@@ -6,6 +6,7 @@ import { authMiddleware } from './middleware/authMiddleware.js';
6
6
  import authRoutes from './routes/authRoutes.js';
7
7
  import { telemetryRoutes } from './routes/telemetryRoutes.js';
8
8
  import { InMemoryExporter } from './exporters/InMemoryDbExporter.js';
9
+ import metricsRoutes from './routes/metricsRoutes.js';
9
10
 
10
11
 
11
12
  let dbglog = () => { };
@@ -41,7 +42,7 @@ export default function oasTelemetry(OasTlmConfig) {
41
42
  }
42
43
  }
43
44
  console.log("baseURL: ", globalOasTlmConfig.baseURL);
44
- globalOasTlmConfig.dynamicExporter.changeExporter( OasTlmConfig.exporter ?? new InMemoryExporter() );
45
+ globalOasTlmConfig.dynamicExporter.changeExporter( globalOasTlmConfig.exporter ?? new InMemoryExporter() );
45
46
 
46
47
  if (globalOasTlmConfig.spec)
47
48
  dbglog(`Spec content provided`);
@@ -57,6 +58,7 @@ export default function oasTelemetry(OasTlmConfig) {
57
58
  router.use(json());
58
59
  router.use(baseURL, allAuthMiddlewares);
59
60
  router.use(baseURL, telemetryRoutes);
61
+ router.use(baseURL + "/metrics", metricsRoutes);
60
62
 
61
63
  if (globalOasTlmConfig.autoActivate) {
62
64
  globalOasTlmConfig.dynamicExporter.exporter?.start();
@@ -1,25 +1,47 @@
1
-
2
1
  import { NodeSDK } from '@opentelemetry/sdk-node';
3
2
  // import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
4
3
  import { Resource } from '@opentelemetry/resources';
5
4
  import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
6
5
  import { globalOasTlmConfig } from './config.js';
6
+ import { getCpuUsageData, getProcessCpuUsageData, getMemoryData, getProcessMemoryData } from './systemMetrics.js'; // Import system metrics functions
7
+
8
+ // DynamicExporter allows changing to any exporter at runtime;
9
+ const traceExporter = globalOasTlmConfig.dynamicExporter;
10
+ // Alternative 1: Using NodeSDK
11
+ const sdk = new NodeSDK({
12
+ resource: new Resource({
13
+ service: 'oas-telemetry-service'
14
+ }),
15
+ traceExporter,
16
+ instrumentations: [new HttpInstrumentation()]
17
+ });
18
+
19
+
20
+ // Collect and export system metrics
21
+ setInterval(() => {
22
+ const cpuUsageData = getCpuUsageData();
23
+ const processCpuUsageData = getProcessCpuUsageData();
24
+ const memoryData = getMemoryData();
25
+ const processMemoryData = getProcessMemoryData();
26
+
27
+ const metrics = {
28
+ timestamp: Date.now(),
29
+ cpuUsageData,
30
+ processCpuUsageData,
31
+ memoryData,
32
+ processMemoryData,
33
+ };
34
+
35
+ // Export the collected metrics using the InMemoryDBMetricsExporter
36
+ const inMemoryDbMetricExporter = globalOasTlmConfig.metricsExporter;
37
+ inMemoryDbMetricExporter.export(metrics, (result) => {});
38
+ }, globalOasTlmConfig.systemMetricsInterval);
7
39
 
40
+ console.log('✅ OpenTelemetry System Metrics initialized.');
8
41
 
9
- // DynamicExporter allows changing to any exporter at runtime;
10
- const traceExporter = globalOasTlmConfig.dynamicExporter;
11
- // Alternative 1: Using NodeSDK
12
- const sdk = new NodeSDK({
13
- resource: new Resource({
14
- service: 'oas-telemetry-service'
15
- }),
16
- traceExporter,
17
- instrumentations: [new HttpInstrumentation()]
18
- });
19
-
20
- if (process.env.OASTLM_MODULE_DISABLED !== 'true') {
21
- sdk.start()
22
- }
42
+ if (process.env.OASTLM_MODULE_DISABLED !== 'true') {
43
+ sdk.start()
44
+ }
23
45
 
24
46
  // Alternative 2:
25
47
  // const provider = new NodeTracerProvider();
@@ -0,0 +1,15 @@
1
+ import { Router } from 'express';
2
+ import {
3
+ listMetrics,
4
+ findMetrics,
5
+ resetMetrics
6
+ } from '../controllers/metricsController.js';
7
+
8
+ export const metricsRoutes = Router();
9
+
10
+ // Metrics Control
11
+ metricsRoutes.get('/', listMetrics);
12
+ metricsRoutes.post('/find', findMetrics);
13
+ metricsRoutes.get('/reset', resetMetrics);
14
+
15
+ export default metricsRoutes;
@@ -0,0 +1,102 @@
1
+ import { cpus, totalmem, freemem } from 'os';
2
+
3
+ const MILLISECOND = 1 / 1e3;
4
+ const MICROSECOND = 1 / 1e6;
5
+
6
+ let prevOsData = {
7
+ time: Date.now(),
8
+ cpus: cpus(),
9
+ };
10
+
11
+ export function getCpuUsageData() {
12
+ const currentTime = Date.now();
13
+ const timeElapsed = currentTime - prevOsData.time;
14
+ const currentOsData = { time: currentTime, cpus: cpus() };
15
+
16
+ const usageData = currentOsData.cpus.map((cpu, cpuNumber) => {
17
+ const prevTimes = prevOsData.cpus[cpuNumber].times;
18
+ const currTimes = cpu.times;
19
+
20
+ const idle = currTimes.idle * MILLISECOND;
21
+ const user = currTimes.user * MILLISECOND;
22
+ const system = currTimes.sys * MILLISECOND;
23
+ const interrupt = currTimes.irq * MILLISECOND;
24
+ const nice = currTimes.nice * MILLISECOND;
25
+
26
+ const idleP = (currTimes.idle - prevTimes.idle) / timeElapsed;
27
+ const userP = (currTimes.user - prevTimes.user) / timeElapsed;
28
+ const systemP = (currTimes.sys - prevTimes.sys) / timeElapsed;
29
+ const interruptP = (currTimes.irq - prevTimes.irq) / timeElapsed;
30
+ const niceP = (currTimes.nice - prevTimes.nice) / timeElapsed;
31
+
32
+ return {
33
+ cpuNumber: String(cpuNumber),
34
+ idle,
35
+ user,
36
+ system,
37
+ interrupt,
38
+ nice,
39
+ userP,
40
+ systemP,
41
+ idleP,
42
+ interruptP,
43
+ niceP,
44
+ };
45
+ });
46
+
47
+ prevOsData = currentOsData;
48
+
49
+ return usageData;
50
+ }
51
+
52
+ let prevProcData = {
53
+ time: Date.now(),
54
+ usage: process.cpuUsage(),
55
+ };
56
+
57
+
58
+
59
+
60
+ export function getProcessCpuUsageData() {
61
+ const currentTime = Date.now();
62
+ const currentUsage = process.cpuUsage();
63
+ const prevUsage = prevProcData.usage;
64
+ const timeElapsed = (currentTime - prevProcData.time) * 1000;
65
+ const cpusTimeElapsed = timeElapsed * prevOsData.cpus.length;
66
+
67
+ const user = currentUsage.user * MICROSECOND;
68
+ const system = currentUsage.system * MICROSECOND;
69
+ const userP = (currentUsage.user - prevUsage.user) / cpusTimeElapsed;
70
+ const systemP = (currentUsage.system - prevUsage.system) / cpusTimeElapsed;
71
+
72
+ prevProcData = { time: currentTime, usage: currentUsage };
73
+
74
+ return {
75
+ user,
76
+ system,
77
+ userP,
78
+ systemP,
79
+ };
80
+ }
81
+
82
+ export function getMemoryData() {
83
+ const total = totalmem();
84
+ const free = freemem();
85
+ const used = total - free;
86
+ const freeP = free / total;
87
+ const usedP = used / total;
88
+
89
+ return {
90
+ used: used,
91
+ free: free,
92
+ usedP: usedP,
93
+ freeP: freeP,
94
+ };
95
+ }
96
+
97
+ export function getProcessMemoryData() {
98
+ if (process.memoryUsage.rss) {
99
+ return process.memoryUsage.rss();
100
+ }
101
+ return process.memoryUsage().rss;
102
+ }