@autter/runtime-node 1.3.2 → 1.3.3

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
@@ -1,5 +1,11 @@
1
1
  # @autter/runtime-node
2
2
 
3
+ The server tracker exports per-instance RSS, heap, memory limit, and GC
4
+ metrics by default. See [memory pressure incidents](../../docs/MEMORY-PRESSURE.md)
5
+ for ECS/Kubernetes OOM event forwarding and optional heap profiles. Set
6
+ `memoryMetrics: false` only if another exporter already emits equivalent
7
+ process metrics.
8
+
3
9
  Autter Runtime for Node.js — two halves in one package:
4
10
 
5
11
  1. **Same-origin browser relay** for `@autter/runtime-browser`
package/dist/index.cjs CHANGED
@@ -255,7 +255,7 @@ function createBrowserRelayHandler(opts) {
255
255
  // src/server.ts
256
256
  var import_node_http = require("http");
257
257
  var import_node_crypto = require("crypto");
258
- var import_api = require("@opentelemetry/api");
258
+ var import_api2 = require("@opentelemetry/api");
259
259
  var import_core2 = require("@opentelemetry/core");
260
260
  var import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
261
261
  var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
@@ -707,6 +707,51 @@ function makeRedactor(options) {
707
707
  return (attributes) => redactWith(attributes, r);
708
708
  }
709
709
 
710
+ // src/memory.ts
711
+ var import_node_perf_hooks = require("perf_hooks");
712
+ var import_node_v8 = require("v8");
713
+ var import_api = require("@opentelemetry/api");
714
+ function startMemoryMetrics() {
715
+ const meter = import_api.metrics.getMeter("autter-process-memory");
716
+ const rss = meter.createObservableGauge("autter.process.memory.rss", { unit: "By" });
717
+ const heapUsed = meter.createObservableGauge("autter.process.memory.heap.used", { unit: "By" });
718
+ const heapLimit = meter.createObservableGauge("autter.process.memory.heap.limit", { unit: "By" });
719
+ const containerLimit = meter.createObservableGauge("autter.process.memory.limit", { unit: "By" });
720
+ const afterGc = meter.createObservableGauge("autter.process.memory.heap.after_gc", { unit: "By" });
721
+ const gcCount = meter.createObservableCounter("autter.process.gc.count", { unit: "{collection}" });
722
+ const gcDuration = meter.createObservableCounter("autter.process.gc.duration", { unit: "ms" });
723
+ let collections = 0;
724
+ let durationMs = 0;
725
+ let lastAfterGc = null;
726
+ const callbacks = [
727
+ [rss, (result) => result.observe(process.memoryUsage().rss)],
728
+ [heapUsed, (result) => result.observe(process.memoryUsage().heapUsed)],
729
+ [heapLimit, (result) => result.observe((0, import_node_v8.getHeapStatistics)().heap_size_limit)],
730
+ [containerLimit, (result) => {
731
+ const constrained = process.constrainedMemory?.() ?? 0;
732
+ if (constrained > 0) result.observe(constrained);
733
+ }],
734
+ [afterGc, (result) => {
735
+ if (lastAfterGc !== null) result.observe(lastAfterGc);
736
+ }],
737
+ [gcCount, (result) => result.observe(collections)],
738
+ [gcDuration, (result) => result.observe(durationMs)]
739
+ ];
740
+ for (const [instrument, callback] of callbacks) instrument.addCallback(callback);
741
+ const observer = new import_node_perf_hooks.PerformanceObserver((list) => {
742
+ for (const entry of list.getEntries()) {
743
+ collections++;
744
+ durationMs += entry.duration;
745
+ lastAfterGc = process.memoryUsage().heapUsed;
746
+ }
747
+ });
748
+ observer.observe({ entryTypes: ["gc"] });
749
+ return () => {
750
+ observer.disconnect();
751
+ for (const [instrument, callback] of callbacks) instrument.removeCallback(callback);
752
+ };
753
+ }
754
+
710
755
  // src/server.ts
711
756
  var LLM_MARKER_ATTRIBUTES = [
712
757
  "gen_ai.operation.name",
@@ -769,14 +814,14 @@ var RETENTION_MAX_BUFFERED_SPANS = 5e3;
769
814
  var RETENTION_SWEEP_INTERVAL_MS = 1e4;
770
815
  var RETENTION_TRACE_TTL_MS = 3e4;
771
816
  function spanIndicatesError(span) {
772
- if (span.status.code === import_api.SpanStatusCode.ERROR) return true;
817
+ if (span.status.code === import_api2.SpanStatusCode.ERROR) return true;
773
818
  return span.events.some((event) => event.name === "exception");
774
819
  }
775
820
  var TAIL_RETAINED_ATTR = "autter.tail_retained";
776
821
  function withSampledFlag(span) {
777
822
  const spanContext = {
778
823
  ...span.spanContext(),
779
- traceFlags: span.spanContext().traceFlags | import_api.TraceFlags.SAMPLED
824
+ traceFlags: span.spanContext().traceFlags | import_api2.TraceFlags.SAMPLED
780
825
  };
781
826
  return {
782
827
  name: span.name,
@@ -816,11 +861,11 @@ var ErrorTraceRetentionProcessor = class {
816
861
  }
817
862
  onEnd(span) {
818
863
  const ctx = span.spanContext();
819
- if ((ctx.traceFlags & import_api.TraceFlags.SAMPLED) !== 0) return;
864
+ if ((ctx.traceFlags & import_api2.TraceFlags.SAMPLED) !== 0) return;
820
865
  const entry = this.entryFor(ctx.traceId);
821
866
  if (entry.retained) {
822
867
  this.forward(span);
823
- } else if (this.retainErrors && spanIndicatesError(span) || this.slowThresholdMs > 0 && span.kind === import_api.SpanKind.SERVER && span.duration[0] * 1e3 + span.duration[1] / 1e6 >= this.slowThresholdMs) {
868
+ } else if (this.retainErrors && spanIndicatesError(span) || this.slowThresholdMs > 0 && span.kind === import_api2.SpanKind.SERVER && span.duration[0] * 1e3 + span.duration[1] / 1e6 >= this.slowThresholdMs) {
824
869
  entry.retained = true;
825
870
  this.flush(entry);
826
871
  this.forward(span);
@@ -835,8 +880,8 @@ var ErrorTraceRetentionProcessor = class {
835
880
  }
836
881
  /** Promote the active trace, if any — called on captured errors. */
837
882
  retainActiveTrace() {
838
- const active2 = import_api.trace.getActiveSpan()?.spanContext();
839
- if (!active2 || (active2.traceFlags & import_api.TraceFlags.SAMPLED) !== 0) return;
883
+ const active2 = import_api2.trace.getActiveSpan()?.spanContext();
884
+ if (!active2 || (active2.traceFlags & import_api2.TraceFlags.SAMPLED) !== 0) return;
840
885
  const entry = this.entryFor(active2.traceId);
841
886
  if (entry.retained) return;
842
887
  entry.retained = true;
@@ -925,7 +970,7 @@ function usageAttributes(usage) {
925
970
  }
926
971
  async function runLlmSpan(tracer, info, fn) {
927
972
  const span = tracer.startSpan(`${info.operation ?? "chat"} ${info.model}`, {
928
- kind: import_api.SpanKind.CLIENT,
973
+ kind: import_api2.SpanKind.CLIENT,
929
974
  attributes: llmBaseAttributes(info)
930
975
  });
931
976
  const setResponseModel = (model) => span.setAttribute("gen_ai.response.model", model);
@@ -937,11 +982,11 @@ async function runLlmSpan(tracer, info, fn) {
937
982
  setAttributes: (attributes) => span.setAttributes(attributes)
938
983
  };
939
984
  try {
940
- const result = await import_api.context.with(
941
- import_api.trace.setSpan(import_api.context.active(), span),
985
+ const result = await import_api2.context.with(
986
+ import_api2.trace.setSpan(import_api2.context.active(), span),
942
987
  () => fn(handle)
943
988
  );
944
- span.setStatus({ code: import_api.SpanStatusCode.OK });
989
+ span.setStatus({ code: import_api2.SpanStatusCode.OK });
945
990
  return result;
946
991
  } catch (err) {
947
992
  if (err instanceof Error) {
@@ -949,7 +994,7 @@ async function runLlmSpan(tracer, info, fn) {
949
994
  span.setAttribute("error.type", err.name);
950
995
  }
951
996
  span.setStatus({
952
- code: import_api.SpanStatusCode.ERROR,
997
+ code: import_api2.SpanStatusCode.ERROR,
953
998
  message: err instanceof Error ? err.message : String(err)
954
999
  });
955
1000
  throw err;
@@ -964,7 +1009,7 @@ function recordLlmCall(tracer, call) {
964
1009
  const span = tracer.startSpan(
965
1010
  `${call.operation ?? "chat"} ${call.model}`,
966
1011
  {
967
- kind: import_api.SpanKind.CLIENT,
1012
+ kind: import_api2.SpanKind.CLIENT,
968
1013
  startTime,
969
1014
  attributes: { ...llmBaseAttributes(call), ...usageAttributes(call) }
970
1015
  }
@@ -975,27 +1020,27 @@ function recordLlmCall(tracer, call) {
975
1020
  span.setAttribute("error.type", call.error.name);
976
1021
  }
977
1022
  span.setStatus({
978
- code: import_api.SpanStatusCode.ERROR,
1023
+ code: import_api2.SpanStatusCode.ERROR,
979
1024
  message: call.error instanceof Error ? call.error.message : String(call.error)
980
1025
  });
981
1026
  } else {
982
- span.setStatus({ code: import_api.SpanStatusCode.OK });
1027
+ span.setStatus({ code: import_api2.SpanStatusCode.OK });
983
1028
  }
984
1029
  span.end(endTime);
985
1030
  }
986
1031
  async function runWithSpan(tracer, name, fn, attributes) {
987
1032
  const span = tracer.startSpan(name, { attributes });
988
1033
  try {
989
- const result = await import_api.context.with(
990
- import_api.trace.setSpan(import_api.context.active(), span),
1034
+ const result = await import_api2.context.with(
1035
+ import_api2.trace.setSpan(import_api2.context.active(), span),
991
1036
  fn
992
1037
  );
993
- span.setStatus({ code: import_api.SpanStatusCode.OK });
1038
+ span.setStatus({ code: import_api2.SpanStatusCode.OK });
994
1039
  return result;
995
1040
  } catch (err) {
996
1041
  if (err instanceof Error) span.recordException(err);
997
1042
  span.setStatus({
998
- code: import_api.SpanStatusCode.ERROR,
1043
+ code: import_api2.SpanStatusCode.ERROR,
999
1044
  message: err instanceof Error ? err.message : String(err)
1000
1045
  });
1001
1046
  throw err;
@@ -1013,7 +1058,7 @@ function expressRouteOf(req) {
1013
1058
  }
1014
1059
  function captureExpressRoute(_span, response) {
1015
1060
  if (!(response instanceof import_node_http.ServerResponse)) return;
1016
- const rpcMetadata = (0, import_core2.getRPCMetadata)(import_api.context.active());
1061
+ const rpcMetadata = (0, import_core2.getRPCMetadata)(import_api2.context.active());
1017
1062
  if (rpcMetadata?.type !== import_core2.RPCType.HTTP) return;
1018
1063
  const setRoute = () => {
1019
1064
  if (rpcMetadata.route) return;
@@ -1039,7 +1084,7 @@ function initAutterServer(options) {
1039
1084
  activeRedactor = makeRedactor(options.redactAttributes ?? true);
1040
1085
  const resource = new import_resources.Resource({
1041
1086
  [import_semantic_conventions.ATTR_SERVICE_NAME]: options.service,
1042
- "service.instance.id": (0, import_node_crypto.randomUUID)(),
1087
+ "service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID || (0, import_node_crypto.randomUUID)()).slice(0, 128),
1043
1088
  ...options.release ? { [import_semantic_conventions.ATTR_SERVICE_VERSION]: options.release } : {},
1044
1089
  "deployment.environment": environment,
1045
1090
  // Tells the ingester request metrics arrive on the metrics pipe, so
@@ -1104,6 +1149,7 @@ function initAutterServer(options) {
1104
1149
  ]
1105
1150
  });
1106
1151
  sdk.start();
1152
+ const stopMemoryMetrics = options.memoryMetrics === false ? null : startMemoryMetrics();
1107
1153
  const alwaysOnProvider = new import_sdk_trace_base.BasicTracerProvider({
1108
1154
  resource,
1109
1155
  sampler: new import_sdk_trace_base.AlwaysOnSampler(),
@@ -1125,7 +1171,7 @@ function initAutterServer(options) {
1125
1171
  const isError = error instanceof Error;
1126
1172
  const message = isError ? error.message : String(error);
1127
1173
  const redacted = activeRedactor(attributes);
1128
- const activeSpan = import_api.trace.getActiveSpan();
1174
+ const activeSpan = import_api2.trace.getActiveSpan();
1129
1175
  const reuseActive = activeSpan?.isRecording() === true;
1130
1176
  const span = reuseActive ? activeSpan : errorTracer.startSpan(isError ? error.name : "Error");
1131
1177
  span.setAttributes({ "autter.severity": "error", ...redacted });
@@ -1150,7 +1196,7 @@ function initAutterServer(options) {
1150
1196
  ...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {}
1151
1197
  });
1152
1198
  }
1153
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message });
1199
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
1154
1200
  if (!reuseActive) span.end();
1155
1201
  }
1156
1202
  function captureMessage2(message, severity = "warning", attributes) {
@@ -1168,13 +1214,13 @@ function initAutterServer(options) {
1168
1214
  ...stack ? { "exception.stacktrace": stack } : {},
1169
1215
  "autter.severity": severity
1170
1216
  });
1171
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message });
1217
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
1172
1218
  span.end();
1173
1219
  }
1174
1220
  function reportOutcome2(name, message, attributes) {
1175
1221
  errorTraceBuffer?.retainActiveTrace();
1176
1222
  telemetryStats.markCaptured();
1177
- const activeSpan = import_api.trace.getActiveSpan();
1223
+ const activeSpan = import_api2.trace.getActiveSpan();
1178
1224
  const reuseActive = activeSpan?.isRecording() === true;
1179
1225
  const span = reuseActive ? activeSpan : errorTracer.startSpan("OutcomeFailure");
1180
1226
  const safeOutcome = activeRedactor({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
@@ -1186,7 +1232,7 @@ function initAutterServer(options) {
1186
1232
  "autter.outcome.name": safeName,
1187
1233
  "autter.outcome.message": safeMessage
1188
1234
  });
1189
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: safeMessage });
1235
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: safeMessage });
1190
1236
  if (!reuseActive) span.end();
1191
1237
  }
1192
1238
  if (options.captureGlobalErrors !== false) {
@@ -1225,6 +1271,7 @@ function initAutterServer(options) {
1225
1271
  withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
1226
1272
  trackLlmCall: (call) => recordLlmCall(llmTracer, call),
1227
1273
  shutdown: async () => {
1274
+ stopMemoryMetrics?.();
1228
1275
  active = null;
1229
1276
  activeAlwaysOnProvider = null;
1230
1277
  autoFlushHandle?.dispose();
@@ -1243,35 +1290,35 @@ function captureException(error, attributes) {
1243
1290
  active.captureException(error, attributes);
1244
1291
  return;
1245
1292
  }
1246
- const span = import_api.trace.getTracer("autter-errors").startSpan(error instanceof Error ? error.name : "Error", { attributes });
1293
+ const span = import_api2.trace.getTracer("autter-errors").startSpan(error instanceof Error ? error.name : "Error", { attributes });
1247
1294
  if (error instanceof Error) span.recordException(error);
1248
1295
  span.setStatus({
1249
- code: import_api.SpanStatusCode.ERROR,
1296
+ code: import_api2.SpanStatusCode.ERROR,
1250
1297
  message: error instanceof Error ? error.message : String(error)
1251
1298
  });
1252
1299
  span.end();
1253
1300
  }
1254
1301
  function withProcessSpan(name, fn, attributes) {
1255
1302
  if (active) return active.withProcessSpan(name, fn, attributes);
1256
- return runWithSpan(import_api.trace.getTracer("autter-processes"), name, fn, attributes);
1303
+ return runWithSpan(import_api2.trace.getTracer("autter-processes"), name, fn, attributes);
1257
1304
  }
1258
1305
  function withLlmCall(info, fn) {
1259
1306
  if (active) return active.withLlmCall(info, fn);
1260
- return runLlmSpan(import_api.trace.getTracer("autter-llm"), info, fn);
1307
+ return runLlmSpan(import_api2.trace.getTracer("autter-llm"), info, fn);
1261
1308
  }
1262
1309
  function trackLlmCall(call) {
1263
1310
  if (active) {
1264
1311
  active.trackLlmCall(call);
1265
1312
  return;
1266
1313
  }
1267
- recordLlmCall(import_api.trace.getTracer("autter-llm"), call);
1314
+ recordLlmCall(import_api2.trace.getTracer("autter-llm"), call);
1268
1315
  }
1269
1316
  function captureMessage(message, severity = "warning", attributes) {
1270
1317
  if (active) {
1271
1318
  active.captureMessage(message, severity, attributes);
1272
1319
  return;
1273
1320
  }
1274
- const span = import_api.trace.getTracer("autter-errors").startSpan("Message", {
1321
+ const span = import_api2.trace.getTracer("autter-errors").startSpan("Message", {
1275
1322
  attributes: { "autter.severity": severity, ...attributes }
1276
1323
  });
1277
1324
  span.addEvent("exception", {
@@ -1279,12 +1326,12 @@ function captureMessage(message, severity = "warning", attributes) {
1279
1326
  "exception.message": message,
1280
1327
  "autter.severity": severity
1281
1328
  });
1282
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message });
1329
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
1283
1330
  span.end();
1284
1331
  }
1285
1332
  function reportOutcome(name, message, attributes) {
1286
1333
  if (active) return active.reportOutcome(name, message, attributes);
1287
- const span = import_api.trace.getTracer("autter-outcomes").startSpan("OutcomeFailure");
1334
+ const span = import_api2.trace.getTracer("autter-outcomes").startSpan("OutcomeFailure");
1288
1335
  const safeOutcome = redactAttributes({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
1289
1336
  const safeName = String(safeOutcome["autter.outcome.name"] ?? "operation");
1290
1337
  const safeMessage = String(safeOutcome["autter.outcome.message"] ?? "failed");
@@ -1294,7 +1341,7 @@ function reportOutcome(name, message, attributes) {
1294
1341
  "autter.outcome.name": safeName,
1295
1342
  "autter.outcome.message": safeMessage
1296
1343
  });
1297
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: safeMessage });
1344
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: safeMessage });
1298
1345
  span.end();
1299
1346
  }
1300
1347
  function makeSafeCapture(options) {
@@ -1305,7 +1352,7 @@ function makeSafeCapture(options) {
1305
1352
  };
1306
1353
  }
1307
1354
  function alwaysOnTracer(name) {
1308
- return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) : import_api.trace.getTracer(name);
1355
+ return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) : import_api2.trace.getTracer(name);
1309
1356
  }
1310
1357
  function autterLlmTracer() {
1311
1358
  return alwaysOnTracer("autter-llm");
@@ -1325,7 +1372,7 @@ async function emitLlmSelftestTrace() {
1325
1372
  attributes: { "autter.selftest": true }
1326
1373
  },
1327
1374
  (llm) => {
1328
- traceId = import_api.trace.getActiveSpan()?.spanContext().traceId ?? "";
1375
+ traceId = import_api2.trace.getActiveSpan()?.spanContext().traceId ?? "";
1329
1376
  llm.setUsage({ inputTokens: 1, outputTokens: 1 });
1330
1377
  llm.setCost(0);
1331
1378
  }
@@ -1335,7 +1382,7 @@ async function emitLlmSelftestTrace() {
1335
1382
  }
1336
1383
 
1337
1384
  // src/llm-instrument.ts
1338
- var import_api2 = require("@opentelemetry/api");
1385
+ var import_api3 = require("@opentelemetry/api");
1339
1386
  var CALL_METHODS = /* @__PURE__ */ new Set([
1340
1387
  "create",
1341
1388
  // openai chat/completions/embeddings/responses, anthropic messages
@@ -1393,7 +1440,7 @@ function applyUsage(span, usage) {
1393
1440
  function endWithError(span, err) {
1394
1441
  if (err instanceof Error) span.recordException(err);
1395
1442
  span.setStatus({
1396
- code: import_api2.SpanStatusCode.ERROR,
1443
+ code: import_api3.SpanStatusCode.ERROR,
1397
1444
  message: err instanceof Error ? err.message : String(err)
1398
1445
  });
1399
1446
  span.end();
@@ -1413,7 +1460,7 @@ function wrapStream(stream, span) {
1413
1460
  if (err !== void 0) {
1414
1461
  endWithError(span, err);
1415
1462
  } else {
1416
- span.setStatus({ code: import_api2.SpanStatusCode.OK });
1463
+ span.setStatus({ code: import_api3.SpanStatusCode.OK });
1417
1464
  span.end();
1418
1465
  }
1419
1466
  };
@@ -1468,7 +1515,7 @@ function instrumentLlmClient(client, options) {
1468
1515
  const operation = /embed/i.test(callPath) ? "embeddings" : "chat";
1469
1516
  const userId = resolveUserId();
1470
1517
  const span = autterLlmTracer().startSpan(`${operation} ${request.model}`, {
1471
- kind: import_api2.SpanKind.CLIENT,
1518
+ kind: import_api3.SpanKind.CLIENT,
1472
1519
  attributes: {
1473
1520
  "gen_ai.operation.name": operation,
1474
1521
  "gen_ai.provider.name": provider,
@@ -1490,7 +1537,7 @@ function instrumentLlmClient(client, options) {
1490
1537
  return wrapStream(result, span);
1491
1538
  }
1492
1539
  applyUsage(span, extractUsage(result));
1493
- span.setStatus({ code: import_api2.SpanStatusCode.OK });
1540
+ span.setStatus({ code: import_api3.SpanStatusCode.OK });
1494
1541
  span.end();
1495
1542
  return result;
1496
1543
  }
@@ -1500,7 +1547,7 @@ function instrumentLlmClient(client, options) {
1500
1547
  return wrapStream(resolved, span);
1501
1548
  }
1502
1549
  applyUsage(span, extractUsage(resolved));
1503
- span.setStatus({ code: import_api2.SpanStatusCode.OK });
1550
+ span.setStatus({ code: import_api3.SpanStatusCode.OK });
1504
1551
  span.end();
1505
1552
  return resolved;
1506
1553
  },
package/dist/index.d.cts CHANGED
@@ -100,6 +100,8 @@ interface AutterServerOptions {
100
100
  /** Ingester base URL. Default: https://otlp.autter.dev */
101
101
  endpoint?: string;
102
102
  service: string;
103
+ /** Stable for this process lifetime; use an ECS task/container ID or pod UID plus restart count to correlate platform events. */
104
+ instanceId?: string;
103
105
  environment?: string;
104
106
  /** e.g. a git SHA. Maps to service.version / release. */
105
107
  release?: string;
@@ -119,6 +121,8 @@ interface AutterServerOptions {
119
121
  retainTracesAboveMs?: number;
120
122
  /** Metric export interval. Default 60_000 ms. */
121
123
  metricIntervalMs?: number;
124
+ /** Collect RSS, heap, limit, and GC signals per process instance. Default true. */
125
+ memoryMetrics?: boolean;
122
126
  /** Capture crashing exceptions via process.uncaughtExceptionMonitor (default true). */
123
127
  captureGlobalErrors?: boolean;
124
128
  /**
package/dist/index.d.ts CHANGED
@@ -100,6 +100,8 @@ interface AutterServerOptions {
100
100
  /** Ingester base URL. Default: https://otlp.autter.dev */
101
101
  endpoint?: string;
102
102
  service: string;
103
+ /** Stable for this process lifetime; use an ECS task/container ID or pod UID plus restart count to correlate platform events. */
104
+ instanceId?: string;
103
105
  environment?: string;
104
106
  /** e.g. a git SHA. Maps to service.version / release. */
105
107
  release?: string;
@@ -119,6 +121,8 @@ interface AutterServerOptions {
119
121
  retainTracesAboveMs?: number;
120
122
  /** Metric export interval. Default 60_000 ms. */
121
123
  metricIntervalMs?: number;
124
+ /** Collect RSS, heap, limit, and GC signals per process instance. Default true. */
125
+ memoryMetrics?: boolean;
122
126
  /** Capture crashing exceptions via process.uncaughtExceptionMonitor (default true). */
123
127
  captureGlobalErrors?: boolean;
124
128
  /**
package/dist/index.js CHANGED
@@ -675,6 +675,51 @@ function makeRedactor(options) {
675
675
  return (attributes) => redactWith(attributes, r);
676
676
  }
677
677
 
678
+ // src/memory.ts
679
+ import { PerformanceObserver } from "perf_hooks";
680
+ import { getHeapStatistics } from "v8";
681
+ import { metrics } from "@opentelemetry/api";
682
+ function startMemoryMetrics() {
683
+ const meter = metrics.getMeter("autter-process-memory");
684
+ const rss = meter.createObservableGauge("autter.process.memory.rss", { unit: "By" });
685
+ const heapUsed = meter.createObservableGauge("autter.process.memory.heap.used", { unit: "By" });
686
+ const heapLimit = meter.createObservableGauge("autter.process.memory.heap.limit", { unit: "By" });
687
+ const containerLimit = meter.createObservableGauge("autter.process.memory.limit", { unit: "By" });
688
+ const afterGc = meter.createObservableGauge("autter.process.memory.heap.after_gc", { unit: "By" });
689
+ const gcCount = meter.createObservableCounter("autter.process.gc.count", { unit: "{collection}" });
690
+ const gcDuration = meter.createObservableCounter("autter.process.gc.duration", { unit: "ms" });
691
+ let collections = 0;
692
+ let durationMs = 0;
693
+ let lastAfterGc = null;
694
+ const callbacks = [
695
+ [rss, (result) => result.observe(process.memoryUsage().rss)],
696
+ [heapUsed, (result) => result.observe(process.memoryUsage().heapUsed)],
697
+ [heapLimit, (result) => result.observe(getHeapStatistics().heap_size_limit)],
698
+ [containerLimit, (result) => {
699
+ const constrained = process.constrainedMemory?.() ?? 0;
700
+ if (constrained > 0) result.observe(constrained);
701
+ }],
702
+ [afterGc, (result) => {
703
+ if (lastAfterGc !== null) result.observe(lastAfterGc);
704
+ }],
705
+ [gcCount, (result) => result.observe(collections)],
706
+ [gcDuration, (result) => result.observe(durationMs)]
707
+ ];
708
+ for (const [instrument, callback] of callbacks) instrument.addCallback(callback);
709
+ const observer = new PerformanceObserver((list) => {
710
+ for (const entry of list.getEntries()) {
711
+ collections++;
712
+ durationMs += entry.duration;
713
+ lastAfterGc = process.memoryUsage().heapUsed;
714
+ }
715
+ });
716
+ observer.observe({ entryTypes: ["gc"] });
717
+ return () => {
718
+ observer.disconnect();
719
+ for (const [instrument, callback] of callbacks) instrument.removeCallback(callback);
720
+ };
721
+ }
722
+
678
723
  // src/server.ts
679
724
  var LLM_MARKER_ATTRIBUTES = [
680
725
  "gen_ai.operation.name",
@@ -1007,7 +1052,7 @@ function initAutterServer(options) {
1007
1052
  activeRedactor = makeRedactor(options.redactAttributes ?? true);
1008
1053
  const resource = new Resource({
1009
1054
  [ATTR_SERVICE_NAME]: options.service,
1010
- "service.instance.id": randomUUID(),
1055
+ "service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID || randomUUID()).slice(0, 128),
1011
1056
  ...options.release ? { [ATTR_SERVICE_VERSION]: options.release } : {},
1012
1057
  "deployment.environment": environment,
1013
1058
  // Tells the ingester request metrics arrive on the metrics pipe, so
@@ -1072,6 +1117,7 @@ function initAutterServer(options) {
1072
1117
  ]
1073
1118
  });
1074
1119
  sdk.start();
1120
+ const stopMemoryMetrics = options.memoryMetrics === false ? null : startMemoryMetrics();
1075
1121
  const alwaysOnProvider = new BasicTracerProvider({
1076
1122
  resource,
1077
1123
  sampler: new AlwaysOnSampler(),
@@ -1193,6 +1239,7 @@ function initAutterServer(options) {
1193
1239
  withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
1194
1240
  trackLlmCall: (call) => recordLlmCall(llmTracer, call),
1195
1241
  shutdown: async () => {
1242
+ stopMemoryMetrics?.();
1196
1243
  active = null;
1197
1244
  activeAlwaysOnProvider = null;
1198
1245
  autoFlushHandle?.dispose();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autter/runtime-node",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Autter Runtime for Node.js: same-origin browser relay handler + curated OpenTelemetry server tracker",
5
5
  "license": "MIT",
6
6
  "type": "module",