@netgreener/runtime 0.1.0
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/CHANGELOG.md +16 -0
- package/README.md +28 -0
- package/TUTORIAL.md +495 -0
- package/dist/aggregator.d.ts +43 -0
- package/dist/aggregator.js +270 -0
- package/dist/analyze/analyzeGate.d.ts +44 -0
- package/dist/analyze/analyzeGate.js +114 -0
- package/dist/analyze/apiOverconsumptionDetector.d.ts +13 -0
- package/dist/analyze/apiOverconsumptionDetector.js +75 -0
- package/dist/analyze/astDetectors.d.ts +38 -0
- package/dist/analyze/astDetectors.js +566 -0
- package/dist/analyze/buildResourceFindings.d.ts +34 -0
- package/dist/analyze/buildResourceFindings.js +86 -0
- package/dist/analyze/cli.d.ts +20 -0
- package/dist/analyze/cli.js +186 -0
- package/dist/analyze/cpuResourceDetectors.d.ts +35 -0
- package/dist/analyze/cpuResourceDetectors.js +163 -0
- package/dist/analyze/heuristicScan.d.ts +61 -0
- package/dist/analyze/heuristicScan.js +91 -0
- package/dist/analyze/index.d.ts +15 -0
- package/dist/analyze/index.js +15 -0
- package/dist/analyze/loadTypescript.d.ts +8 -0
- package/dist/analyze/loadTypescript.js +23 -0
- package/dist/analyze/memoryIoDetectors.d.ts +15 -0
- package/dist/analyze/memoryIoDetectors.js +68 -0
- package/dist/analyze/preferAstDetectors.d.ts +18 -0
- package/dist/analyze/preferAstDetectors.js +91 -0
- package/dist/analyze/projectScan.d.ts +19 -0
- package/dist/analyze/projectScan.js +115 -0
- package/dist/analyze/resourceAdmission.d.ts +33 -0
- package/dist/analyze/resourceAdmission.js +91 -0
- package/dist/analyze/resourceFindingCandidate.d.ts +61 -0
- package/dist/analyze/resourceFindingCandidate.js +75 -0
- package/dist/analyze/retryAmplificationDetector.d.ts +11 -0
- package/dist/analyze/retryAmplificationDetector.js +55 -0
- package/dist/analyze/schemas/resource-finding.schema.json +352 -0
- package/dist/analyze/serviceDiscovery.d.ts +66 -0
- package/dist/analyze/serviceDiscovery.js +210 -0
- package/dist/analyze/unboundedParallelismDetector.d.ts +11 -0
- package/dist/analyze/unboundedParallelismDetector.js +57 -0
- package/dist/analyze/validateResourceFinding.d.ts +19 -0
- package/dist/analyze/validateResourceFinding.js +46 -0
- package/dist/bullmq.d.ts +33 -0
- package/dist/bullmq.js +106 -0
- package/dist/cli/helpText.d.ts +8 -0
- package/dist/cli/helpText.js +99 -0
- package/dist/cli/netgreener.d.ts +12 -0
- package/dist/cli/netgreener.js +65 -0
- package/dist/collectorIpc.d.ts +28 -0
- package/dist/collectorIpc.js +189 -0
- package/dist/collectorMetadata.d.ts +15 -0
- package/dist/collectorMetadata.js +33 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +97 -0
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.js +1 -0
- package/dist/contract/observation-envelope.schema.json +331 -0
- package/dist/contract/observation-protobuf-view.schema.json +773 -0
- package/dist/contract/observation-v1.d.mts +42 -0
- package/dist/contract/observation-v1.mjs +555 -0
- package/dist/contract/observationIdentity.d.ts +11 -0
- package/dist/contract/observationIdentity.js +48 -0
- package/dist/contract/observationProjection.d.ts +3 -0
- package/dist/contract/observationProjection.js +29 -0
- package/dist/contract/validate.d.ts +20 -0
- package/dist/contract/validate.js +227 -0
- package/dist/dogfood/mp3WorkerGates.d.ts +54 -0
- package/dist/dogfood/mp3WorkerGates.js +86 -0
- package/dist/dogfood/retainDryRunArtifact.d.ts +45 -0
- package/dist/dogfood/retainDryRunArtifact.js +103 -0
- package/dist/dogfood/tenantDogfoodGates.d.ts +76 -0
- package/dist/dogfood/tenantDogfoodGates.js +206 -0
- package/dist/exporter.d.ts +56 -0
- package/dist/exporter.js +304 -0
- package/dist/express.d.ts +44 -0
- package/dist/express.js +118 -0
- package/dist/externalApiMeter.d.ts +72 -0
- package/dist/externalApiMeter.js +1167 -0
- package/dist/fastify.d.ts +53 -0
- package/dist/fastify.js +148 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +31 -0
- package/dist/n4/deploymentMatrix.d.ts +22 -0
- package/dist/n4/deploymentMatrix.js +57 -0
- package/dist/n4/missingnessInventory.d.ts +22 -0
- package/dist/n4/missingnessInventory.js +72 -0
- package/dist/nest.d.ts +42 -0
- package/dist/nest.js +87 -0
- package/dist/processResources.d.ts +28 -0
- package/dist/processResources.js +31 -0
- package/dist/processRuntime.d.ts +35 -0
- package/dist/processRuntime.js +96 -0
- package/dist/requestContext.d.ts +21 -0
- package/dist/requestContext.js +33 -0
- package/dist/runtime.d.ts +60 -0
- package/dist/runtime.js +289 -0
- package/dist/runtimeHealth.d.ts +35 -0
- package/dist/runtimeHealth.js +121 -0
- package/dist/serverlessHints.d.ts +26 -0
- package/dist/serverlessHints.js +52 -0
- package/dist/tenantContext.d.ts +78 -0
- package/dist/tenantContext.js +226 -0
- package/dist/tenantDefaults.d.ts +9 -0
- package/dist/tenantDefaults.js +9 -0
- package/dist/types.d.ts +131 -0
- package/dist/types.js +2 -0
- package/dist/uploader.d.ts +23 -0
- package/dist/uploader.js +52 -0
- package/dist/windowId.d.ts +2 -0
- package/dist/windowId.js +7 -0
- package/examples/analyze-manifest.mjs +30 -0
- package/examples/bullmq-live-smoke.mjs +178 -0
- package/examples/bullmq-mp3-worker-gate.mjs +237 -0
- package/examples/express-dry-run.mjs +72 -0
- package/examples/process-mp3-restart-gate.mjs +135 -0
- package/examples/retain-dry-run.mjs +110 -0
- package/examples/tenant-dogfood.mjs +269 -0
- package/fixtures/analyze-sample/architectureNoise.ts +13 -0
- package/fixtures/analyze-sample/cpuHotPaths.ts +27 -0
- package/fixtures/analyze-sample/fanOutClient.ts +15 -0
- package/fixtures/analyze-sample/inferenceHotPaths.ts +22 -0
- package/fixtures/analyze-sample/memoryIoHotPaths.ts +21 -0
- package/fixtures/analyze-sample/modelLoadHotPaths.ts +11 -0
- package/fixtures/analyze-sample/nestedLookup.ts +15 -0
- package/fixtures/analyze-sample/retryClient.ts +10 -0
- package/fixtures/analyze-sample/securitySmell.ts +8 -0
- package/fixtures/analyze-sample/server.ts +11 -0
- package/fixtures/analyze-sample/styleOnly.ts +7 -0
- package/fixtures/analyze-sample/worker.ts +4 -0
- package/fixtures/dual-view-projection.v1.json +53 -0
- package/fixtures/identity-preimages.v1.json +42 -0
- package/fixtures/mp2_shadow_digest_golden.v1.json +47 -0
- package/fixtures/projection-batch-document.v1.json +170 -0
- package/fixtures/projection-batch.v1.json +9 -0
- package/fixtures/python_reference_shapes.md +24 -0
- package/fixtures/session_metadata_runtime_v0.json +120 -0
- package/fixtures/session_metadata_with_tenant.json +85 -0
- package/package.json +137 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type NetGreenerRuntime, type NetGreenerRuntimeOptions } from './runtime.js';
|
|
2
|
+
/** Minimal Fastify-compatible request shape (avoid hard dependency). */
|
|
3
|
+
export type FastifyRequestLike = {
|
|
4
|
+
method?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Fastify 3/4 route pattern when routing has matched. */
|
|
7
|
+
routerPath?: string;
|
|
8
|
+
/** Fastify 4.16+ / 5 route config url (template). */
|
|
9
|
+
routeOptions?: {
|
|
10
|
+
url?: string;
|
|
11
|
+
};
|
|
12
|
+
headers?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
export type FastifyReplyLike = {
|
|
15
|
+
statusCode?: number;
|
|
16
|
+
};
|
|
17
|
+
export type FastifyDone = (err?: Error) => void;
|
|
18
|
+
export type FastifyLike = {
|
|
19
|
+
addHook: (name: 'onRequest' | 'onResponse' | 'onError' | string, handler: (...args: unknown[]) => unknown) => unknown;
|
|
20
|
+
register?: (plugin: unknown, opts?: unknown) => Promise<unknown> | unknown;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Bounded template for service_unit attribution.
|
|
24
|
+
* Never fall back to raw customer paths, query strings, or url.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fastifyRouteTemplate(req: FastifyRequestLike): string;
|
|
27
|
+
/**
|
|
28
|
+
* Bind tenant on the Fastify request-scoped store (and ALS when active).
|
|
29
|
+
* Prefer this after auth in Fastify handlers — ``setTenantContext`` alone may
|
|
30
|
+
* not update the store captured at ``onRequest`` when ALS does not span the route.
|
|
31
|
+
*/
|
|
32
|
+
export declare function bindFastifyTenant(request: object, ctx: import('./tenantContext.js').TenantContext): void;
|
|
33
|
+
/**
|
|
34
|
+
* Fastify plugin: record per-route latency/status into NetGreenerRuntime.
|
|
35
|
+
*
|
|
36
|
+
* Register with ``await fastify.register(netgreenerFastifyPlugin())`` (or
|
|
37
|
+
* ``fastify.register(netgreenerFastifyPlugin({ config }))``).
|
|
38
|
+
*
|
|
39
|
+
* The plugin breaks Fastify encapsulation (skip-override) so hooks apply to
|
|
40
|
+
* routes registered on the same instance after ``register``, matching Express
|
|
41
|
+
* middleware scope.
|
|
42
|
+
*
|
|
43
|
+
* Enable with NETGREENER_SERVICE_RUNTIME=1 and credentials (see README).
|
|
44
|
+
* Installs outbound ``fetch`` metering when runtime is enabled (via getRuntime.start).
|
|
45
|
+
*
|
|
46
|
+
* Route identity prefers ``routeOptions.url`` / ``routerPath`` at response time.
|
|
47
|
+
* Raw ``url`` is never a metering fallback.
|
|
48
|
+
*
|
|
49
|
+
* Tenant/attribution use ``enterWith`` so ALS spans Fastify's hook → route gap
|
|
50
|
+
* (unlike Express, where ``next()`` stays inside ``run()``).
|
|
51
|
+
*/
|
|
52
|
+
export declare function netgreenerFastifyPlugin(opts?: NetGreenerRuntimeOptions): (fastify: FastifyLike) => Promise<void>;
|
|
53
|
+
export declare function getFastifyRuntime(): NetGreenerRuntime;
|
package/dist/fastify.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { getRuntime, } from './runtime.js';
|
|
2
|
+
import { enterTenantStore, resolveTenantFromHttpRequest, runWithTenantContext, setTenantContext, tenantConfigFromRuntime, } from './tenantContext.js';
|
|
3
|
+
import { enterRequestAttribution } from './requestContext.js';
|
|
4
|
+
import { UNKNOWN_ROUTE_TEMPLATE } from './express.js';
|
|
5
|
+
import { beginProcessResourceSample, endProcessResourceSample, } from './processResources.js';
|
|
6
|
+
const MAX_ROUTE_TEMPLATE_LEN = 512;
|
|
7
|
+
const REQ_STATE = Symbol.for('netgreener.fastify.requestState');
|
|
8
|
+
/**
|
|
9
|
+
* Bounded template for service_unit attribution.
|
|
10
|
+
* Never fall back to raw customer paths, query strings, or url.
|
|
11
|
+
*/
|
|
12
|
+
export function fastifyRouteTemplate(req) {
|
|
13
|
+
const candidates = [req.routeOptions?.url, req.routerPath];
|
|
14
|
+
for (const route of candidates) {
|
|
15
|
+
if (typeof route !== 'string')
|
|
16
|
+
continue;
|
|
17
|
+
const template = route.trim();
|
|
18
|
+
if (!template ||
|
|
19
|
+
template.length > MAX_ROUTE_TEMPLATE_LEN ||
|
|
20
|
+
template.includes('?') ||
|
|
21
|
+
template.includes('#')) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
return template;
|
|
25
|
+
}
|
|
26
|
+
return UNKNOWN_ROUTE_TEMPLATE;
|
|
27
|
+
}
|
|
28
|
+
function httpServiceUnit(method, req) {
|
|
29
|
+
return `${method} ${fastifyRouteTemplate(req)}`;
|
|
30
|
+
}
|
|
31
|
+
function getRequestState(req) {
|
|
32
|
+
return req[REQ_STATE];
|
|
33
|
+
}
|
|
34
|
+
function setRequestState(req, state) {
|
|
35
|
+
;
|
|
36
|
+
req[REQ_STATE] = state;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Bind tenant on the Fastify request-scoped store (and ALS when active).
|
|
40
|
+
* Prefer this after auth in Fastify handlers — ``setTenantContext`` alone may
|
|
41
|
+
* not update the store captured at ``onRequest`` when ALS does not span the route.
|
|
42
|
+
*/
|
|
43
|
+
export function bindFastifyTenant(request, ctx) {
|
|
44
|
+
const state = getRequestState(request);
|
|
45
|
+
if (state)
|
|
46
|
+
state.tenantStore.current = ctx;
|
|
47
|
+
setTenantContext(ctx);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Fastify plugin: record per-route latency/status into NetGreenerRuntime.
|
|
51
|
+
*
|
|
52
|
+
* Register with ``await fastify.register(netgreenerFastifyPlugin())`` (or
|
|
53
|
+
* ``fastify.register(netgreenerFastifyPlugin({ config }))``).
|
|
54
|
+
*
|
|
55
|
+
* The plugin breaks Fastify encapsulation (skip-override) so hooks apply to
|
|
56
|
+
* routes registered on the same instance after ``register``, matching Express
|
|
57
|
+
* middleware scope.
|
|
58
|
+
*
|
|
59
|
+
* Enable with NETGREENER_SERVICE_RUNTIME=1 and credentials (see README).
|
|
60
|
+
* Installs outbound ``fetch`` metering when runtime is enabled (via getRuntime.start).
|
|
61
|
+
*
|
|
62
|
+
* Route identity prefers ``routeOptions.url`` / ``routerPath`` at response time.
|
|
63
|
+
* Raw ``url`` is never a metering fallback.
|
|
64
|
+
*
|
|
65
|
+
* Tenant/attribution use ``enterWith`` so ALS spans Fastify's hook → route gap
|
|
66
|
+
* (unlike Express, where ``next()`` stays inside ``run()``).
|
|
67
|
+
*/
|
|
68
|
+
export function netgreenerFastifyPlugin(opts = {}) {
|
|
69
|
+
const runtime = getRuntime({
|
|
70
|
+
collector: 'fastify_middleware',
|
|
71
|
+
framework: 'fastify',
|
|
72
|
+
...opts,
|
|
73
|
+
});
|
|
74
|
+
runtime.start();
|
|
75
|
+
const onSignal = () => {
|
|
76
|
+
void runtime.shutdown();
|
|
77
|
+
};
|
|
78
|
+
process.once('beforeExit', onSignal);
|
|
79
|
+
process.once('SIGINT', onSignal);
|
|
80
|
+
process.once('SIGTERM', onSignal);
|
|
81
|
+
const tenantConfig = tenantConfigFromRuntime(runtime.config);
|
|
82
|
+
const plugin = async function netgreenerFastify(fastify) {
|
|
83
|
+
if (!runtime.enabled)
|
|
84
|
+
return;
|
|
85
|
+
fastify.addHook('onRequest', function netgreenerOnRequest(request, _reply, done) {
|
|
86
|
+
const req = request;
|
|
87
|
+
const entryTenant = resolveTenantFromHttpRequest(req, tenantConfig);
|
|
88
|
+
const method = (req.method || 'GET').toUpperCase();
|
|
89
|
+
const resolveServiceUnit = () => httpServiceUnit(method, req);
|
|
90
|
+
const tenantStore = enterTenantStore(entryTenant);
|
|
91
|
+
enterRequestAttribution({
|
|
92
|
+
resolveServiceUnit,
|
|
93
|
+
serviceUnit: resolveServiceUnit(),
|
|
94
|
+
});
|
|
95
|
+
setRequestState(req, {
|
|
96
|
+
tenantStore,
|
|
97
|
+
method,
|
|
98
|
+
started: process.hrtime.bigint(),
|
|
99
|
+
resourceMark: beginProcessResourceSample(),
|
|
100
|
+
recorded: false,
|
|
101
|
+
});
|
|
102
|
+
if (typeof done === 'function') {
|
|
103
|
+
;
|
|
104
|
+
done();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const finish = (request, reply, done) => {
|
|
108
|
+
const req = request;
|
|
109
|
+
const state = getRequestState(req);
|
|
110
|
+
if (!state || state.recorded) {
|
|
111
|
+
if (typeof done === 'function')
|
|
112
|
+
done();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
state.recorded = true;
|
|
116
|
+
const durationMs = Number(process.hrtime.bigint() - state.started) / 1e6;
|
|
117
|
+
const resources = endProcessResourceSample(state.resourceMark);
|
|
118
|
+
const path = fastifyRouteTemplate(req);
|
|
119
|
+
const statusCode = typeof reply?.statusCode === 'number'
|
|
120
|
+
? reply.statusCode
|
|
121
|
+
: 200;
|
|
122
|
+
runWithTenantContext(state.tenantStore.current, () => {
|
|
123
|
+
runtime.recordHttp({
|
|
124
|
+
method: state.method,
|
|
125
|
+
path,
|
|
126
|
+
statusCode,
|
|
127
|
+
durationMs,
|
|
128
|
+
cpuTimeMs: resources.cpuTimeMs,
|
|
129
|
+
peakRssKb: resources.rssKb,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
if (typeof done === 'function')
|
|
133
|
+
done();
|
|
134
|
+
};
|
|
135
|
+
fastify.addHook('onResponse', function netgreenerOnResponse(request, reply, done) {
|
|
136
|
+
finish(request, reply, done);
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
// Break Fastify encapsulation so root `register(plugin)` meters sibling routes
|
|
140
|
+
// (same intent as `fastify-plugin` without a hard dependency).
|
|
141
|
+
const pluginMeta = plugin;
|
|
142
|
+
pluginMeta[Symbol.for('skip-override')] = true;
|
|
143
|
+
pluginMeta[Symbol.for('fastify.display-name')] = 'netgreener-fastify';
|
|
144
|
+
return plugin;
|
|
145
|
+
}
|
|
146
|
+
export function getFastifyRuntime() {
|
|
147
|
+
return getRuntime({ collector: 'fastify_middleware', framework: 'fastify' });
|
|
148
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @netgreener/runtime — Node emitter for NetGreener.
|
|
3
|
+
*
|
|
4
|
+
* N0: contracts · N1: Express/Fastify/Nest-via-adapter + RunSession flush
|
|
5
|
+
* N2: outbound fetch meter · N3: BullMQ worker + process/cron runtime
|
|
6
|
+
* Does not modify Python CLI/runtime capability.
|
|
7
|
+
*/
|
|
8
|
+
export type { AllocationReconciliationV0, EvidenceGrades, ExternalApiModelBucket, ExternalApiProvider, ExternalApiTenantBucket, ExternalApiV0, MeasurementProvenance, RunContext, RuntimeSessionMetadata, ServiceRuntimeTenantBucket, ServiceRuntimeUnit, ServiceRuntimeV0, TenantId, } from './types.js';
|
|
9
|
+
export { validateExternalApiV0, validateMeasurementProvenance, validateRuntimeSessionMetadata, validateServiceRuntimeV0, type ContractIssue, type ContractResult, } from './contract/index.js';
|
|
10
|
+
export { loadRuntimeConfig, configReady, type RuntimeConfig } from './config.js';
|
|
11
|
+
export { COMPATIBLE_COLLECTOR_METADATA, COLLECTOR_METADATA_NOTES, isCompatibleCollectorMetadata, type CompatibleCollectorMetadata, } from './collectorMetadata.js';
|
|
12
|
+
export { validateObservationEnvelope, classifyObservationDelivery, projectSemanticEnvelopeToProtobufView, validateObservationProtobufView, validateObservationDualView, } from './contract/observation-v1.mjs';
|
|
13
|
+
export { ServiceRuntimeAggregator } from './aggregator.js';
|
|
14
|
+
export { beginProcessResourceSample, endProcessResourceSample, currentRssKb, type ProcessResourceMark, type ProcessResourceSample, } from './processResources.js';
|
|
15
|
+
export { buildRuntimeHealthV0, mergeHealthIntoMetadata, markHooksActive, type RuntimeHealthV0, type RuntimeHealthStatus, } from './runtimeHealth.js';
|
|
16
|
+
export { newRuntimeWindowId } from './windowId.js';
|
|
17
|
+
export { uploadRunSession, type RunSessionCreatePayload, type UploadResult, } from './uploader.js';
|
|
18
|
+
export { resolveObservationExporter, exportModeFromEnv, DirectRunSessionExporter, ThinRunSessionExporter, CollectorRunSessionExporter, type ExportMode, type ExportResult, type ObservationExporter, type ObservationBatchDocument, } from './exporter.js';
|
|
19
|
+
export { detectServerlessPlatformSignals, flushModeRequestsThin, type ServerlessPlatformHint, type ServerlessPlatformSignal, } from './serverlessHints.js';
|
|
20
|
+
export { sendCollectorEvent, encodeCollectorFrame, normalizeCollectorEndpoint, type CollectorIpcAck, } from './collectorIpc.js';
|
|
21
|
+
export { NetGreenerRuntime, getRuntime, _resetRuntimeForTests, type FlushKind, type NetGreenerRuntimeOptions, } from './runtime.js';
|
|
22
|
+
export { netgreenerExpressMiddleware, getExpressRuntime, type ExpressRequest, type ExpressResponse, type ExpressNext, } from './express.js';
|
|
23
|
+
export { netgreenerFastifyPlugin, getFastifyRuntime, fastifyRouteTemplate, bindFastifyTenant, type FastifyLike, type FastifyRequestLike, type FastifyReplyLike, } from './fastify.js';
|
|
24
|
+
export { applyNetGreenerNestHooks, detectNestHttpAdapter, getNestRuntime, netgreenerNestExpressMiddleware, netgreenerNestFastifyPlugin, type NestApplicationLike, type NestHttpAdapterKind, } from './nest.js';
|
|
25
|
+
export { attachWindowTenantToRunContext, getTenantContext, loadTenantConfig, normalizeTenantId, resolveTenantFromHttpRequest, resolveTenantFromTaskData, runWithTenantContext, setTenantContext, tenantConfigFromRuntime, type TenantConfig, type TenantContext, } from './tenantContext.js';
|
|
26
|
+
export { ExternalApiAggregator, buildExternalV0, clearExternalAggregator, getExternalAggregator, installOutboundInstrumentation, providerForHost, usageFromClientBody, _resetOutboundInstrumentationForTests, } from './externalApiMeter.js';
|
|
27
|
+
export { attributionServiceUnit, getRequestAttribution, runWithRequestAttribution, } from './requestContext.js';
|
|
28
|
+
export { getBullMqRuntime, netgreenerBullMqProcessor, runBullMqJobWithNetGreener, type BullMqJobLike, type BullMqProcessorOptions, } from './bullmq.js';
|
|
29
|
+
export { bindProcessTenant, flushProcessRuntime, runWithProcessTenant, startProcessRuntime, type ProcessRuntimeOptions, } from './processRuntime.js';
|
|
30
|
+
export { buildServiceManifest, detectFrameworks, extractServiceUnits, isServiceProject, buildServiceManifestFromPaths, listCandidateSourceFiles, NON_RESOURCE_CATEGORIES, RESOURCE_DOMAINS, admitAnalyzeCandidate, isNonResourceCategory, languageForPath, toResourceFindingCandidate, detectRetryAmplificationHits, detectApiOverconsumptionHits, detectUnboundedParallelismHits, detectPollingIdleHits, detectProcessChurnHits, detectRepeatedWorkHits, detectPerItemInsteadOfBatchHits, detectModelLoadInLoopHits, detectFullDatasetInMemoryHits, detectRepeatedFileReadHits, detectAllHitsAst, detectHotPathHitsAst, detectApiOverconsumptionHitsAst, detectPollingIdleHitsAst, detectProcessChurnHitsAst, detectRepeatedFileReadHitsAst, detectRepeatedWorkHitsAst, detectRetryAmplificationHitsAst, detectUnboundedParallelismHitsAst, detectFullDatasetInMemoryHitsAst, detectPerItemInsteadOfBatchHitsAst, detectModelLoadInLoopHitsAst, collectPreferAstHits, detectApiOverconsumptionHitsPreferAst, detectPollingIdleHitsPreferAst, detectProcessChurnHitsPreferAst, detectRepeatedFileReadHitsPreferAst, detectRepeatedWorkHitsPreferAst, detectRetryAmplificationHitsPreferAst, detectUnboundedParallelismHitsPreferAst, detectFullDatasetInMemoryHitsPreferAst, detectPerItemInsteadOfBatchHitsPreferAst, detectModelLoadInLoopHitsPreferAst, isTypescriptAnalyzeAvailable, tryLoadTypescript, allResourceFindingsValid, filterValidResourceFindings, validateResourceFindingCandidate, countGateFindings, findingsExceedFailThreshold, formatAnalyzeGateFailureMessage, resolveAnalyzeGateLimit, buildAnalyzeGateSummary, buildResourceAnalyzeResultFromPaths, buildResourceFindingsFromSources, type ServiceEndpointUnit, type ServiceFileExtraction, type ServiceManifestV0, type ServiceTaskUnit, type ListCandidateSourceFilesOptions, type AdmissionDecision, type NonResourceCategory, type ResourceDomain, type ResourceCandidateHit, type ResourceFindingCandidateV1, type ResourcePrimaryDomain, type BuildResourceFindingsOptions, type ResourceAnalyzeResultV0, type ResourceFindingValidation, type ResourceFindingValidationError, type AnalyzeGateLimit, type AnalyzeGateSummaryV0, type AstHitBundle, } from './analyze/index.js';
|
|
31
|
+
export { assertNoFalseCgroupClaim, CPU_SOURCE_DURATION_PROXY, CPU_SOURCE_PROCESS, MEMORY_SOURCE_PROCESS, missingnessByStatus, NODE_DIRECT_MISSINGNESS_INVENTORY, type MeasurementStatus, type MissingnessEntry, } from './n4/missingnessInventory.js';
|
|
32
|
+
export { NODE_DEPLOYMENT_MATRIX, claimedDeployModes, assertProductionScopeIsExplicit, type DeployModeEntry, type DeployModeStatus, } from './n4/deploymentMatrix.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @netgreener/runtime — Node emitter for NetGreener.
|
|
3
|
+
*
|
|
4
|
+
* N0: contracts · N1: Express/Fastify/Nest-via-adapter + RunSession flush
|
|
5
|
+
* N2: outbound fetch meter · N3: BullMQ worker + process/cron runtime
|
|
6
|
+
* Does not modify Python CLI/runtime capability.
|
|
7
|
+
*/
|
|
8
|
+
export { validateExternalApiV0, validateMeasurementProvenance, validateRuntimeSessionMetadata, validateServiceRuntimeV0, } from './contract/index.js';
|
|
9
|
+
export { loadRuntimeConfig, configReady } from './config.js';
|
|
10
|
+
export { COMPATIBLE_COLLECTOR_METADATA, COLLECTOR_METADATA_NOTES, isCompatibleCollectorMetadata, } from './collectorMetadata.js';
|
|
11
|
+
export { validateObservationEnvelope, classifyObservationDelivery, projectSemanticEnvelopeToProtobufView, validateObservationProtobufView, validateObservationDualView, } from './contract/observation-v1.mjs';
|
|
12
|
+
export { ServiceRuntimeAggregator } from './aggregator.js';
|
|
13
|
+
export { beginProcessResourceSample, endProcessResourceSample, currentRssKb, } from './processResources.js';
|
|
14
|
+
export { buildRuntimeHealthV0, mergeHealthIntoMetadata, markHooksActive, } from './runtimeHealth.js';
|
|
15
|
+
export { newRuntimeWindowId } from './windowId.js';
|
|
16
|
+
export { uploadRunSession, } from './uploader.js';
|
|
17
|
+
export { resolveObservationExporter, exportModeFromEnv, DirectRunSessionExporter, ThinRunSessionExporter, CollectorRunSessionExporter, } from './exporter.js';
|
|
18
|
+
export { detectServerlessPlatformSignals, flushModeRequestsThin, } from './serverlessHints.js';
|
|
19
|
+
export { sendCollectorEvent, encodeCollectorFrame, normalizeCollectorEndpoint, } from './collectorIpc.js';
|
|
20
|
+
export { NetGreenerRuntime, getRuntime, _resetRuntimeForTests, } from './runtime.js';
|
|
21
|
+
export { netgreenerExpressMiddleware, getExpressRuntime, } from './express.js';
|
|
22
|
+
export { netgreenerFastifyPlugin, getFastifyRuntime, fastifyRouteTemplate, bindFastifyTenant, } from './fastify.js';
|
|
23
|
+
export { applyNetGreenerNestHooks, detectNestHttpAdapter, getNestRuntime, netgreenerNestExpressMiddleware, netgreenerNestFastifyPlugin, } from './nest.js';
|
|
24
|
+
export { attachWindowTenantToRunContext, getTenantContext, loadTenantConfig, normalizeTenantId, resolveTenantFromHttpRequest, resolveTenantFromTaskData, runWithTenantContext, setTenantContext, tenantConfigFromRuntime, } from './tenantContext.js';
|
|
25
|
+
export { ExternalApiAggregator, buildExternalV0, clearExternalAggregator, getExternalAggregator, installOutboundInstrumentation, providerForHost, usageFromClientBody, _resetOutboundInstrumentationForTests, } from './externalApiMeter.js';
|
|
26
|
+
export { attributionServiceUnit, getRequestAttribution, runWithRequestAttribution, } from './requestContext.js';
|
|
27
|
+
export { getBullMqRuntime, netgreenerBullMqProcessor, runBullMqJobWithNetGreener, } from './bullmq.js';
|
|
28
|
+
export { bindProcessTenant, flushProcessRuntime, runWithProcessTenant, startProcessRuntime, } from './processRuntime.js';
|
|
29
|
+
export { buildServiceManifest, detectFrameworks, extractServiceUnits, isServiceProject, buildServiceManifestFromPaths, listCandidateSourceFiles, NON_RESOURCE_CATEGORIES, RESOURCE_DOMAINS, admitAnalyzeCandidate, isNonResourceCategory, languageForPath, toResourceFindingCandidate, detectRetryAmplificationHits, detectApiOverconsumptionHits, detectUnboundedParallelismHits, detectPollingIdleHits, detectProcessChurnHits, detectRepeatedWorkHits, detectPerItemInsteadOfBatchHits, detectModelLoadInLoopHits, detectFullDatasetInMemoryHits, detectRepeatedFileReadHits, detectAllHitsAst, detectHotPathHitsAst, detectApiOverconsumptionHitsAst, detectPollingIdleHitsAst, detectProcessChurnHitsAst, detectRepeatedFileReadHitsAst, detectRepeatedWorkHitsAst, detectRetryAmplificationHitsAst, detectUnboundedParallelismHitsAst, detectFullDatasetInMemoryHitsAst, detectPerItemInsteadOfBatchHitsAst, detectModelLoadInLoopHitsAst, collectPreferAstHits, detectApiOverconsumptionHitsPreferAst, detectPollingIdleHitsPreferAst, detectProcessChurnHitsPreferAst, detectRepeatedFileReadHitsPreferAst, detectRepeatedWorkHitsPreferAst, detectRetryAmplificationHitsPreferAst, detectUnboundedParallelismHitsPreferAst, detectFullDatasetInMemoryHitsPreferAst, detectPerItemInsteadOfBatchHitsPreferAst, detectModelLoadInLoopHitsPreferAst, isTypescriptAnalyzeAvailable, tryLoadTypescript, allResourceFindingsValid, filterValidResourceFindings, validateResourceFindingCandidate, countGateFindings, findingsExceedFailThreshold, formatAnalyzeGateFailureMessage, resolveAnalyzeGateLimit, buildAnalyzeGateSummary, buildResourceAnalyzeResultFromPaths, buildResourceFindingsFromSources, } from './analyze/index.js';
|
|
30
|
+
export { assertNoFalseCgroupClaim, CPU_SOURCE_DURATION_PROXY, CPU_SOURCE_PROCESS, MEMORY_SOURCE_PROCESS, missingnessByStatus, NODE_DIRECT_MISSINGNESS_INVENTORY, } from './n4/missingnessInventory.js';
|
|
31
|
+
export { NODE_DEPLOYMENT_MATRIX, claimedDeployModes, assertProductionScopeIsExplicit, } from './n4/deploymentMatrix.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CAP-R6 deployment-mode matrix (Node Runtime production scope).
|
|
3
|
+
*
|
|
4
|
+
* Freezes what we **claim** vs **exercise** vs **exclude**. Does not invent
|
|
5
|
+
* Kubernetes/container live evidence. Does not flip export defaults.
|
|
6
|
+
*/
|
|
7
|
+
export type DeployModeStatus = 'claimed_and_exercised' | 'documented_degraded' | 'not_claimed' | 'open';
|
|
8
|
+
export type DeployModeEntry = {
|
|
9
|
+
check_id: string;
|
|
10
|
+
mode: string;
|
|
11
|
+
status: DeployModeStatus;
|
|
12
|
+
evidence: string;
|
|
13
|
+
notes: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Production claim scope for Node Runtime (2026-09-24):
|
|
17
|
+
* long-running process / VM-like host with default ``direct`` export.
|
|
18
|
+
* Container/K8s/CI/serverless are **not** silently included.
|
|
19
|
+
*/
|
|
20
|
+
export declare const NODE_DEPLOYMENT_MATRIX: readonly DeployModeEntry[];
|
|
21
|
+
export declare function claimedDeployModes(): DeployModeEntry[];
|
|
22
|
+
export declare function assertProductionScopeIsExplicit(): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CAP-R6 deployment-mode matrix (Node Runtime production scope).
|
|
3
|
+
*
|
|
4
|
+
* Freezes what we **claim** vs **exercise** vs **exclude**. Does not invent
|
|
5
|
+
* Kubernetes/container live evidence. Does not flip export defaults.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Production claim scope for Node Runtime (2026-09-24):
|
|
9
|
+
* long-running process / VM-like host with default ``direct`` export.
|
|
10
|
+
* Container/K8s/CI/serverless are **not** silently included.
|
|
11
|
+
*/
|
|
12
|
+
export const NODE_DEPLOYMENT_MATRIX = [
|
|
13
|
+
{
|
|
14
|
+
check_id: 'CHK-R6.vm-process',
|
|
15
|
+
mode: 'long_running_process',
|
|
16
|
+
status: 'claimed_and_exercised',
|
|
17
|
+
evidence: 'LIVE_ACCEPTANCE_EVIDENCE.md (HTTP dogfood, BullMQ 40654/55, process 40660/61)',
|
|
18
|
+
notes: 'Primary production claim surface for Node Runtime',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
check_id: 'CHK-R6.container',
|
|
22
|
+
mode: 'container',
|
|
23
|
+
status: 'not_claimed',
|
|
24
|
+
evidence: 'none retained as production gate',
|
|
25
|
+
notes: 'May work identically to process; do not claim until dedicated live gate',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
check_id: 'CHK-R6.kubernetes',
|
|
29
|
+
mode: 'kubernetes',
|
|
30
|
+
status: 'not_claimed',
|
|
31
|
+
evidence: 'none',
|
|
32
|
+
notes: 'Out of Node Runtime production claim until exercised',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
check_id: 'CHK-R6.ci-job',
|
|
36
|
+
mode: 'ci_job',
|
|
37
|
+
status: 'documented_degraded',
|
|
38
|
+
evidence: 'unit/integration in CI; not a customer Runtime deployment claim',
|
|
39
|
+
notes: 'CI proves library; not a production Runtime deploy mode',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
check_id: 'CHK-R6.serverless-degraded',
|
|
43
|
+
mode: 'serverless_thin',
|
|
44
|
+
status: 'documented_degraded',
|
|
45
|
+
evidence: 'serverlessHints unit tests; EXPORT_MODE=thin explicit only',
|
|
46
|
+
notes: 'Best-effort thin flush; no auto-cutover; not durable collector',
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
export function claimedDeployModes() {
|
|
50
|
+
return NODE_DEPLOYMENT_MATRIX.filter((e) => e.status === 'claimed_and_exercised');
|
|
51
|
+
}
|
|
52
|
+
export function assertProductionScopeIsExplicit() {
|
|
53
|
+
const claimed = claimedDeployModes();
|
|
54
|
+
if (claimed.length !== 1)
|
|
55
|
+
return false;
|
|
56
|
+
return claimed[0].mode === 'long_running_process';
|
|
57
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* N4 / N5 missingness inventory — Node Runtime (direct emitter).
|
|
3
|
+
*
|
|
4
|
+
* Documents what is measured vs proxy vs not measured. Does not invent
|
|
5
|
+
* cgroup/process-tree values or change the upload pipeline.
|
|
6
|
+
*/
|
|
7
|
+
import { CPU_SOURCE_PROCESS, MEMORY_SOURCE_PROCESS } from '../processResources.js';
|
|
8
|
+
export type MeasurementStatus = 'measured' | 'proxy' | 'derived_estimate' | 'not_measured' | 'absent';
|
|
9
|
+
export type MissingnessEntry = {
|
|
10
|
+
signal_id: string;
|
|
11
|
+
status: MeasurementStatus;
|
|
12
|
+
/** Provenance string when present on session_metadata (e.g. cpu_source). */
|
|
13
|
+
provenance?: string;
|
|
14
|
+
notes: string;
|
|
15
|
+
};
|
|
16
|
+
export { CPU_SOURCE_PROCESS, MEMORY_SOURCE_PROCESS };
|
|
17
|
+
/** Fallback when a sample omits measured CPU (tests / bare recordHttp). */
|
|
18
|
+
export declare const CPU_SOURCE_DURATION_PROXY = "request_duration_proxy";
|
|
19
|
+
/** Frozen inventory for `@netgreener/runtime` default direct path (2026-09-24). */
|
|
20
|
+
export declare const NODE_DIRECT_MISSINGNESS_INVENTORY: readonly MissingnessEntry[];
|
|
21
|
+
export declare function missingnessByStatus(status: MeasurementStatus): MissingnessEntry[];
|
|
22
|
+
export declare function assertNoFalseCgroupClaim(cpuSource: string | undefined): boolean;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* N4 / N5 missingness inventory — Node Runtime (direct emitter).
|
|
3
|
+
*
|
|
4
|
+
* Documents what is measured vs proxy vs not measured. Does not invent
|
|
5
|
+
* cgroup/process-tree values or change the upload pipeline.
|
|
6
|
+
*/
|
|
7
|
+
import { CPU_SOURCE_PROCESS, MEMORY_SOURCE_PROCESS, } from '../processResources.js';
|
|
8
|
+
export { CPU_SOURCE_PROCESS, MEMORY_SOURCE_PROCESS };
|
|
9
|
+
/** Fallback when a sample omits measured CPU (tests / bare recordHttp). */
|
|
10
|
+
export const CPU_SOURCE_DURATION_PROXY = 'request_duration_proxy';
|
|
11
|
+
/** Frozen inventory for `@netgreener/runtime` default direct path (2026-09-24). */
|
|
12
|
+
export const NODE_DIRECT_MISSINGNESS_INVENTORY = [
|
|
13
|
+
{
|
|
14
|
+
signal_id: 'service_unit.calls_errors_duration',
|
|
15
|
+
status: 'measured',
|
|
16
|
+
notes: 'Adapter samples from HTTP middleware / BullMQ / process helpers',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
signal_id: 'tenant.by_tenant',
|
|
20
|
+
status: 'measured',
|
|
21
|
+
notes: 'When tenant bind is configured; otherwise absent',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
signal_id: 'external_api_v0',
|
|
25
|
+
status: 'measured',
|
|
26
|
+
notes: 'When outbound instrumentation is installed',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
signal_id: 'unit.cpu_seconds_total',
|
|
30
|
+
status: 'measured',
|
|
31
|
+
provenance: CPU_SOURCE_PROCESS,
|
|
32
|
+
notes: 'process.cpuUsage delta on Express/Fastify/BullMQ/process hooks; duration proxy only if sample omits CPU',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
signal_id: 'unit.peak_rss_kb_max',
|
|
36
|
+
status: 'measured',
|
|
37
|
+
provenance: MEMORY_SOURCE_PROCESS,
|
|
38
|
+
notes: 'process.memoryUsage().rss at sample end (KiB)',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
signal_id: 'unit.energy_kwh_carbon_g',
|
|
42
|
+
status: 'derived_estimate',
|
|
43
|
+
notes: 'Emitter-side TDP util estimate (prefers measured CPU when present); cloud policy ownership open',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
signal_id: 'collector.process_tree_cgroup',
|
|
47
|
+
status: 'not_measured',
|
|
48
|
+
notes: 'Requires shared MP2 collector sampler + Node launcher — still open',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
signal_id: 'runtime_health_v0',
|
|
52
|
+
status: 'measured',
|
|
53
|
+
notes: 'Export attempt/ok/fail/in-flight-skip counters + hooks_active on each flush (CAP-R5 honesty)',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
signal_id: 'host_gpu_energy_meters',
|
|
57
|
+
status: 'not_measured',
|
|
58
|
+
notes: 'Out of thin-adapter scope',
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
export function missingnessByStatus(status) {
|
|
62
|
+
return NODE_DIRECT_MISSINGNESS_INVENTORY.filter((e) => e.status === status);
|
|
63
|
+
}
|
|
64
|
+
export function assertNoFalseCgroupClaim(cpuSource) {
|
|
65
|
+
if (!cpuSource)
|
|
66
|
+
return true;
|
|
67
|
+
const lowered = cpuSource.toLowerCase();
|
|
68
|
+
if (lowered.includes('cgroup') || lowered.includes('process_tree')) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
package/dist/nest.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NestJS via underlying HTTP adapter (experimental N1 v0).
|
|
3
|
+
*
|
|
4
|
+
* Nest does not need a separate interception stack: apply Express middleware or
|
|
5
|
+
* the Fastify plugin on the adapter Nest already uses. No hard ``@nestjs/*``
|
|
6
|
+
* dependency.
|
|
7
|
+
*/
|
|
8
|
+
import { type ExpressNext, type ExpressRequest, type ExpressResponse } from './express.js';
|
|
9
|
+
import { type FastifyLike } from './fastify.js';
|
|
10
|
+
import { type NetGreenerRuntime, type NetGreenerRuntimeOptions } from './runtime.js';
|
|
11
|
+
/** Duck-typed Nest application surface used for adapter detection. */
|
|
12
|
+
export type NestApplicationLike = {
|
|
13
|
+
getHttpAdapter?: () => {
|
|
14
|
+
getType?: () => string;
|
|
15
|
+
getInstance?: () => FastifyLike | unknown;
|
|
16
|
+
};
|
|
17
|
+
use?: (middleware: unknown) => unknown;
|
|
18
|
+
};
|
|
19
|
+
export type NestHttpAdapterKind = 'express' | 'fastify' | 'unknown';
|
|
20
|
+
/**
|
|
21
|
+
* Detect whether a Nest app is backed by Express or Fastify.
|
|
22
|
+
* Uses ``HttpAdapter.getType()`` when present (Nest public API).
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectNestHttpAdapter(app: NestApplicationLike): NestHttpAdapterKind;
|
|
25
|
+
/**
|
|
26
|
+
* Express-adapter Nest apps: ``app.use(netgreenerNestExpressMiddleware())``
|
|
27
|
+
* after ``NestFactory.create`` (same place you add other Express middleware).
|
|
28
|
+
*/
|
|
29
|
+
export declare function netgreenerNestExpressMiddleware(opts?: NetGreenerRuntimeOptions): (req: ExpressRequest, res: ExpressResponse, next: ExpressNext) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Fastify-adapter Nest apps: register on the underlying Fastify instance::
|
|
32
|
+
*
|
|
33
|
+
* const instance = app.getHttpAdapter().getInstance()
|
|
34
|
+
* await instance.register(netgreenerNestFastifyPlugin())
|
|
35
|
+
*/
|
|
36
|
+
export declare function netgreenerNestFastifyPlugin(opts?: NetGreenerRuntimeOptions): (fastify: FastifyLike) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Convenience: apply the matching hook for the Nest HTTP adapter in use.
|
|
39
|
+
* Returns the detected adapter kind. Fastify path is async (``register``).
|
|
40
|
+
*/
|
|
41
|
+
export declare function applyNetGreenerNestHooks(app: NestApplicationLike, opts?: NetGreenerRuntimeOptions): Promise<NestHttpAdapterKind>;
|
|
42
|
+
export declare function getNestRuntime(kind?: 'express' | 'fastify'): NetGreenerRuntime;
|
package/dist/nest.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NestJS via underlying HTTP adapter (experimental N1 v0).
|
|
3
|
+
*
|
|
4
|
+
* Nest does not need a separate interception stack: apply Express middleware or
|
|
5
|
+
* the Fastify plugin on the adapter Nest already uses. No hard ``@nestjs/*``
|
|
6
|
+
* dependency.
|
|
7
|
+
*/
|
|
8
|
+
import { netgreenerExpressMiddleware, } from './express.js';
|
|
9
|
+
import { netgreenerFastifyPlugin } from './fastify.js';
|
|
10
|
+
import { getRuntime, } from './runtime.js';
|
|
11
|
+
/**
|
|
12
|
+
* Detect whether a Nest app is backed by Express or Fastify.
|
|
13
|
+
* Uses ``HttpAdapter.getType()`` when present (Nest public API).
|
|
14
|
+
*/
|
|
15
|
+
export function detectNestHttpAdapter(app) {
|
|
16
|
+
try {
|
|
17
|
+
const adapter = app.getHttpAdapter?.();
|
|
18
|
+
const raw = (adapter?.getType?.() || '').toLowerCase().trim();
|
|
19
|
+
if (raw === 'express')
|
|
20
|
+
return 'express';
|
|
21
|
+
if (raw === 'fastify')
|
|
22
|
+
return 'fastify';
|
|
23
|
+
const name = adapter?.constructor?.name || '';
|
|
24
|
+
const lower = name.toLowerCase();
|
|
25
|
+
if (lower.includes('fastify'))
|
|
26
|
+
return 'fastify';
|
|
27
|
+
if (lower.includes('express'))
|
|
28
|
+
return 'express';
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
/* ignore */
|
|
32
|
+
}
|
|
33
|
+
return 'unknown';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Express-adapter Nest apps: ``app.use(netgreenerNestExpressMiddleware())``
|
|
37
|
+
* after ``NestFactory.create`` (same place you add other Express middleware).
|
|
38
|
+
*/
|
|
39
|
+
export function netgreenerNestExpressMiddleware(opts = {}) {
|
|
40
|
+
return netgreenerExpressMiddleware({
|
|
41
|
+
...opts,
|
|
42
|
+
collector: opts.collector ?? 'nest_express_middleware',
|
|
43
|
+
framework: opts.framework ?? 'nest',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Fastify-adapter Nest apps: register on the underlying Fastify instance::
|
|
48
|
+
*
|
|
49
|
+
* const instance = app.getHttpAdapter().getInstance()
|
|
50
|
+
* await instance.register(netgreenerNestFastifyPlugin())
|
|
51
|
+
*/
|
|
52
|
+
export function netgreenerNestFastifyPlugin(opts = {}) {
|
|
53
|
+
return netgreenerFastifyPlugin({
|
|
54
|
+
...opts,
|
|
55
|
+
collector: opts.collector ?? 'nest_fastify_middleware',
|
|
56
|
+
framework: opts.framework ?? 'nest',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Convenience: apply the matching hook for the Nest HTTP adapter in use.
|
|
61
|
+
* Returns the detected adapter kind. Fastify path is async (``register``).
|
|
62
|
+
*/
|
|
63
|
+
export async function applyNetGreenerNestHooks(app, opts = {}) {
|
|
64
|
+
const kind = detectNestHttpAdapter(app);
|
|
65
|
+
if (kind === 'express') {
|
|
66
|
+
if (typeof app.use !== 'function') {
|
|
67
|
+
throw new Error('Nest Express adapter expected app.use(...)');
|
|
68
|
+
}
|
|
69
|
+
app.use(netgreenerNestExpressMiddleware(opts));
|
|
70
|
+
return kind;
|
|
71
|
+
}
|
|
72
|
+
if (kind === 'fastify') {
|
|
73
|
+
const instance = app.getHttpAdapter?.()?.getInstance?.();
|
|
74
|
+
if (!instance || typeof instance.register !== 'function') {
|
|
75
|
+
throw new Error('Nest Fastify adapter expected getInstance().register(...)');
|
|
76
|
+
}
|
|
77
|
+
await instance.register(netgreenerNestFastifyPlugin(opts));
|
|
78
|
+
return kind;
|
|
79
|
+
}
|
|
80
|
+
throw new Error('Unknown Nest HTTP adapter — use netgreenerNestExpressMiddleware or netgreenerNestFastifyPlugin explicitly');
|
|
81
|
+
}
|
|
82
|
+
export function getNestRuntime(kind = 'express') {
|
|
83
|
+
if (kind === 'fastify') {
|
|
84
|
+
return getRuntime({ collector: 'nest_fastify_middleware', framework: 'nest' });
|
|
85
|
+
}
|
|
86
|
+
return getRuntime({ collector: 'nest_express_middleware', framework: 'nest' });
|
|
87
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-level CPU / RSS sampling for Node Runtime (CAP-R4 / N5).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Python ASGI middleware: ``time.process_time()`` + ``psutil`` RSS.
|
|
5
|
+
* Uses Node ``process.cpuUsage`` / ``process.memoryUsage().rss`` only —
|
|
6
|
+
* never invents cgroup or process-tree values.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CPU_SOURCE_PROCESS = "node_process_cpu_usage";
|
|
9
|
+
export declare const MEMORY_SOURCE_PROCESS = "node_process_memory_rss";
|
|
10
|
+
export type ProcessResourceMark = {
|
|
11
|
+
/** ``process.cpuUsage()`` snapshot at begin. */
|
|
12
|
+
cpuStart: NodeJS.CpuUsage;
|
|
13
|
+
};
|
|
14
|
+
export type ProcessResourceSample = {
|
|
15
|
+
/** Process CPU time attributed to the interval (milliseconds). */
|
|
16
|
+
cpuTimeMs: number;
|
|
17
|
+
/** Current RSS in KiB at end of interval. */
|
|
18
|
+
rssKb: number;
|
|
19
|
+
};
|
|
20
|
+
/** Start a CPU interval (call at request/task entry). */
|
|
21
|
+
export declare function beginProcessResourceSample(): ProcessResourceMark;
|
|
22
|
+
/**
|
|
23
|
+
* End a CPU interval and read current RSS.
|
|
24
|
+
* ``cpuUsage(previous)`` returns user+system microseconds since ``previous``.
|
|
25
|
+
*/
|
|
26
|
+
export declare function endProcessResourceSample(mark: ProcessResourceMark): ProcessResourceSample;
|
|
27
|
+
/** Point-in-time RSS (KiB) when no interval mark is available. */
|
|
28
|
+
export declare function currentRssKb(): number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-level CPU / RSS sampling for Node Runtime (CAP-R4 / N5).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Python ASGI middleware: ``time.process_time()`` + ``psutil`` RSS.
|
|
5
|
+
* Uses Node ``process.cpuUsage`` / ``process.memoryUsage().rss`` only —
|
|
6
|
+
* never invents cgroup or process-tree values.
|
|
7
|
+
*/
|
|
8
|
+
export const CPU_SOURCE_PROCESS = 'node_process_cpu_usage';
|
|
9
|
+
export const MEMORY_SOURCE_PROCESS = 'node_process_memory_rss';
|
|
10
|
+
/** Start a CPU interval (call at request/task entry). */
|
|
11
|
+
export function beginProcessResourceSample() {
|
|
12
|
+
return { cpuStart: process.cpuUsage() };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* End a CPU interval and read current RSS.
|
|
16
|
+
* ``cpuUsage(previous)`` returns user+system microseconds since ``previous``.
|
|
17
|
+
*/
|
|
18
|
+
export function endProcessResourceSample(mark) {
|
|
19
|
+
const delta = process.cpuUsage(mark.cpuStart);
|
|
20
|
+
const cpuTimeMs = (delta.user + delta.system) / 1000;
|
|
21
|
+
const rssKb = Math.round(process.memoryUsage().rss / 1024);
|
|
22
|
+
return {
|
|
23
|
+
cpuTimeMs: Number.isFinite(cpuTimeMs) && cpuTimeMs >= 0 ? cpuTimeMs : 0,
|
|
24
|
+
rssKb: Number.isFinite(rssKb) && rssKb >= 0 ? rssKb : 0,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Point-in-time RSS (KiB) when no interval mark is available. */
|
|
28
|
+
export function currentRssKb() {
|
|
29
|
+
const rssKb = Math.round(process.memoryUsage().rss / 1024);
|
|
30
|
+
return Number.isFinite(rssKb) && rssKb >= 0 ? rssKb : 0;
|
|
31
|
+
}
|