ai-perf-sdk 1.0.3 → 1.0.4

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,14 +1,12 @@
1
1
  {
2
2
  "name": "ai-perf-sdk",
3
- "version": "1.0.3",
4
- "description": "Plug-and-play performance monitoring SDK using OpenTelemetry",
3
+ "version": "1.0.4",
4
+ "description": "Plug-and-play custom performance monitoring SDK",
5
5
  "license": "MIT",
6
6
  "author": "Akash",
7
7
  "type": "commonjs",
8
8
  "main": "src/index.js",
9
9
  "dependencies": {
10
- "@opentelemetry/sdk-node": "^0.46.0",
11
- "@opentelemetry/auto-instrumentations-node": "^0.46.0",
12
- "@opentelemetry/exporter-trace-otlp-http": "^0.46.0"
10
+ "node-fetch": "^2.7.0"
13
11
  }
14
12
  }
package/src/batcher.js ADDED
@@ -0,0 +1,64 @@
1
+ const { sendBatch } = require("./client");
2
+
3
+ let queue = [];
4
+ let timer = null;
5
+ let config = {};
6
+
7
+ /**
8
+ * Initializes the batching system.
9
+ */
10
+ function initBatcher(opts) {
11
+ config = {
12
+ endpoint: opts.endpoint,
13
+ batchSize: opts.batchSize || 10,
14
+ flushInterval: opts.flushInterval || 5000,
15
+ headers: opts.headers || {}
16
+ };
17
+
18
+ if (timer) clearInterval(timer);
19
+ timer = setInterval(flush, config.flushInterval);
20
+
21
+ return {
22
+ stop: () => {
23
+ if (timer) clearInterval(timer);
24
+ return flush();
25
+ }
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Adds data to the queue and flushes if batch size reached.
31
+ */
32
+ function enqueue(data) {
33
+ queue.push(data);
34
+ if (queue.length >= config.batchSize) {
35
+ flush();
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Flushes the current queue using the client.
41
+ */
42
+ async function flush() {
43
+ if (queue.length === 0) return;
44
+
45
+ const payload = queue.splice(0, queue.length);
46
+
47
+ try {
48
+ await sendBatch({
49
+ endpoint: config.endpoint,
50
+ headers: config.headers,
51
+ payload: payload
52
+ });
53
+ } catch (err) {
54
+ console.error("[AI-PERF-SDK] Batch flush failed. Retrying later...", err.message);
55
+ // Put back in queue to retry
56
+ queue.unshift(...payload);
57
+ }
58
+ }
59
+
60
+ module.exports = {
61
+ initBatcher,
62
+ enqueue,
63
+ flush
64
+ };
package/src/client.js ADDED
@@ -0,0 +1,32 @@
1
+ const fetch = global.fetch || (typeof require !== 'undefined' ? require("node-fetch") : null);
2
+
3
+ /**
4
+ * Sends a batch of telemetry data to the endpoint.
5
+ * @param {Object} options
6
+ * @param {string} options.endpoint - Collector URL
7
+ * @param {Object} options.headers - Custom request headers
8
+ * @param {Array} options.payload - Array of telemetry items
9
+ */
10
+ async function sendBatch({ endpoint, headers, payload }) {
11
+ if (!fetch) {
12
+ throw new Error("Fetch is not available. Please install 'node-fetch' if using Node.js < 18.");
13
+ }
14
+
15
+ const response = await fetch(endpoint, {
16
+ method: "POST",
17
+ headers: {
18
+ "Content-Type": "application/json",
19
+ ...headers,
20
+ },
21
+ body: JSON.stringify(payload),
22
+ });
23
+
24
+ if (!response.ok) {
25
+ const text = await response.text();
26
+ throw new Error(
27
+ `Telemetry send failed: ${response.status} ${text}`
28
+ );
29
+ }
30
+ }
31
+
32
+ module.exports = { sendBatch };
package/src/index.js CHANGED
@@ -1,6 +1,46 @@
1
- const { startSDK, shutdownSDK } = require("./sdk");
1
+ const { initBatcher, flush } = require("./batcher");
2
+ const { instrumentHttp } = require("./instrument");
3
+
4
+ let stopBatcher = null;
5
+
6
+ /**
7
+ * Starts the custom Performance SDK.
8
+ * @param {Object} options
9
+ * @param {string} options.endpoint - Collector URL
10
+ * @param {number} [options.batchSize] - Items per batch (default 10)
11
+ * @param {number} [options.flushInterval] - MS between flushes (default 5000)
12
+ * @param {Object} [options.headers] - Custom headers (e.g., x-session-id)
13
+ */
14
+ function startSdk(options = {}) {
15
+ if (!options.endpoint) {
16
+ console.error("[AI-PERF-SDK] Configuration error: Missing 'endpoint'.");
17
+ return;
18
+ }
19
+
20
+ // Initialize Batching Logic
21
+ const batcher = initBatcher(options);
22
+ stopBatcher = batcher.stop;
23
+
24
+ // Initialize Instrumentation (Fetch/HTTP/HTTPS)
25
+ instrumentHttp();
26
+
27
+ console.log("[AI-PERF-SDK] SDK Initialized successfully.");
28
+ }
29
+
30
+ /**
31
+ * Shuts down the SDK and flushing pending spans.
32
+ */
33
+ async function shutdownSdk() {
34
+ console.log("[AI-PERF-SDK] Shutting down and flushing final batch...");
35
+ if (stopBatcher) {
36
+ await stopBatcher();
37
+ }
38
+ }
2
39
 
3
40
  module.exports = {
4
- initPerformanceSDK: startSDK,
5
- shutdownPerformanceSDK: shutdownSDK
41
+ startSDK: startSdk,
42
+ shutdownSDK: shutdownSdk,
43
+ // Aliases for compatibility
44
+ initPerformanceSDK: startSdk,
45
+ shutdownPerformanceSDK: shutdownSdk
6
46
  };
@@ -0,0 +1,102 @@
1
+ const { performance } = require("perf_hooks");
2
+ const { enqueue } = require("./batcher");
3
+ const http = require("http");
4
+ const https = require("https");
5
+
6
+ /**
7
+ * Instruments global fetch and Node.js http/https modules.
8
+ */
9
+ function instrumentHttp() {
10
+ // 1. Instrument global fetch
11
+ if (global.fetch) {
12
+ const originalFetch = global.fetch;
13
+ global.fetch = async (...args) => {
14
+ const url = args[0];
15
+ const urlStr = typeof url === 'string' ? url : (url.url || url.toString());
16
+
17
+ // Avoid self-telemetry loops
18
+ if (urlStr && urlStr.includes('/api/telemetry')) {
19
+ return originalFetch(...args);
20
+ }
21
+
22
+ const start = performance.now();
23
+ let status = 0;
24
+ let error = null;
25
+
26
+ try {
27
+ const res = await originalFetch(...args);
28
+ status = res.status;
29
+ return res;
30
+ } catch (err) {
31
+ error = err.message;
32
+ throw err;
33
+ } finally {
34
+ const duration = performance.now() - start;
35
+ enqueue({
36
+ type: "http-fetch",
37
+ url: urlStr,
38
+ method: args[1]?.method || 'GET',
39
+ duration: Math.round(duration),
40
+ status: status,
41
+ error: error,
42
+ timestamp: new Date().toISOString()
43
+ });
44
+ }
45
+ };
46
+ }
47
+
48
+ // 2. Instrument Node.js http/https modules
49
+ [http, https].forEach((module) => {
50
+ const originalRequest = module.request;
51
+ module.request = function (options, callback) {
52
+ const start = performance.now();
53
+
54
+ // Reconstruct URL
55
+ const protocol = module === https ? 'https:' : 'http:';
56
+ const host = options.host || options.hostname || 'localhost';
57
+ const path = options.path || '/';
58
+ const urlStr = `${protocol}//${host}${path}`;
59
+
60
+ if (urlStr.includes('/api/telemetry')) {
61
+ return originalRequest.apply(this, arguments);
62
+ }
63
+
64
+ const req = originalRequest.call(this, options, (res) => {
65
+ res.on('end', () => {
66
+ enqueue({
67
+ type: "http-node",
68
+ url: urlStr,
69
+ method: options.method || 'GET',
70
+ duration: Math.round(performance.now() - start),
71
+ status: res.statusCode,
72
+ timestamp: new Date().toISOString()
73
+ });
74
+ });
75
+ if (callback) callback(res);
76
+ });
77
+
78
+ req.on('error', (err) => {
79
+ enqueue({
80
+ type: "http-node-error",
81
+ url: urlStr,
82
+ method: options.method || 'GET',
83
+ duration: Math.round(performance.now() - start),
84
+ status: 0,
85
+ error: err.message,
86
+ timestamp: new Date().toISOString()
87
+ });
88
+ });
89
+
90
+ return req;
91
+ };
92
+
93
+ // Also patch module.get
94
+ module.get = function (options, callback) {
95
+ const req = module.request(options, callback);
96
+ req.end();
97
+ return req;
98
+ };
99
+ });
100
+ }
101
+
102
+ module.exports = { instrumentHttp };
package/src/utils.js ADDED
File without changes
package/src/sdk.js DELETED
@@ -1,118 +0,0 @@
1
- const { NodeSDK } = require("@opentelemetry/sdk-node");
2
- const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");
3
- const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-http");
4
- const { diag, DiagConsoleLogger, DiagLogLevel } = require("@opentelemetry/api");
5
- const defaults = require("./config");
6
-
7
- let sdkInstance = null;
8
-
9
- /**
10
- * Initializes the AI Performance SDK.
11
- * @param {Object} options Configuration options
12
- * @param {string} [options.collectorEndpoint] - URL of the telemetry collector
13
- * @param {Object} [options.headers] - Headers to send with telemetry (e.g., x-session-id)
14
- * @param {string} [options.serviceName] - Name of the service
15
- * @param {boolean} [options.debug] - Enable debug logging
16
- * @param {boolean} [options.disableDbInstrumentation] - Disable DB auto-instrumentation (MongoDB/Mongoose/Redis) to prevent crashes
17
- */
18
- function startSDK(options = {}) {
19
- if (sdkInstance) {
20
- if (options.debug) console.log("[AI-PERF] SDK already started.");
21
- return;
22
- }
23
-
24
- // 1. Merge Configuration
25
- const config = {
26
- ...defaults,
27
- ...options
28
- };
29
-
30
- // 2. Enable Debug Logging if requested
31
- if (config.debug) {
32
- console.log("[AI-PERF] Debug Mode Enabled");
33
- diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
34
- }
35
-
36
- // 3. Robust Header Handling
37
- let combinedHeaders = { ...config.headers };
38
-
39
- if (process.env.OTEL_EXPORTER_OTLP_HEADERS) {
40
- try {
41
- const envHeaders = process.env.OTEL_EXPORTER_OTLP_HEADERS.split(',').reduce((acc, pair) => {
42
- const [k, v] = pair.split('=');
43
- if (k && v) acc[k.trim()] = v.trim();
44
- return acc;
45
- }, {});
46
- combinedHeaders = { ...combinedHeaders, ...envHeaders };
47
- } catch (e) {
48
- console.warn("[AI-PERF] Failed to parse OTEL_EXPORTER_OTLP_HEADERS", e);
49
- }
50
- }
51
-
52
- if (config.debug) {
53
- console.log("[AI-PERF] Exporter Config:", {
54
- url: config.collectorEndpoint,
55
- headers: combinedHeaders,
56
- protocol: process.env.OTEL_EXPORTER_OTLP_PROTOCOL || 'default (protobuf)'
57
- });
58
- }
59
-
60
- const exporter = new OTLPTraceExporter({
61
- url: config.collectorEndpoint,
62
- headers: combinedHeaders
63
- });
64
-
65
- // 4. Configure Auto-Instrumentation
66
- const instrumentationConfig = {};
67
-
68
- if (config.disableDbInstrumentation) {
69
- if (config.debug) console.log("[AI-PERF] Disabling DB Instrumentations");
70
- instrumentationConfig['@opentelemetry/instrumentation-mongodb'] = { enabled: false };
71
- instrumentationConfig['@opentelemetry/instrumentation-mongoose'] = { enabled: false };
72
- instrumentationConfig['@opentelemetry/instrumentation-redis'] = { enabled: false };
73
- instrumentationConfig['@opentelemetry/instrumentation-ioredis'] = { enabled: false };
74
- instrumentationConfig['@opentelemetry/instrumentation-pg'] = { enabled: false };
75
- instrumentationConfig['@opentelemetry/instrumentation-mysql'] = { enabled: false };
76
- instrumentationConfig['@opentelemetry/instrumentation-mysql2'] = { enabled: false };
77
- }
78
-
79
- sdkInstance = new NodeSDK({
80
- serviceName: config.serviceName,
81
- traceExporter: exporter,
82
- instrumentations: [getNodeAutoInstrumentations(instrumentationConfig)]
83
- });
84
-
85
- try {
86
- sdkInstance.start();
87
- console.log(`[AI-PERF] SDK started for ${config.serviceName} ` + (config.disableDbInstrumentation ? "(Safe Mode)" : ""));
88
-
89
- // 5. Automatic Graceful Shutdown
90
- const handleShutdown = async () => {
91
- console.log("[AI-PERF] Graceful shutdown initiated...");
92
- await shutdownSDK();
93
- process.exit(0);
94
- };
95
-
96
- process.on('SIGTERM', handleShutdown);
97
- process.on('SIGINT', handleShutdown);
98
-
99
- } catch (e) {
100
- console.error("[AI-PERF] Failed to start SDK:", e);
101
- }
102
- }
103
-
104
- function shutdownSDK() {
105
- if (!sdkInstance) return;
106
-
107
- sdkInstance.shutdown().then(() => {
108
- console.log("[AI-PERF] SDK shutdown");
109
- sdkInstance = null;
110
- }).catch((error) => {
111
- console.error("[AI-PERF] Error shutting down SDK", error);
112
- });
113
- }
114
-
115
- module.exports = {
116
- startSDK,
117
- shutdownSDK
118
- };