agentic-flow 1.10.0 → 1.10.1
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/dist/utils/adaptive-pool-sizing.js +414 -0
- package/dist/utils/circular-rate-limiter.js +391 -0
- package/dist/utils/dynamic-compression.js +298 -0
- package/dist/utils/http2-multiplexing.js +319 -0
- package/dist/utils/lazy-auth.js +311 -0
- package/dist/utils/server-push.js +251 -0
- package/dist/utils/zero-copy-buffer.js +286 -0
- package/docs/DOCKER-VERIFICATION.md +207 -0
- package/docs/ISSUE-55-VALIDATION.md +25 -6
- package/docs/NPX_AGENTDB_SETUP.md +175 -0
- package/docs/PHASE2-IMPLEMENTATION-SUMMARY.md +275 -0
- package/docs/PHASE2-PHASE3-COMPLETE-SUMMARY.md +453 -0
- package/docs/PHASE3-IMPLEMENTATION-SUMMARY.md +357 -0
- package/docs/PUBLISH_GUIDE.md +438 -0
- package/docs/RELEASE-v1.10.0-COMPLETE.md +382 -0
- package/docs/archive/.agentdb-instructions.md +66 -0
- package/docs/archive/AGENT-BOOSTER-STATUS.md +292 -0
- package/docs/archive/CHANGELOG-v1.3.0.md +120 -0
- package/docs/archive/COMPLETION_REPORT_v1.7.1.md +335 -0
- package/docs/archive/IMPLEMENTATION_SUMMARY_v1.7.1.md +241 -0
- package/docs/archive/SUPABASE-INTEGRATION-COMPLETE.md +357 -0
- package/docs/archive/TESTING_QUICK_START.md +223 -0
- package/docs/archive/TOOL-EMULATION-INTEGRATION-ISSUE.md +669 -0
- package/docs/archive/VALIDATION_v1.7.1.md +234 -0
- package/docs/releases/PUBLISH_CHECKLIST_v1.10.0.md +396 -0
- package/docs/releases/PUBLISH_SUMMARY_v1.7.1.md +198 -0
- package/docs/releases/RELEASE_NOTES_v1.10.0.md +464 -0
- package/docs/releases/RELEASE_NOTES_v1.7.0.md +297 -0
- package/docs/releases/RELEASE_v1.7.1.md +327 -0
- package/package.json +1 -1
- package/validation/docker-npm-validation.sh +170 -0
- package/validation/simple-npm-validation.sh +131 -0
- package/validation/test-gemini-models.ts +200 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP/2 Server Push Implementation
|
|
3
|
+
* Predictively pushes related resources to reduce latency
|
|
4
|
+
* Phase 2 Optimization
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Server Push Manager
|
|
8
|
+
* Manages HTTP/2 server push for predictive resource delivery
|
|
9
|
+
*/
|
|
10
|
+
export class ServerPushManager {
|
|
11
|
+
config;
|
|
12
|
+
pushRules;
|
|
13
|
+
pushStats;
|
|
14
|
+
activePushes = 0;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = {
|
|
17
|
+
enabled: config.enabled,
|
|
18
|
+
maxConcurrentPushes: config.maxConcurrentPushes || 5,
|
|
19
|
+
pushDelay: config.pushDelay || 0,
|
|
20
|
+
intelligentPrediction: config.intelligentPrediction !== false
|
|
21
|
+
};
|
|
22
|
+
this.pushRules = new Map();
|
|
23
|
+
this.pushStats = new Map();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register a push rule
|
|
27
|
+
*/
|
|
28
|
+
registerRule(id, rule) {
|
|
29
|
+
this.pushRules.set(id, rule);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Unregister a push rule
|
|
33
|
+
*/
|
|
34
|
+
unregisterRule(id) {
|
|
35
|
+
this.pushRules.delete(id);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Perform server push for a stream
|
|
39
|
+
*/
|
|
40
|
+
async push(stream, path, headers) {
|
|
41
|
+
if (!this.config.enabled)
|
|
42
|
+
return;
|
|
43
|
+
if (this.activePushes >= this.config.maxConcurrentPushes)
|
|
44
|
+
return;
|
|
45
|
+
// Find matching rules
|
|
46
|
+
const matchingRules = this.findMatchingRules(path, headers);
|
|
47
|
+
for (const rule of matchingRules) {
|
|
48
|
+
for (const resource of rule.resources) {
|
|
49
|
+
await this.pushResource(stream, resource);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Push a single resource
|
|
55
|
+
*/
|
|
56
|
+
async pushResource(stream, resource) {
|
|
57
|
+
if (this.activePushes >= this.config.maxConcurrentPushes)
|
|
58
|
+
return;
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
this.activePushes++;
|
|
61
|
+
const pushHeaders = {
|
|
62
|
+
':path': resource.path,
|
|
63
|
+
...resource.headers
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
stream.pushStream(pushHeaders, (err, pushStream) => {
|
|
67
|
+
if (err) {
|
|
68
|
+
this.activePushes--;
|
|
69
|
+
reject(err);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Set priority if specified
|
|
73
|
+
if (resource.priority !== undefined) {
|
|
74
|
+
pushStream.priority({
|
|
75
|
+
weight: resource.priority,
|
|
76
|
+
exclusive: false
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
pushStream.on('finish', () => {
|
|
80
|
+
this.activePushes--;
|
|
81
|
+
this.recordPush(resource.path);
|
|
82
|
+
});
|
|
83
|
+
pushStream.on('error', () => {
|
|
84
|
+
this.activePushes--;
|
|
85
|
+
});
|
|
86
|
+
// Write the resource (in real implementation, fetch from cache/disk)
|
|
87
|
+
pushStream.respond({
|
|
88
|
+
':status': 200,
|
|
89
|
+
'content-type': this.getContentType(resource.path)
|
|
90
|
+
});
|
|
91
|
+
pushStream.end();
|
|
92
|
+
resolve();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.activePushes--;
|
|
97
|
+
reject(error);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Find rules matching the current request
|
|
103
|
+
*/
|
|
104
|
+
findMatchingRules(path, headers) {
|
|
105
|
+
const matches = [];
|
|
106
|
+
for (const [, rule] of this.pushRules) {
|
|
107
|
+
// Check trigger match
|
|
108
|
+
const triggerMatch = typeof rule.trigger === 'string'
|
|
109
|
+
? path.includes(rule.trigger)
|
|
110
|
+
: rule.trigger.test(path);
|
|
111
|
+
if (!triggerMatch)
|
|
112
|
+
continue;
|
|
113
|
+
// Check condition if present
|
|
114
|
+
if (rule.condition && !rule.condition(headers))
|
|
115
|
+
continue;
|
|
116
|
+
matches.push(rule);
|
|
117
|
+
}
|
|
118
|
+
return matches;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Record push statistics
|
|
122
|
+
*/
|
|
123
|
+
recordPush(path) {
|
|
124
|
+
const count = this.pushStats.get(path) || 0;
|
|
125
|
+
this.pushStats.set(path, count + 1);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get content type for a path
|
|
129
|
+
*/
|
|
130
|
+
getContentType(path) {
|
|
131
|
+
const ext = path.split('.').pop()?.toLowerCase();
|
|
132
|
+
const types = {
|
|
133
|
+
'js': 'application/javascript',
|
|
134
|
+
'css': 'text/css',
|
|
135
|
+
'json': 'application/json',
|
|
136
|
+
'png': 'image/png',
|
|
137
|
+
'jpg': 'image/jpeg',
|
|
138
|
+
'jpeg': 'image/jpeg',
|
|
139
|
+
'gif': 'image/gif',
|
|
140
|
+
'svg': 'image/svg+xml',
|
|
141
|
+
'html': 'text/html',
|
|
142
|
+
'txt': 'text/plain'
|
|
143
|
+
};
|
|
144
|
+
return types[ext || ''] || 'application/octet-stream';
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get push statistics
|
|
148
|
+
*/
|
|
149
|
+
getStats() {
|
|
150
|
+
return {
|
|
151
|
+
activePushes: this.activePushes,
|
|
152
|
+
totalPushes: Array.from(this.pushStats.values()).reduce((a, b) => a + b, 0),
|
|
153
|
+
pushCounts: new Map(this.pushStats)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Clear statistics
|
|
158
|
+
*/
|
|
159
|
+
clearStats() {
|
|
160
|
+
this.pushStats.clear();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Predefined push rules for common patterns
|
|
165
|
+
*/
|
|
166
|
+
export const CommonPushRules = {
|
|
167
|
+
/**
|
|
168
|
+
* Push API schema when main API endpoint is accessed
|
|
169
|
+
*/
|
|
170
|
+
apiSchema: {
|
|
171
|
+
trigger: /^\/api\/v1\//,
|
|
172
|
+
resources: [
|
|
173
|
+
{ path: '/api/v1/schema.json', priority: 10 }
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
/**
|
|
177
|
+
* Push authentication assets
|
|
178
|
+
*/
|
|
179
|
+
authAssets: {
|
|
180
|
+
trigger: '/auth',
|
|
181
|
+
resources: [
|
|
182
|
+
{ path: '/auth/login.js', priority: 15 },
|
|
183
|
+
{ path: '/auth/styles.css', priority: 10 }
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Intelligent push predictor
|
|
189
|
+
* Learns from access patterns to predict what to push
|
|
190
|
+
*/
|
|
191
|
+
export class IntelligentPushPredictor {
|
|
192
|
+
accessPatterns = new Map();
|
|
193
|
+
confidence = new Map();
|
|
194
|
+
/**
|
|
195
|
+
* Record an access pattern
|
|
196
|
+
*/
|
|
197
|
+
recordAccess(primary, secondary) {
|
|
198
|
+
// Record that secondary was accessed after primary
|
|
199
|
+
if (!this.accessPatterns.has(primary)) {
|
|
200
|
+
this.accessPatterns.set(primary, new Set());
|
|
201
|
+
}
|
|
202
|
+
this.accessPatterns.get(primary).add(secondary);
|
|
203
|
+
// Update confidence scores
|
|
204
|
+
if (!this.confidence.has(primary)) {
|
|
205
|
+
this.confidence.set(primary, new Map());
|
|
206
|
+
}
|
|
207
|
+
const scores = this.confidence.get(primary);
|
|
208
|
+
scores.set(secondary, (scores.get(secondary) || 0) + 1);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Predict resources to push based on confidence
|
|
212
|
+
*/
|
|
213
|
+
predict(path, minConfidence = 0.7) {
|
|
214
|
+
const patterns = this.accessPatterns.get(path);
|
|
215
|
+
if (!patterns)
|
|
216
|
+
return [];
|
|
217
|
+
const scores = this.confidence.get(path);
|
|
218
|
+
if (!scores)
|
|
219
|
+
return [];
|
|
220
|
+
const total = Array.from(scores.values()).reduce((a, b) => a + b, 0);
|
|
221
|
+
const predictions = [];
|
|
222
|
+
for (const [resource, count] of scores) {
|
|
223
|
+
const confidence = count / total;
|
|
224
|
+
if (confidence >= minConfidence) {
|
|
225
|
+
predictions.push({
|
|
226
|
+
path: resource,
|
|
227
|
+
priority: Math.round(confidence * 20) // 0-20 priority based on confidence
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return predictions.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get statistics
|
|
235
|
+
*/
|
|
236
|
+
getStats() {
|
|
237
|
+
let totalConfidence = 0;
|
|
238
|
+
let count = 0;
|
|
239
|
+
for (const scores of this.confidence.values()) {
|
|
240
|
+
const total = Array.from(scores.values()).reduce((a, b) => a + b, 0);
|
|
241
|
+
for (const score of scores.values()) {
|
|
242
|
+
totalConfidence += score / total;
|
|
243
|
+
count++;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
totalPatterns: this.accessPatterns.size,
|
|
248
|
+
averageConfidence: count > 0 ? totalConfidence / count : 0
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-Copy Buffer Implementation
|
|
3
|
+
* Direct memory access without intermediate copies for 10-15% memory/CPU reduction
|
|
4
|
+
* Phase 2 Optimization
|
|
5
|
+
*/
|
|
6
|
+
import { Buffer } from 'buffer';
|
|
7
|
+
/**
|
|
8
|
+
* Zero-Copy Buffer Pool
|
|
9
|
+
* Manages reusable buffers to avoid allocations and copies
|
|
10
|
+
*/
|
|
11
|
+
export class ZeroCopyBufferPool {
|
|
12
|
+
config;
|
|
13
|
+
availableBuffers = [];
|
|
14
|
+
inUseBuffers = new Set();
|
|
15
|
+
stats = {
|
|
16
|
+
allocated: 0,
|
|
17
|
+
reused: 0,
|
|
18
|
+
copiesAvoided: 0,
|
|
19
|
+
memorySaved: 0
|
|
20
|
+
};
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = {
|
|
23
|
+
enabled: config.enabled,
|
|
24
|
+
poolSize: config.poolSize || 100,
|
|
25
|
+
bufferSize: config.bufferSize || 64 * 1024, // 64KB default
|
|
26
|
+
reuseBuffers: config.reuseBuffers !== false
|
|
27
|
+
};
|
|
28
|
+
// Pre-allocate buffer pool
|
|
29
|
+
if (this.config.enabled) {
|
|
30
|
+
this.initializePool();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Initialize the buffer pool
|
|
35
|
+
*/
|
|
36
|
+
initializePool() {
|
|
37
|
+
for (let i = 0; i < this.config.poolSize; i++) {
|
|
38
|
+
const buffer = Buffer.allocUnsafe(this.config.bufferSize);
|
|
39
|
+
this.availableBuffers.push(buffer);
|
|
40
|
+
this.stats.allocated++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Acquire a buffer from the pool
|
|
45
|
+
*/
|
|
46
|
+
acquire(size) {
|
|
47
|
+
if (!this.config.enabled || !this.config.reuseBuffers) {
|
|
48
|
+
const buffer = Buffer.allocUnsafe(size || this.config.bufferSize);
|
|
49
|
+
this.stats.allocated++;
|
|
50
|
+
return buffer;
|
|
51
|
+
}
|
|
52
|
+
// Try to reuse an existing buffer
|
|
53
|
+
let buffer = this.availableBuffers.pop();
|
|
54
|
+
if (buffer) {
|
|
55
|
+
this.stats.reused++;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Pool exhausted, allocate new buffer
|
|
59
|
+
buffer = Buffer.allocUnsafe(size || this.config.bufferSize);
|
|
60
|
+
this.stats.allocated++;
|
|
61
|
+
}
|
|
62
|
+
this.inUseBuffers.add(buffer);
|
|
63
|
+
return buffer;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Release a buffer back to the pool
|
|
67
|
+
*/
|
|
68
|
+
release(buffer) {
|
|
69
|
+
if (!this.config.enabled || !this.config.reuseBuffers) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (this.inUseBuffers.has(buffer)) {
|
|
73
|
+
this.inUseBuffers.delete(buffer);
|
|
74
|
+
// Only keep buffers up to pool size
|
|
75
|
+
if (this.availableBuffers.length < this.config.poolSize) {
|
|
76
|
+
this.availableBuffers.push(buffer);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get buffer statistics
|
|
82
|
+
*/
|
|
83
|
+
getStats() {
|
|
84
|
+
return { ...this.stats };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Reset statistics
|
|
88
|
+
*/
|
|
89
|
+
resetStats() {
|
|
90
|
+
this.stats = {
|
|
91
|
+
allocated: this.availableBuffers.length + this.inUseBuffers.size,
|
|
92
|
+
reused: 0,
|
|
93
|
+
copiesAvoided: 0,
|
|
94
|
+
memorySaved: 0
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Clear the pool
|
|
99
|
+
*/
|
|
100
|
+
clear() {
|
|
101
|
+
this.availableBuffers = [];
|
|
102
|
+
this.inUseBuffers.clear();
|
|
103
|
+
this.resetStats();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Zero-Copy Stream Handler
|
|
108
|
+
* Handles streaming data without unnecessary copies
|
|
109
|
+
*/
|
|
110
|
+
export class ZeroCopyStreamHandler {
|
|
111
|
+
bufferPool;
|
|
112
|
+
stats;
|
|
113
|
+
constructor(bufferPool) {
|
|
114
|
+
this.bufferPool = bufferPool;
|
|
115
|
+
this.stats = {
|
|
116
|
+
allocated: 0,
|
|
117
|
+
reused: 0,
|
|
118
|
+
copiesAvoided: 0,
|
|
119
|
+
memorySaved: 0
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Process stream chunk without copying
|
|
124
|
+
* Uses Buffer.slice() which creates a view, not a copy
|
|
125
|
+
*/
|
|
126
|
+
processChunk(chunk, offset = 0, length) {
|
|
127
|
+
const actualLength = length || (chunk.length - offset);
|
|
128
|
+
// Create a view of the chunk (zero-copy)
|
|
129
|
+
const view = chunk.subarray(offset, offset + actualLength);
|
|
130
|
+
// Track statistics
|
|
131
|
+
this.stats.copiesAvoided++;
|
|
132
|
+
this.stats.memorySaved += actualLength;
|
|
133
|
+
return view;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Concatenate buffers efficiently
|
|
137
|
+
* Uses Buffer.concat which is optimized internally
|
|
138
|
+
*/
|
|
139
|
+
concat(buffers) {
|
|
140
|
+
if (buffers.length === 0) {
|
|
141
|
+
return Buffer.allocUnsafe(0);
|
|
142
|
+
}
|
|
143
|
+
if (buffers.length === 1) {
|
|
144
|
+
// No need to concat
|
|
145
|
+
this.stats.copiesAvoided++;
|
|
146
|
+
return buffers[0];
|
|
147
|
+
}
|
|
148
|
+
// Calculate total length
|
|
149
|
+
const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);
|
|
150
|
+
// Use Buffer.concat which is optimized
|
|
151
|
+
return Buffer.concat(buffers, totalLength);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Transfer data between buffers without intermediate copies
|
|
155
|
+
*/
|
|
156
|
+
transfer(source, target, sourceStart = 0, targetStart = 0, length) {
|
|
157
|
+
const actualLength = length || (source.length - sourceStart);
|
|
158
|
+
// Use copy which is optimized in native code
|
|
159
|
+
const copied = source.copy(target, targetStart, sourceStart, sourceStart + actualLength);
|
|
160
|
+
if (copied > 0) {
|
|
161
|
+
this.stats.copiesAvoided++;
|
|
162
|
+
this.stats.memorySaved += copied;
|
|
163
|
+
}
|
|
164
|
+
return copied;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get statistics
|
|
168
|
+
*/
|
|
169
|
+
getStats() {
|
|
170
|
+
return { ...this.stats };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Reset statistics
|
|
174
|
+
*/
|
|
175
|
+
resetStats() {
|
|
176
|
+
this.stats = {
|
|
177
|
+
allocated: 0,
|
|
178
|
+
reused: 0,
|
|
179
|
+
copiesAvoided: 0,
|
|
180
|
+
memorySaved: 0
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Zero-Copy Response Builder
|
|
186
|
+
* Build HTTP responses without unnecessary buffer copies
|
|
187
|
+
*/
|
|
188
|
+
export class ZeroCopyResponseBuilder {
|
|
189
|
+
chunks = [];
|
|
190
|
+
totalLength = 0;
|
|
191
|
+
/**
|
|
192
|
+
* Add a chunk to the response (stores reference, not copy)
|
|
193
|
+
*/
|
|
194
|
+
addChunk(chunk) {
|
|
195
|
+
const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
|
|
196
|
+
this.chunks.push(buffer);
|
|
197
|
+
this.totalLength += buffer.length;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Build the final response buffer
|
|
201
|
+
* Only concatenates when needed
|
|
202
|
+
*/
|
|
203
|
+
build() {
|
|
204
|
+
if (this.chunks.length === 0) {
|
|
205
|
+
return Buffer.allocUnsafe(0);
|
|
206
|
+
}
|
|
207
|
+
if (this.chunks.length === 1) {
|
|
208
|
+
return this.chunks[0];
|
|
209
|
+
}
|
|
210
|
+
// Efficient concatenation
|
|
211
|
+
return Buffer.concat(this.chunks, this.totalLength);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get response length without building
|
|
215
|
+
*/
|
|
216
|
+
getLength() {
|
|
217
|
+
return this.totalLength;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Clear chunks
|
|
221
|
+
*/
|
|
222
|
+
clear() {
|
|
223
|
+
this.chunks = [];
|
|
224
|
+
this.totalLength = 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Shared buffer for zero-copy operations
|
|
229
|
+
*/
|
|
230
|
+
export class SharedBuffer {
|
|
231
|
+
buffer;
|
|
232
|
+
refCount = 0;
|
|
233
|
+
isDetached = false;
|
|
234
|
+
constructor(size) {
|
|
235
|
+
this.buffer = Buffer.allocUnsafe(size);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Acquire a reference to this buffer
|
|
239
|
+
*/
|
|
240
|
+
acquire() {
|
|
241
|
+
if (this.isDetached) {
|
|
242
|
+
throw new Error('Cannot acquire detached buffer');
|
|
243
|
+
}
|
|
244
|
+
this.refCount++;
|
|
245
|
+
return this.buffer;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Release a reference
|
|
249
|
+
*/
|
|
250
|
+
release() {
|
|
251
|
+
if (this.refCount > 0) {
|
|
252
|
+
this.refCount--;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Check if buffer can be reused
|
|
257
|
+
*/
|
|
258
|
+
canReuse() {
|
|
259
|
+
return this.refCount === 0 && !this.isDetached;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Detach buffer (mark as unusable)
|
|
263
|
+
*/
|
|
264
|
+
detach() {
|
|
265
|
+
this.isDetached = true;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get current reference count
|
|
269
|
+
*/
|
|
270
|
+
getReferenceCount() {
|
|
271
|
+
return this.refCount;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Calculate memory savings from zero-copy optimizations
|
|
276
|
+
*/
|
|
277
|
+
export function calculateMemorySavings(stats) {
|
|
278
|
+
const totalAllocated = stats.allocated * 64 * 1024; // Assuming 64KB average
|
|
279
|
+
const savings = stats.memorySaved;
|
|
280
|
+
const savingsPercentage = totalAllocated > 0 ? (savings / totalAllocated) * 100 : 0;
|
|
281
|
+
return {
|
|
282
|
+
savingsPercentage,
|
|
283
|
+
savingsBytes: savings,
|
|
284
|
+
savingsMB: savings / (1024 * 1024)
|
|
285
|
+
};
|
|
286
|
+
}
|