@intelligems/sst 2.49.6-ig.1 → 2.49.6-ig.10
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/cli/commands/dev.js +69 -3
- package/cli/sst.js +0 -1
- package/iot.d.ts +31 -0
- package/iot.js +169 -8
- package/package.json +2 -2
- package/runtime/debug-bridge-logging.d.ts +24 -0
- package/runtime/debug-bridge-logging.js +98 -0
- package/runtime/debug-file-logger.d.ts +80 -0
- package/runtime/debug-file-logger.js +148 -0
- package/runtime/event-trace-logging.d.ts +27 -0
- package/runtime/event-trace-logging.js +69 -0
- package/runtime/handlers/node.js +62 -21
- package/runtime/handlers.d.ts +6 -2
- package/runtime/handlers.js +130 -59
- package/runtime/iot.js +31 -4
- package/runtime/mono-build-config.d.ts +58 -0
- package/runtime/mono-build-config.js +104 -0
- package/runtime/request-utils.d.ts +21 -0
- package/runtime/request-utils.js +102 -0
- package/runtime/runtime.d.ts +1 -0
- package/runtime/server.d.ts +4 -1
- package/runtime/server.js +122 -29
- package/runtime/worker-pool-logging.d.ts +29 -0
- package/runtime/worker-pool-logging.js +185 -0
- package/runtime/workers.d.ts +39 -7
- package/runtime/workers.js +831 -38
- package/support/bridge/live-lambda.mjs +38 -38
- package/support/nodejs-runtime/index.mjs +44 -11
- package/support/python-runtime/runtime.py +116 -45
- package/README.md +0 -43
- package/package.json.bak +0 -156
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { createDebugFileLogger } from "./debug-file-logger.js";
|
|
2
|
+
// Configuration
|
|
3
|
+
const SST_BUILD_CONCURRENCY = parseInt(process.env.SST_BUILD_CONCURRENCY || "4", 10);
|
|
4
|
+
export const POOL_SIZE = parseInt(process.env.SST_WORKER_POOL_SIZE || "10", 10);
|
|
5
|
+
export const IDLE_TIMEOUT = parseInt(process.env.SST_WORKER_IDLE_TIMEOUT || "60000", 10);
|
|
6
|
+
const DEBUG_POOL = process.env.SST_DEBUG_POOL === "true";
|
|
7
|
+
const DEBUG_POOL_FILE = process.env.SST_DEBUG_POOL_FILE || ".sst/worker-pool.log";
|
|
8
|
+
const INVOKE_TRACE_FILE = ".sst/invoke-trace.log";
|
|
9
|
+
const metrics = {
|
|
10
|
+
coldStarts: new Map(),
|
|
11
|
+
concurrentRequests: new Map(),
|
|
12
|
+
peakConcurrent: new Map(),
|
|
13
|
+
totalRequests: new Map(),
|
|
14
|
+
poolHits: new Map(),
|
|
15
|
+
avgResponseTime: new Map(),
|
|
16
|
+
responseCount: new Map(),
|
|
17
|
+
};
|
|
18
|
+
// Create pool logger using the abstract logger
|
|
19
|
+
let poolLogger = null;
|
|
20
|
+
function getPoolLogger() {
|
|
21
|
+
if (!DEBUG_POOL)
|
|
22
|
+
return null;
|
|
23
|
+
if (!poolLogger) {
|
|
24
|
+
poolLogger = createDebugFileLogger({
|
|
25
|
+
filePath: DEBUG_POOL_FILE,
|
|
26
|
+
sessionName: "POOL",
|
|
27
|
+
sessionHeader: `POOL_SIZE=${POOL_SIZE} IDLE_TIMEOUT=${IDLE_TIMEOUT}ms BUILD_CONCURRENCY=${SST_BUILD_CONCURRENCY}`,
|
|
28
|
+
width: 100,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return poolLogger;
|
|
32
|
+
}
|
|
33
|
+
// Function name resolver - set by workers.ts
|
|
34
|
+
let functionNameResolver = (id) => id.slice(0, 25);
|
|
35
|
+
/**
|
|
36
|
+
* Set the function name resolver callback
|
|
37
|
+
* Called by workers.ts to provide access to useFunctions()
|
|
38
|
+
*/
|
|
39
|
+
export function setFunctionNameResolver(resolver) {
|
|
40
|
+
functionNameResolver = resolver;
|
|
41
|
+
}
|
|
42
|
+
// Extract readable function name from handler path
|
|
43
|
+
function getFunctionName(functionID) {
|
|
44
|
+
return functionNameResolver(functionID);
|
|
45
|
+
}
|
|
46
|
+
// Calculate bottleneck indicators
|
|
47
|
+
function getBottleneckFlags(functionID) {
|
|
48
|
+
const flags = [];
|
|
49
|
+
const concurrent = metrics.concurrentRequests.get(functionID) || 0;
|
|
50
|
+
const total = metrics.totalRequests.get(functionID) || 0;
|
|
51
|
+
const hits = metrics.poolHits.get(functionID) || 0;
|
|
52
|
+
const hitRate = total > 0 ? (hits / total) * 100 : 0;
|
|
53
|
+
const coldStarts = metrics.coldStarts.get(functionID) || 0;
|
|
54
|
+
// SATURATED: All pool slots in use
|
|
55
|
+
if (concurrent >= POOL_SIZE) {
|
|
56
|
+
flags.push("SATURATED");
|
|
57
|
+
}
|
|
58
|
+
// HIGH_LOAD: >70% pool utilization
|
|
59
|
+
else if (concurrent >= POOL_SIZE * 0.7) {
|
|
60
|
+
flags.push("HIGH_LOAD");
|
|
61
|
+
}
|
|
62
|
+
// COLD_START: Low hit rate indicates frequent cold starts
|
|
63
|
+
if (total >= 5 && hitRate < 30) {
|
|
64
|
+
flags.push("LOW_REUSE");
|
|
65
|
+
}
|
|
66
|
+
// BOTTLENECK: High cold start ratio
|
|
67
|
+
if (total >= 3 && coldStarts / total > 0.5) {
|
|
68
|
+
flags.push("COLD_HEAVY");
|
|
69
|
+
}
|
|
70
|
+
return flags.length > 0 ? " [" + flags.join(" ") + "]" : "";
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Pool debug logging helper - writes to file with bottleneck detection
|
|
74
|
+
*/
|
|
75
|
+
export function logPool(action, details = {}) {
|
|
76
|
+
const logger = getPoolLogger();
|
|
77
|
+
if (!logger)
|
|
78
|
+
return;
|
|
79
|
+
const funcName = details.functionID
|
|
80
|
+
? getFunctionName(details.functionID)
|
|
81
|
+
: "";
|
|
82
|
+
const bottleneckFlags = details.functionID &&
|
|
83
|
+
["CREATE", "REUSE", "POOL_MISS", "RESPONSE"].includes(action)
|
|
84
|
+
? getBottleneckFlags(details.functionID)
|
|
85
|
+
: "";
|
|
86
|
+
// Build metrics string for key actions
|
|
87
|
+
let metricsStr = "";
|
|
88
|
+
if (details.functionID && ["RESPONSE", "RETURN_TO_POOL"].includes(action)) {
|
|
89
|
+
const concurrent = metrics.concurrentRequests.get(details.functionID) || 0;
|
|
90
|
+
const total = metrics.totalRequests.get(details.functionID) || 0;
|
|
91
|
+
const hits = metrics.poolHits.get(details.functionID) || 0;
|
|
92
|
+
const hitRate = total > 0 ? ((hits / total) * 100).toFixed(0) : "0";
|
|
93
|
+
metricsStr = `concurrent=${concurrent} hitRate=${hitRate}%`;
|
|
94
|
+
}
|
|
95
|
+
// Build the log details
|
|
96
|
+
const logDetails = {};
|
|
97
|
+
// Add function name first if present
|
|
98
|
+
if (funcName) {
|
|
99
|
+
logDetails.func = funcName;
|
|
100
|
+
}
|
|
101
|
+
// Add all other details except functionID
|
|
102
|
+
for (const [k, v] of Object.entries(details)) {
|
|
103
|
+
if (k !== "functionID") {
|
|
104
|
+
logDetails[k] = v;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Add metrics if present
|
|
108
|
+
if (metricsStr) {
|
|
109
|
+
logDetails.metrics = metricsStr;
|
|
110
|
+
}
|
|
111
|
+
// Add bottleneck flags
|
|
112
|
+
if (bottleneckFlags) {
|
|
113
|
+
logDetails.status = bottleneckFlags.trim();
|
|
114
|
+
}
|
|
115
|
+
logger.log(action, logDetails);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Track request lifecycle for metrics - called at request start
|
|
119
|
+
*/
|
|
120
|
+
export function trackRequestStart(functionID, isPoolHit) {
|
|
121
|
+
const current = (metrics.concurrentRequests.get(functionID) || 0) + 1;
|
|
122
|
+
metrics.concurrentRequests.set(functionID, current);
|
|
123
|
+
metrics.totalRequests.set(functionID, (metrics.totalRequests.get(functionID) || 0) + 1);
|
|
124
|
+
const peak = metrics.peakConcurrent.get(functionID) || 0;
|
|
125
|
+
if (current > peak) {
|
|
126
|
+
metrics.peakConcurrent.set(functionID, current);
|
|
127
|
+
}
|
|
128
|
+
if (isPoolHit) {
|
|
129
|
+
metrics.poolHits.set(functionID, (metrics.poolHits.get(functionID) || 0) + 1);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
metrics.coldStarts.set(functionID, (metrics.coldStarts.get(functionID) || 0) + 1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Track request lifecycle for metrics - called at request end
|
|
137
|
+
*/
|
|
138
|
+
export function trackRequestEnd(functionID) {
|
|
139
|
+
const current = metrics.concurrentRequests.get(functionID) || 0;
|
|
140
|
+
if (current > 0) {
|
|
141
|
+
metrics.concurrentRequests.set(functionID, current - 1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Write session end summary and close log stream
|
|
146
|
+
* Called from workers.ts on process exit
|
|
147
|
+
*/
|
|
148
|
+
export function writeSessionEndSummary() {
|
|
149
|
+
const logger = getPoolLogger();
|
|
150
|
+
if (!logger)
|
|
151
|
+
return;
|
|
152
|
+
let summary = "";
|
|
153
|
+
for (const [funcID, total] of metrics.totalRequests) {
|
|
154
|
+
const hits = metrics.poolHits.get(funcID) || 0;
|
|
155
|
+
const cold = metrics.coldStarts.get(funcID) || 0;
|
|
156
|
+
const peak = metrics.peakConcurrent.get(funcID) || 0;
|
|
157
|
+
const hitRate = total > 0 ? ((hits / total) * 100).toFixed(1) : "0";
|
|
158
|
+
const funcName = getFunctionName(funcID);
|
|
159
|
+
summary += ` ${funcName.padEnd(25)} total=${total} poolHits=${hits} coldStarts=${cold} peakConcurrent=${peak} hitRate=${hitRate}%\n`;
|
|
160
|
+
}
|
|
161
|
+
logger.close(summary);
|
|
162
|
+
}
|
|
163
|
+
// Invocation trace logging - uses the abstract logger
|
|
164
|
+
let traceLogger = null;
|
|
165
|
+
function getTraceLogger() {
|
|
166
|
+
if (!traceLogger) {
|
|
167
|
+
traceLogger = createDebugFileLogger({
|
|
168
|
+
filePath: INVOKE_TRACE_FILE,
|
|
169
|
+
sessionName: "TRACE",
|
|
170
|
+
width: 80,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return traceLogger;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Log invocation trace events to .sst/invoke-trace.log
|
|
177
|
+
* Always enabled for debugging invocation flow
|
|
178
|
+
*/
|
|
179
|
+
export function logInvokeTrace(stage, requestID, details) {
|
|
180
|
+
const logger = getTraceLogger();
|
|
181
|
+
logger.log(stage, {
|
|
182
|
+
req: requestID.slice(0, 8),
|
|
183
|
+
...(details ? { info: details } : {}),
|
|
184
|
+
});
|
|
185
|
+
}
|
package/runtime/workers.d.ts
CHANGED
|
@@ -18,20 +18,52 @@ declare module "../bus.js" {
|
|
|
18
18
|
requestID: string;
|
|
19
19
|
message: string;
|
|
20
20
|
};
|
|
21
|
+
"worker.reused": {
|
|
22
|
+
workerID: string;
|
|
23
|
+
functionID: string;
|
|
24
|
+
pooledWorkerID: string;
|
|
25
|
+
};
|
|
26
|
+
"warmup.start": {
|
|
27
|
+
count: number;
|
|
28
|
+
};
|
|
29
|
+
"warmup.progress": {
|
|
30
|
+
completed: number;
|
|
31
|
+
total: number;
|
|
32
|
+
success: number;
|
|
33
|
+
failed: number;
|
|
34
|
+
};
|
|
35
|
+
"warmup.complete": {
|
|
36
|
+
success: number;
|
|
37
|
+
failed: number;
|
|
38
|
+
elapsedMs: number;
|
|
39
|
+
};
|
|
21
40
|
}
|
|
22
41
|
}
|
|
23
|
-
interface Worker {
|
|
24
|
-
workerID: string;
|
|
25
|
-
functionID: string;
|
|
26
|
-
}
|
|
27
42
|
export declare const useRuntimeWorkers: () => Promise<{
|
|
28
|
-
fromID(workerID: string):
|
|
43
|
+
fromID(workerID: string): {
|
|
44
|
+
workerID: string;
|
|
45
|
+
functionID: string;
|
|
46
|
+
};
|
|
29
47
|
getCurrentRequestID(workerID: string): string | undefined;
|
|
30
48
|
stdout(workerID: string, message: string): void;
|
|
31
49
|
exited(workerID: string): void;
|
|
32
|
-
|
|
50
|
+
onResponse(pooledWorkerID: string): void;
|
|
51
|
+
getAwsWorkerID(pooledWorkerID: string): string | undefined;
|
|
52
|
+
isPooled(workerID: string): boolean;
|
|
53
|
+
subscribe: <Type extends "worker.started" | "worker.stopped" | "worker.exited" | "worker.stdout" | "worker.reused">(type: Type, cb: (payload: import("../bus.js").EventPayload<Type>) => void) => {
|
|
33
54
|
type: keyof import("../bus.js").Events;
|
|
34
55
|
cb: (payload: any) => void;
|
|
35
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* Trigger warmup by invoking Lambda functions with warmup payloads.
|
|
59
|
+
* This sends real requests through the IoT bridge, which naturally creates workers.
|
|
60
|
+
* @param count Number of workers to warm up (default: 15)
|
|
61
|
+
*/
|
|
62
|
+
triggerWarmup(count?: number): Promise<{
|
|
63
|
+
warmed: number;
|
|
64
|
+
elapsed?: undefined;
|
|
65
|
+
} | {
|
|
66
|
+
warmed: number;
|
|
67
|
+
elapsed: number;
|
|
68
|
+
}>;
|
|
36
69
|
}>;
|
|
37
|
-
export {};
|