@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.
@@ -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
+ }
@@ -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): Worker;
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
- subscribe: <Type extends "worker.started" | "worker.stopped" | "worker.exited" | "worker.stdout">(type: Type, cb: (payload: import("../bus.js").EventPayload<Type>) => void) => {
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 {};