@dexcost/sdk 0.2.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/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/adapters/_netbytes.d.ts +31 -0
- package/dist/adapters/_netbytes.d.ts.map +1 -0
- package/dist/adapters/_netbytes.js +154 -0
- package/dist/adapters/_netbytes.js.map +1 -0
- package/dist/adapters/aws-lambda.d.ts +41 -0
- package/dist/adapters/aws-lambda.d.ts.map +1 -0
- package/dist/adapters/aws-lambda.js +65 -0
- package/dist/adapters/aws-lambda.js.map +1 -0
- package/dist/adapters/browser.d.ts +52 -0
- package/dist/adapters/browser.d.ts.map +1 -0
- package/dist/adapters/browser.js +127 -0
- package/dist/adapters/browser.js.map +1 -0
- package/dist/adapters/compute-wrap.d.ts +33 -0
- package/dist/adapters/compute-wrap.d.ts.map +1 -0
- package/dist/adapters/compute-wrap.js +188 -0
- package/dist/adapters/compute-wrap.js.map +1 -0
- package/dist/adapters/data/aws_lambda_pricing.json +61 -0
- package/dist/adapters/gpu-wrap.d.ts +31 -0
- package/dist/adapters/gpu-wrap.d.ts.map +1 -0
- package/dist/adapters/gpu-wrap.js +147 -0
- package/dist/adapters/gpu-wrap.js.map +1 -0
- package/dist/adapters/http.d.ts +58 -0
- package/dist/adapters/http.d.ts.map +1 -0
- package/dist/adapters/http.js +769 -0
- package/dist/adapters/http.js.map +1 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/network-accountant.d.ts +63 -0
- package/dist/adapters/network-accountant.d.ts.map +1 -0
- package/dist/adapters/network-accountant.js +153 -0
- package/dist/adapters/network-accountant.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +225 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/scanner.d.ts +39 -0
- package/dist/cli/scanner.d.ts.map +1 -0
- package/dist/cli/scanner.js +480 -0
- package/dist/cli/scanner.js.map +1 -0
- package/dist/clients.d.ts +54 -0
- package/dist/clients.d.ts.map +1 -0
- package/dist/clients.js +240 -0
- package/dist/clients.js.map +1 -0
- package/dist/cloud-detect.d.ts +96 -0
- package/dist/cloud-detect.d.ts.map +1 -0
- package/dist/cloud-detect.js +545 -0
- package/dist/cloud-detect.js.map +1 -0
- package/dist/core/auto-task.d.ts +20 -0
- package/dist/core/auto-task.d.ts.map +1 -0
- package/dist/core/auto-task.js +34 -0
- package/dist/core/auto-task.js.map +1 -0
- package/dist/core/cgroup-reader.d.ts +45 -0
- package/dist/core/cgroup-reader.d.ts.map +1 -0
- package/dist/core/cgroup-reader.js +124 -0
- package/dist/core/cgroup-reader.js.map +1 -0
- package/dist/core/cgroup-walker.d.ts +60 -0
- package/dist/core/cgroup-walker.d.ts.map +1 -0
- package/dist/core/cgroup-walker.js +166 -0
- package/dist/core/cgroup-walker.js.map +1 -0
- package/dist/core/compute-accountant.d.ts +51 -0
- package/dist/core/compute-accountant.d.ts.map +1 -0
- package/dist/core/compute-accountant.js +179 -0
- package/dist/core/compute-accountant.js.map +1 -0
- package/dist/core/compute-runtime.d.ts +42 -0
- package/dist/core/compute-runtime.d.ts.map +1 -0
- package/dist/core/compute-runtime.js +80 -0
- package/dist/core/compute-runtime.js.map +1 -0
- package/dist/core/config.d.ts +44 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +66 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/context.d.ts +76 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +91 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/fargate-metadata.d.ts +27 -0
- package/dist/core/fargate-metadata.d.ts.map +1 -0
- package/dist/core/fargate-metadata.js +102 -0
- package/dist/core/fargate-metadata.js.map +1 -0
- package/dist/core/gpu-accountant.d.ts +104 -0
- package/dist/core/gpu-accountant.d.ts.map +1 -0
- package/dist/core/gpu-accountant.js +383 -0
- package/dist/core/gpu-accountant.js.map +1 -0
- package/dist/core/gpu-runtime.d.ts +58 -0
- package/dist/core/gpu-runtime.d.ts.map +1 -0
- package/dist/core/gpu-runtime.js +131 -0
- package/dist/core/gpu-runtime.js.map +1 -0
- package/dist/core/heuristics.d.ts +74 -0
- package/dist/core/heuristics.d.ts.map +1 -0
- package/dist/core/heuristics.js +182 -0
- package/dist/core/heuristics.js.map +1 -0
- package/dist/core/models.d.ts +149 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +226 -0
- package/dist/core/models.js.map +1 -0
- package/dist/core/nvml-reader.d.ts +114 -0
- package/dist/core/nvml-reader.d.ts.map +1 -0
- package/dist/core/nvml-reader.js +323 -0
- package/dist/core/nvml-reader.js.map +1 -0
- package/dist/core/session.d.ts +48 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +123 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/tracker.d.ts +364 -0
- package/dist/core/tracker.d.ts.map +1 -0
- package/dist/core/tracker.js +1073 -0
- package/dist/core/tracker.js.map +1 -0
- package/dist/data/compute_prices.json +180 -0
- package/dist/data/egress_prices.json +418 -0
- package/dist/data/gpu_prices.json +412 -0
- package/dist/data/service_prices.json +2595 -0
- package/dist/dev-console.d.ts +12 -0
- package/dist/dev-console.d.ts.map +1 -0
- package/dist/dev-console.js +60 -0
- package/dist/dev-console.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/instruments/anthropic.d.ts +26 -0
- package/dist/instruments/anthropic.d.ts.map +1 -0
- package/dist/instruments/anthropic.js +242 -0
- package/dist/instruments/anthropic.js.map +1 -0
- package/dist/instruments/bedrock.d.ts +29 -0
- package/dist/instruments/bedrock.d.ts.map +1 -0
- package/dist/instruments/bedrock.js +215 -0
- package/dist/instruments/bedrock.js.map +1 -0
- package/dist/instruments/cohere.d.ts +29 -0
- package/dist/instruments/cohere.d.ts.map +1 -0
- package/dist/instruments/cohere.js +237 -0
- package/dist/instruments/cohere.js.map +1 -0
- package/dist/instruments/gemini.d.ts +30 -0
- package/dist/instruments/gemini.d.ts.map +1 -0
- package/dist/instruments/gemini.js +247 -0
- package/dist/instruments/gemini.js.map +1 -0
- package/dist/instruments/index.d.ts +35 -0
- package/dist/instruments/index.d.ts.map +1 -0
- package/dist/instruments/index.js +54 -0
- package/dist/instruments/index.js.map +1 -0
- package/dist/instruments/mcp.d.ts +24 -0
- package/dist/instruments/mcp.d.ts.map +1 -0
- package/dist/instruments/mcp.js +459 -0
- package/dist/instruments/mcp.js.map +1 -0
- package/dist/instruments/openai.d.ts +26 -0
- package/dist/instruments/openai.d.ts.map +1 -0
- package/dist/instruments/openai.js +221 -0
- package/dist/instruments/openai.js.map +1 -0
- package/dist/instruments/vercel-ai.d.ts +28 -0
- package/dist/instruments/vercel-ai.d.ts.map +1 -0
- package/dist/instruments/vercel-ai.js +192 -0
- package/dist/instruments/vercel-ai.js.map +1 -0
- package/dist/integrations/langchain.d.ts +65 -0
- package/dist/integrations/langchain.d.ts.map +1 -0
- package/dist/integrations/langchain.js +165 -0
- package/dist/integrations/langchain.js.map +1 -0
- package/dist/middleware/express.d.ts +55 -0
- package/dist/middleware/express.d.ts.map +1 -0
- package/dist/middleware/express.js +101 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/index.d.ts +6 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +5 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/pricing/compute-pricing.d.ts +57 -0
- package/dist/pricing/compute-pricing.d.ts.map +1 -0
- package/dist/pricing/compute-pricing.js +627 -0
- package/dist/pricing/compute-pricing.js.map +1 -0
- package/dist/pricing/cost_map.json +37665 -0
- package/dist/pricing/egress-pricing.d.ts +55 -0
- package/dist/pricing/egress-pricing.d.ts.map +1 -0
- package/dist/pricing/egress-pricing.js +226 -0
- package/dist/pricing/egress-pricing.js.map +1 -0
- package/dist/pricing/engine.d.ts +24 -0
- package/dist/pricing/engine.d.ts.map +1 -0
- package/dist/pricing/engine.js +148 -0
- package/dist/pricing/engine.js.map +1 -0
- package/dist/pricing/gpu-pricing.d.ts +63 -0
- package/dist/pricing/gpu-pricing.d.ts.map +1 -0
- package/dist/pricing/gpu-pricing.js +484 -0
- package/dist/pricing/gpu-pricing.js.map +1 -0
- package/dist/pricing/rates.d.ts +17 -0
- package/dist/pricing/rates.d.ts.map +1 -0
- package/dist/pricing/rates.js +102 -0
- package/dist/pricing/rates.js.map +1 -0
- package/dist/pricing/service-catalog.d.ts +87 -0
- package/dist/pricing/service-catalog.d.ts.map +1 -0
- package/dist/pricing/service-catalog.js +406 -0
- package/dist/pricing/service-catalog.js.map +1 -0
- package/dist/schema/dexcost-event.v1.json +111 -0
- package/dist/schema/dexcost-task.v1.json +160 -0
- package/dist/schema/validate.d.ts +15 -0
- package/dist/schema/validate.d.ts.map +1 -0
- package/dist/schema/validate.js +87 -0
- package/dist/schema/validate.js.map +1 -0
- package/dist/security/redaction.d.ts +55 -0
- package/dist/security/redaction.d.ts.map +1 -0
- package/dist/security/redaction.js +144 -0
- package/dist/security/redaction.js.map +1 -0
- package/dist/transport/buffer.d.ts +117 -0
- package/dist/transport/buffer.d.ts.map +1 -0
- package/dist/transport/buffer.js +759 -0
- package/dist/transport/buffer.js.map +1 -0
- package/dist/transport/pusher.d.ts +89 -0
- package/dist/transport/pusher.d.ts.map +1 -0
- package/dist/transport/pusher.js +323 -0
- package/dist/transport/pusher.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core data models for dexcost TypeScript SDK.
|
|
3
|
+
*
|
|
4
|
+
* These interfaces match the Dexcost Standard Event Schema v1
|
|
5
|
+
* and mirror the Python SDK's Task and Event dataclasses.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Serialise a Date to the canonical wire format. Sprint 3 Theme F /
|
|
9
|
+
* §4.1.1 (P1): RFC3339 with microsecond precision (6 fractional
|
|
10
|
+
* digits) + "Z" suffix, matching the Python canonical.
|
|
11
|
+
*
|
|
12
|
+
* `Date.toISOString()` only gives 3-digit millisecond precision, so
|
|
13
|
+
* we pad with three trailing zeros. Customer-visible Date objects
|
|
14
|
+
* carry no sub-millisecond data anyway, so the pad is information-
|
|
15
|
+
* preserving — it just aligns the wire string for cross-SDK parity.
|
|
16
|
+
*/
|
|
17
|
+
function isoCanonical(d) {
|
|
18
|
+
const iso = d.toISOString();
|
|
19
|
+
// iso is `YYYY-MM-DDTHH:mm:ss.sssZ` (24 chars); pad to .sssNNNZ
|
|
20
|
+
return iso.slice(0, -1) + "000Z";
|
|
21
|
+
}
|
|
22
|
+
/** Create a default Task with required fields. */
|
|
23
|
+
export function createTask(overrides) {
|
|
24
|
+
return {
|
|
25
|
+
taskType: "",
|
|
26
|
+
status: "pending",
|
|
27
|
+
startedAt: new Date(),
|
|
28
|
+
metadata: {},
|
|
29
|
+
llmCostUsd: 0,
|
|
30
|
+
externalCostUsd: 0,
|
|
31
|
+
computeCostUsd: 0,
|
|
32
|
+
networkCostUsd: 0,
|
|
33
|
+
gpuCostUsd: 0,
|
|
34
|
+
totalCostUsd: 0,
|
|
35
|
+
totalInputTokens: 0,
|
|
36
|
+
totalOutputTokens: 0,
|
|
37
|
+
totalCachedTokens: 0,
|
|
38
|
+
retryCount: 0,
|
|
39
|
+
retryCostUsd: 0,
|
|
40
|
+
failureCount: 0,
|
|
41
|
+
networkBytesIn: 0,
|
|
42
|
+
networkBytesOut: 0,
|
|
43
|
+
networkCallCount: 0,
|
|
44
|
+
networkByHost: { hosts: [] },
|
|
45
|
+
schemaVersion: "1",
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Create a default CostEvent with required fields. */
|
|
50
|
+
export function createCostEvent(overrides) {
|
|
51
|
+
return {
|
|
52
|
+
eventType: "llm_call",
|
|
53
|
+
occurredAt: new Date(),
|
|
54
|
+
costUsd: 0,
|
|
55
|
+
costConfidence: "exact",
|
|
56
|
+
isRetry: false,
|
|
57
|
+
details: {},
|
|
58
|
+
schemaVersion: "1",
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Serialise a Task to a JSON-safe dictionary matching the Standard Event Schema v1.
|
|
64
|
+
* Costs are serialised as strings to preserve precision.
|
|
65
|
+
*/
|
|
66
|
+
export function taskToDict(task) {
|
|
67
|
+
return {
|
|
68
|
+
task_id: task.taskId,
|
|
69
|
+
task_type: task.taskType,
|
|
70
|
+
status: task.status,
|
|
71
|
+
started_at: isoCanonical(task.startedAt),
|
|
72
|
+
ended_at: task.endedAt ? isoCanonical(task.endedAt) : null,
|
|
73
|
+
metadata: task.metadata,
|
|
74
|
+
customer_id: task.customerId ?? null,
|
|
75
|
+
project_id: task.projectId ?? null,
|
|
76
|
+
parent_task_id: task.parentTaskId ?? null,
|
|
77
|
+
experiment_id: task.experimentId ?? null,
|
|
78
|
+
variant: task.variant ?? null,
|
|
79
|
+
llm_cost_usd: String(task.llmCostUsd),
|
|
80
|
+
external_cost_usd: String(task.externalCostUsd),
|
|
81
|
+
compute_cost_usd: String(task.computeCostUsd),
|
|
82
|
+
network_cost_usd: String(task.networkCostUsd),
|
|
83
|
+
gpu_cost_usd: String(task.gpuCostUsd),
|
|
84
|
+
total_cost_usd: String(task.totalCostUsd),
|
|
85
|
+
total_input_tokens: task.totalInputTokens,
|
|
86
|
+
total_output_tokens: task.totalOutputTokens,
|
|
87
|
+
total_cached_tokens: task.totalCachedTokens,
|
|
88
|
+
retry_count: task.retryCount,
|
|
89
|
+
retry_cost_usd: String(task.retryCostUsd),
|
|
90
|
+
failure_count: task.failureCount,
|
|
91
|
+
network_bytes_in: task.networkBytesIn,
|
|
92
|
+
network_bytes_out: task.networkBytesOut,
|
|
93
|
+
network_call_count: task.networkCallCount,
|
|
94
|
+
network_by_host: task.networkByHost,
|
|
95
|
+
schema_version: task.schemaVersion,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Serialise a CostEvent to a JSON-safe dictionary matching the Standard Event Schema v1.
|
|
100
|
+
* Costs are serialised as strings to preserve precision.
|
|
101
|
+
*/
|
|
102
|
+
export function eventToDict(event) {
|
|
103
|
+
return {
|
|
104
|
+
event_id: event.eventId,
|
|
105
|
+
task_id: event.taskId,
|
|
106
|
+
event_type: event.eventType,
|
|
107
|
+
occurred_at: isoCanonical(event.occurredAt),
|
|
108
|
+
cost_usd: String(event.costUsd),
|
|
109
|
+
cost_confidence: event.costConfidence,
|
|
110
|
+
pricing_source: event.pricingSource ?? null,
|
|
111
|
+
pricing_version: event.pricingVersion ?? null,
|
|
112
|
+
provider: event.provider ?? null,
|
|
113
|
+
model: event.model ?? null,
|
|
114
|
+
input_tokens: event.inputTokens ?? null,
|
|
115
|
+
output_tokens: event.outputTokens ?? null,
|
|
116
|
+
cached_tokens: event.cachedTokens ?? null,
|
|
117
|
+
latency_ms: event.latencyMs ?? null,
|
|
118
|
+
service_name: event.serviceName ?? null,
|
|
119
|
+
is_retry: event.isRetry,
|
|
120
|
+
retry_reason: event.retryReason ?? null,
|
|
121
|
+
retry_of: event.retryOf ?? null,
|
|
122
|
+
details: event.details,
|
|
123
|
+
schema_version: event.schemaVersion,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Deserialisation helpers (inverse of taskToDict / eventToDict)
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
function _requireString(data, key) {
|
|
130
|
+
const value = data[key];
|
|
131
|
+
if (typeof value !== "string") {
|
|
132
|
+
throw new Error(`Invalid task/event data: missing or non-string field "${key}"`);
|
|
133
|
+
}
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
function _optString(value) {
|
|
137
|
+
return typeof value === "string" ? value : undefined;
|
|
138
|
+
}
|
|
139
|
+
function _toNumber(value, fallback = 0) {
|
|
140
|
+
if (typeof value === "number")
|
|
141
|
+
return value;
|
|
142
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
143
|
+
const n = Number(value);
|
|
144
|
+
if (!Number.isNaN(n))
|
|
145
|
+
return n;
|
|
146
|
+
}
|
|
147
|
+
return fallback;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Deserialise a Task from a JSON-safe dictionary (inverse of `taskToDict`).
|
|
151
|
+
*
|
|
152
|
+
* Mirrors the Python SDK's `Task.from_dict`. Throws an Error when required
|
|
153
|
+
* fields are missing or malformed.
|
|
154
|
+
*/
|
|
155
|
+
export function taskFromDict(data) {
|
|
156
|
+
const startedAtRaw = _requireString(data, "started_at");
|
|
157
|
+
const endedAtRaw = data["ended_at"];
|
|
158
|
+
return {
|
|
159
|
+
taskId: _requireString(data, "task_id"),
|
|
160
|
+
taskType: typeof data["task_type"] === "string" ? data["task_type"] : "",
|
|
161
|
+
status: _requireString(data, "status"),
|
|
162
|
+
startedAt: new Date(startedAtRaw),
|
|
163
|
+
endedAt: typeof endedAtRaw === "string" ? new Date(endedAtRaw) : undefined,
|
|
164
|
+
metadata: data["metadata"] && typeof data["metadata"] === "object"
|
|
165
|
+
? data["metadata"]
|
|
166
|
+
: {},
|
|
167
|
+
customerId: _optString(data["customer_id"]),
|
|
168
|
+
projectId: _optString(data["project_id"]),
|
|
169
|
+
parentTaskId: _optString(data["parent_task_id"]),
|
|
170
|
+
experimentId: _optString(data["experiment_id"]),
|
|
171
|
+
variant: _optString(data["variant"]),
|
|
172
|
+
llmCostUsd: _toNumber(data["llm_cost_usd"]),
|
|
173
|
+
externalCostUsd: _toNumber(data["external_cost_usd"]),
|
|
174
|
+
computeCostUsd: _toNumber(data["compute_cost_usd"]),
|
|
175
|
+
networkCostUsd: _toNumber(data["network_cost_usd"]),
|
|
176
|
+
gpuCostUsd: _toNumber(data["gpu_cost_usd"]),
|
|
177
|
+
totalCostUsd: _toNumber(data["total_cost_usd"]),
|
|
178
|
+
totalInputTokens: _toNumber(data["total_input_tokens"]),
|
|
179
|
+
totalOutputTokens: _toNumber(data["total_output_tokens"]),
|
|
180
|
+
totalCachedTokens: _toNumber(data["total_cached_tokens"]),
|
|
181
|
+
retryCount: _toNumber(data["retry_count"]),
|
|
182
|
+
retryCostUsd: _toNumber(data["retry_cost_usd"]),
|
|
183
|
+
failureCount: _toNumber(data["failure_count"]),
|
|
184
|
+
networkBytesIn: _toNumber(data["network_bytes_in"]),
|
|
185
|
+
networkBytesOut: _toNumber(data["network_bytes_out"]),
|
|
186
|
+
networkCallCount: _toNumber(data["network_call_count"]),
|
|
187
|
+
networkByHost: data["network_by_host"] && typeof data["network_by_host"] === "object"
|
|
188
|
+
? data["network_by_host"]
|
|
189
|
+
: { hosts: [] },
|
|
190
|
+
schemaVersion: _optString(data["schema_version"]) ?? "1",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Deserialise a CostEvent from a JSON-safe dictionary (inverse of `eventToDict`).
|
|
195
|
+
*
|
|
196
|
+
* Mirrors the Python SDK's `Event.from_dict`. Throws an Error when required
|
|
197
|
+
* fields are missing or malformed.
|
|
198
|
+
*/
|
|
199
|
+
export function eventFromDict(data) {
|
|
200
|
+
const occurredAtRaw = _requireString(data, "occurred_at");
|
|
201
|
+
return {
|
|
202
|
+
eventId: _requireString(data, "event_id"),
|
|
203
|
+
taskId: _requireString(data, "task_id"),
|
|
204
|
+
eventType: _requireString(data, "event_type"),
|
|
205
|
+
occurredAt: new Date(occurredAtRaw),
|
|
206
|
+
costUsd: _toNumber(data["cost_usd"]),
|
|
207
|
+
costConfidence: _requireString(data, "cost_confidence"),
|
|
208
|
+
pricingSource: _optString(data["pricing_source"]),
|
|
209
|
+
pricingVersion: _optString(data["pricing_version"]),
|
|
210
|
+
provider: _optString(data["provider"]),
|
|
211
|
+
model: _optString(data["model"]),
|
|
212
|
+
inputTokens: typeof data["input_tokens"] === "number" ? data["input_tokens"] : undefined,
|
|
213
|
+
outputTokens: typeof data["output_tokens"] === "number" ? data["output_tokens"] : undefined,
|
|
214
|
+
cachedTokens: typeof data["cached_tokens"] === "number" ? data["cached_tokens"] : undefined,
|
|
215
|
+
latencyMs: typeof data["latency_ms"] === "number" ? data["latency_ms"] : undefined,
|
|
216
|
+
serviceName: _optString(data["service_name"]),
|
|
217
|
+
isRetry: data["is_retry"] === true,
|
|
218
|
+
retryReason: _optString(data["retry_reason"]),
|
|
219
|
+
retryOf: _optString(data["retry_of"]),
|
|
220
|
+
details: data["details"] && typeof data["details"] === "object"
|
|
221
|
+
? data["details"]
|
|
222
|
+
: {},
|
|
223
|
+
schemaVersion: _optString(data["schema_version"]) ?? "1",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/core/models.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NVML reader — Phase 2 GPU foundation.
|
|
3
|
+
*
|
|
4
|
+
* **TypeScript architectural deviation from Python**: there is no maintained
|
|
5
|
+
* native NVML binding for Node in 2026. This module shells out to the
|
|
6
|
+
* `nvidia-smi` CLI via `child_process.spawnSync` and parses CSV output.
|
|
7
|
+
* Functions still return typed objects (ProcessInfo, UtilSample, MemInfo)
|
|
8
|
+
* so downstream callers see the same shape as the Python `pynvml`
|
|
9
|
+
* wrapper. Per-probe overhead is ~50ms (acceptable for finalize-time use;
|
|
10
|
+
* not suitable for hot-path sampling — but neither is NVML).
|
|
11
|
+
*
|
|
12
|
+
* Fail-silent contract (convention §9) identical to Python:
|
|
13
|
+
* every reader returns `null` (or `false` for booleans) on missing binary,
|
|
14
|
+
* non-zero exit, or parse failure — the caller (GpuAccountant) decides
|
|
15
|
+
* the fallback policy per Decision #1's classification table.
|
|
16
|
+
*
|
|
17
|
+
* **Browser safety**: nvmlAvailable() short-circuits to false off-Node.
|
|
18
|
+
*
|
|
19
|
+
* Decision #4: getProductName applies NFC + lowercase + whitespace collapse
|
|
20
|
+
* on the raw nvidia-smi product-name output before returning — catalog
|
|
21
|
+
* alias matching depends on byte-level equality after normalization.
|
|
22
|
+
*
|
|
23
|
+
* Decision #8: getProcessUtilization takes a mutable `lastSeenTimestamps`
|
|
24
|
+
* dict and updates it in place. The TS implementation uses
|
|
25
|
+
* `process.hrtime.bigint() → ms` for the per-PID timestamp (nvidia-smi pmon
|
|
26
|
+
* does not expose NVML's sample-buffer timestamp). The accountant uses
|
|
27
|
+
* (endTs - startTs) ms as the per-PID active window proxy.
|
|
28
|
+
*/
|
|
29
|
+
import type { SpawnSyncReturns } from "node:child_process";
|
|
30
|
+
type SpawnFn = (cmd: string, args: string[], options: {
|
|
31
|
+
encoding: "utf-8";
|
|
32
|
+
timeout: number;
|
|
33
|
+
}) => SpawnSyncReturns<string>;
|
|
34
|
+
/** Test-only — override the spawnSync implementation. Pass `null` to restore. */
|
|
35
|
+
export declare function _setSpawnFnForTests(fn: SpawnFn | null): void;
|
|
36
|
+
export declare function _resetWarningStateForTests(): void;
|
|
37
|
+
/** Test-only — override the nvidia-smi binary path. Pass `null` to restore. */
|
|
38
|
+
export declare function _setNvidiaSmiPathForTests(p: string | null): void;
|
|
39
|
+
export interface ProcessInfo {
|
|
40
|
+
/** PID of the process holding the GPU. */
|
|
41
|
+
pid: number;
|
|
42
|
+
/** VRAM used by this process in bytes (0 when nvidia-smi reports N/A). */
|
|
43
|
+
usedGpuMemory: number;
|
|
44
|
+
}
|
|
45
|
+
export interface UtilSample {
|
|
46
|
+
pid: number;
|
|
47
|
+
/** 0–100; percent of time SMs had ≥1 kernel running during the sample window. */
|
|
48
|
+
smUtil: number;
|
|
49
|
+
/** 0–100; percent of time memory subsystem was busy. */
|
|
50
|
+
memUtil: number;
|
|
51
|
+
/** Per-PID timestamp (milliseconds since process start) for Decision #8 state. */
|
|
52
|
+
timeStamp: number;
|
|
53
|
+
}
|
|
54
|
+
export interface MemInfo {
|
|
55
|
+
usedBytes: number;
|
|
56
|
+
totalBytes: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* True when `nvidia-smi` is invokable and exits 0 on the count query.
|
|
60
|
+
*
|
|
61
|
+
* The TS-SDK definition of "available" is "binary works"; in Python this
|
|
62
|
+
* was "pynvml importable AND nvmlInit succeeds". Same end behaviour:
|
|
63
|
+
* downstream code checks this predicate before calling readers.
|
|
64
|
+
*/
|
|
65
|
+
export declare function nvmlAvailable(): boolean;
|
|
66
|
+
/** Call `nvidia-smi --query-gpu=count` once. True on success, false on any failure. */
|
|
67
|
+
export declare function initNvml(): boolean;
|
|
68
|
+
/** No-op on TS; the CLI has no persistent handle. Provided for API parity. */
|
|
69
|
+
export declare function shutdownNvml(): void;
|
|
70
|
+
/** Number of NVIDIA devices visible to nvidia-smi. `null` on failure. */
|
|
71
|
+
export declare function getDeviceCount(): number | null;
|
|
72
|
+
/**
|
|
73
|
+
* Return the nvidia-smi product-name for `index`, NFC-normalized + lowercased.
|
|
74
|
+
*
|
|
75
|
+
* Decision #4 — alias matching against the catalog depends on byte-level
|
|
76
|
+
* equality post-normalization.
|
|
77
|
+
*/
|
|
78
|
+
export declare function getProductName(index: number): string | null;
|
|
79
|
+
/**
|
|
80
|
+
* List of PIDs currently holding GPU `index` + their VRAM usage in bytes.
|
|
81
|
+
*
|
|
82
|
+
* Returns `null` on permission denied / failure — the Decision #1
|
|
83
|
+
* load-bearing case for non-root containers. The accountant's cgroup walk
|
|
84
|
+
* then degrades to self-PID-only.
|
|
85
|
+
*/
|
|
86
|
+
export declare function getComputeRunningProcesses(index: number): ProcessInfo[] | null;
|
|
87
|
+
/**
|
|
88
|
+
* Per-PID utilization sampled from `nvidia-smi pmon -c 1 -s u`.
|
|
89
|
+
*
|
|
90
|
+
* Decision #8 — TS uses `Date.now()` as the per-PID timestamp proxy
|
|
91
|
+
* because nvidia-smi pmon does not expose the NVML sample-buffer epoch.
|
|
92
|
+
* The accountant uses (endTs - startTs) ms as the active-GPU-time
|
|
93
|
+
* approximation between snapshots.
|
|
94
|
+
*
|
|
95
|
+
* Updates `lastSeenTimestamps` in place — callers persist across snapshots.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Returns multi-sample-per-PID utilization data.
|
|
99
|
+
*
|
|
100
|
+
* Sprint 2 Theme C / §3.1.1 (B2 TS port). Pre-fix the return type
|
|
101
|
+
* was `Record<number, UtilSample>` — single-latest-sample-per-PID,
|
|
102
|
+
* collapsing the integration data the accountant needs. Now returns
|
|
103
|
+
* `Record<number, UtilSample[]>` with samples sorted by `timeStamp`
|
|
104
|
+
* ascending. nvidia-smi pmon returns one snapshot per call, so the
|
|
105
|
+
* array length is 1 in practice; the type accommodates future
|
|
106
|
+
* sampling implementations that batch multiple observations.
|
|
107
|
+
*/
|
|
108
|
+
export declare function getProcessUtilization(index: number, lastSeenTimestamps: Record<number, number>): Record<number, UtilSample[]> | null;
|
|
109
|
+
/** Device-level used / total VRAM (bytes). `null` on failure. */
|
|
110
|
+
export declare function getMemoryInfo(index: number): MemInfo | null;
|
|
111
|
+
/** True when MIG is enabled on this device (Decision #2 detection). */
|
|
112
|
+
export declare function getMigMode(index: number): boolean;
|
|
113
|
+
export {};
|
|
114
|
+
//# sourceMappingURL=nvml-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nvml-reader.d.ts","sourceRoot":"","sources":["../../src/core/nvml-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D,KAAK,OAAO,GAAG,CACb,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KAC5C,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAK9B,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAK5D;AAMD,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAmBD,+EAA+E;AAC/E,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEhE;AAID,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AA6BD;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAYvC;AAED,uFAAuF;AACvF,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAID,yEAAyE;AACzE,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAc9C;AAID;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkB3D;AAID;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,GAAG,IAAI,CA8B9E;AAID;;;;;;;;;GASG;AACH;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACzC,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,GAAG,IAAI,CAmDrC;AAID,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CA0B3D;AAED,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAejD"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NVML reader — Phase 2 GPU foundation.
|
|
3
|
+
*
|
|
4
|
+
* **TypeScript architectural deviation from Python**: there is no maintained
|
|
5
|
+
* native NVML binding for Node in 2026. This module shells out to the
|
|
6
|
+
* `nvidia-smi` CLI via `child_process.spawnSync` and parses CSV output.
|
|
7
|
+
* Functions still return typed objects (ProcessInfo, UtilSample, MemInfo)
|
|
8
|
+
* so downstream callers see the same shape as the Python `pynvml`
|
|
9
|
+
* wrapper. Per-probe overhead is ~50ms (acceptable for finalize-time use;
|
|
10
|
+
* not suitable for hot-path sampling — but neither is NVML).
|
|
11
|
+
*
|
|
12
|
+
* Fail-silent contract (convention §9) identical to Python:
|
|
13
|
+
* every reader returns `null` (or `false` for booleans) on missing binary,
|
|
14
|
+
* non-zero exit, or parse failure — the caller (GpuAccountant) decides
|
|
15
|
+
* the fallback policy per Decision #1's classification table.
|
|
16
|
+
*
|
|
17
|
+
* **Browser safety**: nvmlAvailable() short-circuits to false off-Node.
|
|
18
|
+
*
|
|
19
|
+
* Decision #4: getProductName applies NFC + lowercase + whitespace collapse
|
|
20
|
+
* on the raw nvidia-smi product-name output before returning — catalog
|
|
21
|
+
* alias matching depends on byte-level equality after normalization.
|
|
22
|
+
*
|
|
23
|
+
* Decision #8: getProcessUtilization takes a mutable `lastSeenTimestamps`
|
|
24
|
+
* dict and updates it in place. The TS implementation uses
|
|
25
|
+
* `process.hrtime.bigint() → ms` for the per-PID timestamp (nvidia-smi pmon
|
|
26
|
+
* does not expose NVML's sample-buffer timestamp). The accountant uses
|
|
27
|
+
* (endTs - startTs) ms as the per-PID active window proxy.
|
|
28
|
+
*/
|
|
29
|
+
import { spawnSync as nodeSpawnSync } from "node:child_process";
|
|
30
|
+
let _spawnFn = (cmd, args, options) => nodeSpawnSync(cmd, args, options);
|
|
31
|
+
/** Test-only — override the spawnSync implementation. Pass `null` to restore. */
|
|
32
|
+
export function _setSpawnFnForTests(fn) {
|
|
33
|
+
_spawnFn =
|
|
34
|
+
fn ??
|
|
35
|
+
((cmd, args, options) => nodeSpawnSync(cmd, args, options));
|
|
36
|
+
}
|
|
37
|
+
// ─── Warn-once tracking (single-threaded JS → no lock) ──────────────────────
|
|
38
|
+
const _warnedModes = new Set();
|
|
39
|
+
export function _resetWarningStateForTests() {
|
|
40
|
+
_warnedModes.clear();
|
|
41
|
+
}
|
|
42
|
+
function _warnOnce(mode, message) {
|
|
43
|
+
if (_warnedModes.has(mode))
|
|
44
|
+
return;
|
|
45
|
+
_warnedModes.add(mode);
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.warn(message);
|
|
48
|
+
}
|
|
49
|
+
// ─── Browser-safety guard ───────────────────────────────────────────────────
|
|
50
|
+
function _isNode() {
|
|
51
|
+
return typeof process !== "undefined" && !!process.versions?.node;
|
|
52
|
+
}
|
|
53
|
+
// ─── Test-only nvidia-smi path override ─────────────────────────────────────
|
|
54
|
+
let _nvidiaSmiPath = "nvidia-smi";
|
|
55
|
+
/** Test-only — override the nvidia-smi binary path. Pass `null` to restore. */
|
|
56
|
+
export function _setNvidiaSmiPathForTests(p) {
|
|
57
|
+
_nvidiaSmiPath = p ?? "nvidia-smi";
|
|
58
|
+
}
|
|
59
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
60
|
+
function _runSmi(args) {
|
|
61
|
+
if (!_isNode())
|
|
62
|
+
return { stdout: "", ok: false };
|
|
63
|
+
try {
|
|
64
|
+
const r = _spawnFn(_nvidiaSmiPath, args, {
|
|
65
|
+
encoding: "utf-8",
|
|
66
|
+
timeout: 5000,
|
|
67
|
+
});
|
|
68
|
+
if (r.error || r.status !== 0) {
|
|
69
|
+
return { stdout: "", ok: false };
|
|
70
|
+
}
|
|
71
|
+
return { stdout: r.stdout ?? "", ok: true };
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return { stdout: "", ok: false };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function _normalizeProductName(raw) {
|
|
78
|
+
// NFC → lowercase → whitespace collapse (incl. NBSP / NNBSP / zero-width).
|
|
79
|
+
const nfc = raw.normalize("NFC");
|
|
80
|
+
// String.split(/\s+/) treats Unicode whitespace incl. U+00A0 NBSP as a delimiter.
|
|
81
|
+
return nfc.toLowerCase().split(/\s+/).filter(Boolean).join(" ");
|
|
82
|
+
}
|
|
83
|
+
// ─── Availability + init ────────────────────────────────────────────────────
|
|
84
|
+
/**
|
|
85
|
+
* True when `nvidia-smi` is invokable and exits 0 on the count query.
|
|
86
|
+
*
|
|
87
|
+
* The TS-SDK definition of "available" is "binary works"; in Python this
|
|
88
|
+
* was "pynvml importable AND nvmlInit succeeds". Same end behaviour:
|
|
89
|
+
* downstream code checks this predicate before calling readers.
|
|
90
|
+
*/
|
|
91
|
+
export function nvmlAvailable() {
|
|
92
|
+
if (!_isNode())
|
|
93
|
+
return false;
|
|
94
|
+
const r = _runSmi(["--query-gpu=count", "--format=csv,noheader"]);
|
|
95
|
+
if (!r.ok) {
|
|
96
|
+
_warnOnce("gpu_nvidia_smi_not_found", "nvidia-smi not on PATH or exited non-zero; GPU capture disabled. " +
|
|
97
|
+
"Install NVIDIA driver + container toolkit for GPU cost attribution.");
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
/** Call `nvidia-smi --query-gpu=count` once. True on success, false on any failure. */
|
|
103
|
+
export function initNvml() {
|
|
104
|
+
return nvmlAvailable();
|
|
105
|
+
}
|
|
106
|
+
/** No-op on TS; the CLI has no persistent handle. Provided for API parity. */
|
|
107
|
+
export function shutdownNvml() {
|
|
108
|
+
/* no-op */
|
|
109
|
+
}
|
|
110
|
+
// ─── Device enumeration ─────────────────────────────────────────────────────
|
|
111
|
+
/** Number of NVIDIA devices visible to nvidia-smi. `null` on failure. */
|
|
112
|
+
export function getDeviceCount() {
|
|
113
|
+
if (!_isNode())
|
|
114
|
+
return null;
|
|
115
|
+
const r = _runSmi(["--query-gpu=count", "--format=csv,noheader"]);
|
|
116
|
+
if (!r.ok)
|
|
117
|
+
return null;
|
|
118
|
+
// nvidia-smi --query-gpu=count emits one line per GPU, all with the same count value.
|
|
119
|
+
// Take the first non-empty line.
|
|
120
|
+
const first = r.stdout.split("\n").map((s) => s.trim()).find((s) => s);
|
|
121
|
+
if (!first)
|
|
122
|
+
return null;
|
|
123
|
+
const n = parseInt(first, 10);
|
|
124
|
+
if (Number.isNaN(n)) {
|
|
125
|
+
_warnOnce("gpu_device_count_parse", `nvidia-smi count unparseable: ${first}`);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return n;
|
|
129
|
+
}
|
|
130
|
+
// ─── Decision #4: NFC-normalized productName ────────────────────────────────
|
|
131
|
+
/**
|
|
132
|
+
* Return the nvidia-smi product-name for `index`, NFC-normalized + lowercased.
|
|
133
|
+
*
|
|
134
|
+
* Decision #4 — alias matching against the catalog depends on byte-level
|
|
135
|
+
* equality post-normalization.
|
|
136
|
+
*/
|
|
137
|
+
export function getProductName(index) {
|
|
138
|
+
if (!_isNode())
|
|
139
|
+
return null;
|
|
140
|
+
const r = _runSmi([
|
|
141
|
+
`--query-gpu=name`,
|
|
142
|
+
`--format=csv,noheader`,
|
|
143
|
+
`-i`,
|
|
144
|
+
String(index),
|
|
145
|
+
]);
|
|
146
|
+
if (!r.ok) {
|
|
147
|
+
_warnOnce("gpu_product_name_failed", `nvidia-smi --query-gpu=name failed for device ${index}`);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const line = r.stdout.split("\n").map((s) => s.trim()).find((s) => s);
|
|
151
|
+
if (!line)
|
|
152
|
+
return null;
|
|
153
|
+
return _normalizeProductName(line);
|
|
154
|
+
}
|
|
155
|
+
// ─── Per-PID compute processes (Decision #1 measurement-side primitive) ────
|
|
156
|
+
/**
|
|
157
|
+
* List of PIDs currently holding GPU `index` + their VRAM usage in bytes.
|
|
158
|
+
*
|
|
159
|
+
* Returns `null` on permission denied / failure — the Decision #1
|
|
160
|
+
* load-bearing case for non-root containers. The accountant's cgroup walk
|
|
161
|
+
* then degrades to self-PID-only.
|
|
162
|
+
*/
|
|
163
|
+
export function getComputeRunningProcesses(index) {
|
|
164
|
+
if (!_isNode())
|
|
165
|
+
return null;
|
|
166
|
+
const r = _runSmi([
|
|
167
|
+
`--query-compute-apps=pid,used_memory`,
|
|
168
|
+
`--format=csv,noheader,nounits`,
|
|
169
|
+
`-i`,
|
|
170
|
+
String(index),
|
|
171
|
+
]);
|
|
172
|
+
if (!r.ok) {
|
|
173
|
+
_warnOnce("gpu_nvml_permission_denied", `nvidia-smi --query-compute-apps failed for device ${index}; ` +
|
|
174
|
+
`GpuAccountant will degrade to self-PID-only`);
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const out = [];
|
|
178
|
+
for (const line of r.stdout.split("\n")) {
|
|
179
|
+
const s = line.trim();
|
|
180
|
+
if (!s)
|
|
181
|
+
continue;
|
|
182
|
+
const parts = s.split(",").map((p) => p.trim());
|
|
183
|
+
if (parts.length < 2)
|
|
184
|
+
continue;
|
|
185
|
+
const pid = parseInt(parts[0], 10);
|
|
186
|
+
if (Number.isNaN(pid))
|
|
187
|
+
continue;
|
|
188
|
+
let memMib = parseInt(parts[1], 10);
|
|
189
|
+
if (Number.isNaN(memMib))
|
|
190
|
+
memMib = 0;
|
|
191
|
+
// nounits ⇒ used_memory is MiB.
|
|
192
|
+
out.push({ pid, usedGpuMemory: memMib * 1024 * 1024 });
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
// ─── Per-PID utilization (Decision #8 persistent timestamps) ────────────────
|
|
197
|
+
/**
|
|
198
|
+
* Per-PID utilization sampled from `nvidia-smi pmon -c 1 -s u`.
|
|
199
|
+
*
|
|
200
|
+
* Decision #8 — TS uses `Date.now()` as the per-PID timestamp proxy
|
|
201
|
+
* because nvidia-smi pmon does not expose the NVML sample-buffer epoch.
|
|
202
|
+
* The accountant uses (endTs - startTs) ms as the active-GPU-time
|
|
203
|
+
* approximation between snapshots.
|
|
204
|
+
*
|
|
205
|
+
* Updates `lastSeenTimestamps` in place — callers persist across snapshots.
|
|
206
|
+
*/
|
|
207
|
+
/**
|
|
208
|
+
* Returns multi-sample-per-PID utilization data.
|
|
209
|
+
*
|
|
210
|
+
* Sprint 2 Theme C / §3.1.1 (B2 TS port). Pre-fix the return type
|
|
211
|
+
* was `Record<number, UtilSample>` — single-latest-sample-per-PID,
|
|
212
|
+
* collapsing the integration data the accountant needs. Now returns
|
|
213
|
+
* `Record<number, UtilSample[]>` with samples sorted by `timeStamp`
|
|
214
|
+
* ascending. nvidia-smi pmon returns one snapshot per call, so the
|
|
215
|
+
* array length is 1 in practice; the type accommodates future
|
|
216
|
+
* sampling implementations that batch multiple observations.
|
|
217
|
+
*/
|
|
218
|
+
export function getProcessUtilization(index, lastSeenTimestamps) {
|
|
219
|
+
if (!_isNode())
|
|
220
|
+
return null;
|
|
221
|
+
const r = _runSmi([
|
|
222
|
+
`pmon`,
|
|
223
|
+
`-c`,
|
|
224
|
+
`1`,
|
|
225
|
+
`-s`,
|
|
226
|
+
`u`,
|
|
227
|
+
`-o`,
|
|
228
|
+
`T`,
|
|
229
|
+
`-i`,
|
|
230
|
+
String(index),
|
|
231
|
+
]);
|
|
232
|
+
if (!r.ok) {
|
|
233
|
+
_warnOnce("gpu_process_utilization_failed", `nvidia-smi pmon failed for device ${index}`);
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const out = {};
|
|
237
|
+
const nowMs = Date.now();
|
|
238
|
+
for (const rawLine of r.stdout.split("\n")) {
|
|
239
|
+
const line = rawLine.trim();
|
|
240
|
+
if (!line)
|
|
241
|
+
continue;
|
|
242
|
+
// Skip header lines and N/A rows.
|
|
243
|
+
if (line.startsWith("#"))
|
|
244
|
+
continue;
|
|
245
|
+
// Whitespace-separated columns: gpu pid type sm mem enc dec command
|
|
246
|
+
const parts = line.split(/\s+/);
|
|
247
|
+
if (parts.length < 5)
|
|
248
|
+
continue;
|
|
249
|
+
const pid = parseInt(parts[1], 10);
|
|
250
|
+
if (Number.isNaN(pid))
|
|
251
|
+
continue;
|
|
252
|
+
const sm = parseInt(parts[3], 10);
|
|
253
|
+
const mem = parseInt(parts[4], 10);
|
|
254
|
+
if (Number.isNaN(sm) || Number.isNaN(mem))
|
|
255
|
+
continue;
|
|
256
|
+
const sample = { pid, smUtil: sm, memUtil: mem, timeStamp: nowMs };
|
|
257
|
+
if (out[pid]) {
|
|
258
|
+
out[pid].push(sample);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
out[pid] = [sample];
|
|
262
|
+
}
|
|
263
|
+
if (nowMs > (lastSeenTimestamps[pid] ?? 0)) {
|
|
264
|
+
lastSeenTimestamps[pid] = nowMs;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Sort each PID's samples by timestamp ascending — the integrator
|
|
268
|
+
// assumes monotonic ordering.
|
|
269
|
+
for (const pid in out) {
|
|
270
|
+
out[pid].sort((a, b) => a.timeStamp - b.timeStamp);
|
|
271
|
+
}
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
// ─── Memory + MIG ───────────────────────────────────────────────────────────
|
|
275
|
+
/** Device-level used / total VRAM (bytes). `null` on failure. */
|
|
276
|
+
export function getMemoryInfo(index) {
|
|
277
|
+
if (!_isNode())
|
|
278
|
+
return null;
|
|
279
|
+
const r = _runSmi([
|
|
280
|
+
`--query-gpu=memory.used,memory.total`,
|
|
281
|
+
`--format=csv,noheader,nounits`,
|
|
282
|
+
`-i`,
|
|
283
|
+
String(index),
|
|
284
|
+
]);
|
|
285
|
+
if (!r.ok) {
|
|
286
|
+
_warnOnce("gpu_memory_info_failed", `nvidia-smi --query-gpu=memory.used,memory.total failed for device ${index}`);
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const line = r.stdout.split("\n").map((s) => s.trim()).find((s) => s);
|
|
290
|
+
if (!line)
|
|
291
|
+
return null;
|
|
292
|
+
const parts = line.split(",").map((s) => s.trim());
|
|
293
|
+
if (parts.length < 2)
|
|
294
|
+
return null;
|
|
295
|
+
const usedMib = parseInt(parts[0], 10);
|
|
296
|
+
const totalMib = parseInt(parts[1], 10);
|
|
297
|
+
if (Number.isNaN(usedMib) || Number.isNaN(totalMib))
|
|
298
|
+
return null;
|
|
299
|
+
return {
|
|
300
|
+
usedBytes: usedMib * 1024 * 1024,
|
|
301
|
+
totalBytes: totalMib * 1024 * 1024,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/** True when MIG is enabled on this device (Decision #2 detection). */
|
|
305
|
+
export function getMigMode(index) {
|
|
306
|
+
if (!_isNode())
|
|
307
|
+
return false;
|
|
308
|
+
const r = _runSmi([
|
|
309
|
+
`--query-gpu=mig.mode.current`,
|
|
310
|
+
`--format=csv,noheader`,
|
|
311
|
+
`-i`,
|
|
312
|
+
String(index),
|
|
313
|
+
]);
|
|
314
|
+
if (!r.ok) {
|
|
315
|
+
// Older GPUs without MIG support → fail-silent (NOT an error).
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
const line = r.stdout.split("\n").map((s) => s.trim()).find((s) => s);
|
|
319
|
+
if (!line)
|
|
320
|
+
return false;
|
|
321
|
+
return line.toLowerCase() === "enabled";
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=nvml-reader.js.map
|