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 +3 -5
- package/src/batcher.js +64 -0
- package/src/client.js +32 -0
- package/src/index.js +43 -3
- package/src/instrument.js +102 -0
- package/src/utils.js +0 -0
- package/src/sdk.js +0 -118
package/package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-perf-sdk",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Plug-and-play performance monitoring SDK
|
|
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
|
-
"
|
|
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 {
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
};
|