vladx 1.0.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/README.md +256 -0
- package/bin/cli.js +486 -0
- package/bin/vlad.js +539 -0
- package/bin/vladpm.js +710 -0
- package/bin/vladx.js +491 -0
- package/package.json +57 -0
- package/src/engine/jit-compiler.js +285 -0
- package/src/engine/vladx-engine.js +941 -0
- package/src/index.js +44 -0
- package/src/interpreter/interpreter.js +2114 -0
- package/src/lexer/lexer.js +658 -0
- package/src/lexer/optimized-lexer.js +106 -0
- package/src/lexer/regex-cache.js +83 -0
- package/src/parser/ast-nodes.js +472 -0
- package/src/parser/parser.js +1408 -0
- package/src/runtime/advanced-type-system.js +209 -0
- package/src/runtime/async-manager.js +252 -0
- package/src/runtime/builtins.js +143 -0
- package/src/runtime/bundler.js +422 -0
- package/src/runtime/cache-manager.js +126 -0
- package/src/runtime/data-structures.js +612 -0
- package/src/runtime/debugger.js +260 -0
- package/src/runtime/enhanced-module-system.js +196 -0
- package/src/runtime/environment-enhanced.js +272 -0
- package/src/runtime/environment.js +140 -0
- package/src/runtime/event-emitter.js +232 -0
- package/src/runtime/formatter.js +280 -0
- package/src/runtime/functional.js +359 -0
- package/src/runtime/io-operations.js +390 -0
- package/src/runtime/linter.js +374 -0
- package/src/runtime/logging.js +314 -0
- package/src/runtime/minifier.js +242 -0
- package/src/runtime/module-system.js +377 -0
- package/src/runtime/network-operations.js +373 -0
- package/src/runtime/profiler.js +295 -0
- package/src/runtime/repl.js +336 -0
- package/src/runtime/security-manager.js +244 -0
- package/src/runtime/source-map-generator.js +208 -0
- package/src/runtime/test-runner.js +394 -0
- package/src/runtime/transformer.js +277 -0
- package/src/runtime/type-system.js +244 -0
- package/src/runtime/vladx-object.js +250 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NetworkOperations — Сетевые операции
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import https from 'https';
|
|
6
|
+
import http from 'http';
|
|
7
|
+
import { URL } from 'url';
|
|
8
|
+
|
|
9
|
+
export class NetworkOperations {
|
|
10
|
+
constructor(securityManager) {
|
|
11
|
+
this.securityManager = securityManager;
|
|
12
|
+
this.defaultOptions = {
|
|
13
|
+
timeout: 30000,
|
|
14
|
+
maxRedirects: 5,
|
|
15
|
+
headers: {
|
|
16
|
+
'User-Agent': 'VladX/1.0'
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* HTTP GET запрос
|
|
23
|
+
*/
|
|
24
|
+
async get(url, options = {}) {
|
|
25
|
+
return this.request(url, {
|
|
26
|
+
...options,
|
|
27
|
+
method: 'GET'
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* HTTP POST запрос
|
|
33
|
+
*/
|
|
34
|
+
async post(url, data, options = {}) {
|
|
35
|
+
return this.request(url, {
|
|
36
|
+
...options,
|
|
37
|
+
method: 'POST',
|
|
38
|
+
body: data
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* HTTP PUT запрос
|
|
44
|
+
*/
|
|
45
|
+
async put(url, data, options = {}) {
|
|
46
|
+
return this.request(url, {
|
|
47
|
+
...options,
|
|
48
|
+
method: 'PUT',
|
|
49
|
+
body: data
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* HTTP DELETE запрос
|
|
55
|
+
*/
|
|
56
|
+
async delete(url, options = {}) {
|
|
57
|
+
return this.request(url, {
|
|
58
|
+
...options,
|
|
59
|
+
method: 'DELETE'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* HTTP PATCH запрос
|
|
65
|
+
*/
|
|
66
|
+
async patch(url, data, options = {}) {
|
|
67
|
+
return this.request(url, {
|
|
68
|
+
...options,
|
|
69
|
+
method: 'PATCH',
|
|
70
|
+
body: data
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Общий HTTP запрос
|
|
76
|
+
*/
|
|
77
|
+
async request(url, options = {}) {
|
|
78
|
+
if (this.securityManager) {
|
|
79
|
+
this.securityManager.checkURL(url);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const mergedOptions = {
|
|
83
|
+
...this.defaultOptions,
|
|
84
|
+
...options
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const urlObj = new URL(url);
|
|
89
|
+
const client = urlObj.protocol === 'https:' ? https : http;
|
|
90
|
+
|
|
91
|
+
const reqOptions = {
|
|
92
|
+
method: mergedOptions.method,
|
|
93
|
+
headers: {
|
|
94
|
+
...mergedOptions.headers
|
|
95
|
+
},
|
|
96
|
+
timeout: mergedOptions.timeout
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const req = client.request(url, reqOptions, (res) => {
|
|
100
|
+
let data = '';
|
|
101
|
+
|
|
102
|
+
res.on('data', (chunk) => {
|
|
103
|
+
data += chunk;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
res.on('end', () => {
|
|
107
|
+
const response = {
|
|
108
|
+
status: res.statusCode,
|
|
109
|
+
statusText: res.statusMessage,
|
|
110
|
+
headers: res.headers,
|
|
111
|
+
data: this.parseResponse(data, res.headers['content-type'])
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Редиректы
|
|
115
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
116
|
+
if (mergedOptions.maxRedirects > 0) {
|
|
117
|
+
this.request(res.headers.location, {
|
|
118
|
+
...mergedOptions,
|
|
119
|
+
maxRedirects: mergedOptions.maxRedirects - 1
|
|
120
|
+
}).then(resolve).catch(reject);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
resolve(response);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
req.on('error', reject);
|
|
130
|
+
req.on('timeout', () => {
|
|
131
|
+
req.destroy();
|
|
132
|
+
reject(new Error(`Request timeout: ${url}`));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (mergedOptions.body) {
|
|
136
|
+
req.write(mergedOptions.body);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
req.end();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Парсинг ответа
|
|
145
|
+
*/
|
|
146
|
+
parseResponse(data, contentType) {
|
|
147
|
+
if (!contentType) return data;
|
|
148
|
+
|
|
149
|
+
if (contentType.includes('application/json')) {
|
|
150
|
+
try {
|
|
151
|
+
return JSON.parse(data);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
return data;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return data;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Download файла
|
|
162
|
+
*/
|
|
163
|
+
async downloadFile(url, destPath) {
|
|
164
|
+
if (this.securityManager) {
|
|
165
|
+
this.securityManager.checkURL(url);
|
|
166
|
+
this.securityManager.checkPath(destPath);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const urlObj = new URL(url);
|
|
171
|
+
const client = urlObj.protocol === 'https:' ? https : http;
|
|
172
|
+
const fs = require('fs');
|
|
173
|
+
const file = fs.createWriteStream(destPath);
|
|
174
|
+
|
|
175
|
+
client.get(url, (response) => {
|
|
176
|
+
response.pipe(file);
|
|
177
|
+
|
|
178
|
+
file.on('finish', () => {
|
|
179
|
+
file.close();
|
|
180
|
+
resolve(destPath);
|
|
181
|
+
});
|
|
182
|
+
}).on('error', (err) => {
|
|
183
|
+
fs.unlink(destPath, () => {});
|
|
184
|
+
reject(err);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Upload файла
|
|
191
|
+
*/
|
|
192
|
+
async uploadFile(url, filePath, options = {}) {
|
|
193
|
+
if (this.securityManager) {
|
|
194
|
+
this.securityManager.checkURL(url);
|
|
195
|
+
this.securityManager.checkPath(filePath);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const fs = require('fs');
|
|
199
|
+
const formData = require('form-data');
|
|
200
|
+
|
|
201
|
+
const form = new FormData();
|
|
202
|
+
const stats = fs.statSync(filePath);
|
|
203
|
+
|
|
204
|
+
form.append('file', fs.createReadStream(filePath), {
|
|
205
|
+
filename: require('path').basename(filePath),
|
|
206
|
+
knownLength: stats.size
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const formHeaders = form.getHeaders();
|
|
210
|
+
|
|
211
|
+
return this.request(url, {
|
|
212
|
+
...options,
|
|
213
|
+
method: 'POST',
|
|
214
|
+
headers: {
|
|
215
|
+
...options.headers,
|
|
216
|
+
...formHeaders
|
|
217
|
+
},
|
|
218
|
+
body: form
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* WebSocket соединение
|
|
224
|
+
*/
|
|
225
|
+
createWebSocket(url, options = {}) {
|
|
226
|
+
if (this.securityManager) {
|
|
227
|
+
this.securityManager.checkURL(url);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const WebSocket = require('ws');
|
|
231
|
+
return new WebSocket(url, options);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Создать HTTP сервер
|
|
236
|
+
*/
|
|
237
|
+
createServer(port, handler, options = {}) {
|
|
238
|
+
const server = http.createServer((req, res) => {
|
|
239
|
+
// CORS
|
|
240
|
+
if (options.cors) {
|
|
241
|
+
res.setHeader('Access-Control-Allow-Origin', options.cors.origin || '*');
|
|
242
|
+
res.setHeader('Access-Control-Allow-Methods', options.cors.methods || 'GET, POST, PUT, DELETE');
|
|
243
|
+
res.setHeader('Access-Control-Allow-Headers', options.cors.headers || 'Content-Type');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Logging
|
|
247
|
+
if (options.logging) {
|
|
248
|
+
console.log(`${req.method} ${req.url}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
handler(req, res);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
server.listen(port, () => {
|
|
255
|
+
console.log(`Server running on port ${port}`);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return server;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Создать HTTPS сервер
|
|
263
|
+
*/
|
|
264
|
+
createHttpsServer(port, handler, options = {}) {
|
|
265
|
+
const server = https.createServer(options.credentials || {}, (req, res) => {
|
|
266
|
+
// CORS
|
|
267
|
+
if (options.cors) {
|
|
268
|
+
res.setHeader('Access-Control-Allow-Origin', options.cors.origin || '*');
|
|
269
|
+
res.setHeader('Access-Control-Allow-Methods', options.cors.methods || 'GET, POST, PUT, DELETE');
|
|
270
|
+
res.setHeader('Access-Control-Allow-Headers', options.cors.headers || 'Content-Type');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
handler(req, res);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
server.listen(port, () => {
|
|
277
|
+
console.log(`HTTPS Server running on port ${port}`);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return server;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Проверить доступность URL
|
|
285
|
+
*/
|
|
286
|
+
async checkUrl(url) {
|
|
287
|
+
try {
|
|
288
|
+
const response = await this.head(url);
|
|
289
|
+
return response.status >= 200 && response.status < 400;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* HEAD запрос
|
|
297
|
+
*/
|
|
298
|
+
async head(url, options = {}) {
|
|
299
|
+
return this.request(url, {
|
|
300
|
+
...options,
|
|
301
|
+
method: 'HEAD'
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* OPTIONS запрос
|
|
307
|
+
*/
|
|
308
|
+
async options(url, options = {}) {
|
|
309
|
+
return this.request(url, {
|
|
310
|
+
...options,
|
|
311
|
+
method: 'OPTIONS'
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Отправить FormData
|
|
317
|
+
*/
|
|
318
|
+
async sendForm(url, formData, options = {}) {
|
|
319
|
+
if (typeof formData === 'object') {
|
|
320
|
+
const form = new FormData();
|
|
321
|
+
for (const [key, value] of Object.entries(formData)) {
|
|
322
|
+
form.append(key, value);
|
|
323
|
+
}
|
|
324
|
+
formData = form;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const formHeaders = formData.getHeaders();
|
|
328
|
+
|
|
329
|
+
return this.request(url, {
|
|
330
|
+
...options,
|
|
331
|
+
method: 'POST',
|
|
332
|
+
headers: {
|
|
333
|
+
...options.headers,
|
|
334
|
+
...formHeaders
|
|
335
|
+
},
|
|
336
|
+
body: formData
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* multipart/form-data
|
|
342
|
+
*/
|
|
343
|
+
createMultipartForm() {
|
|
344
|
+
const boundary = `----VladX${Date.now()}`;
|
|
345
|
+
const parts = [];
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
addField: (name, value) => {
|
|
349
|
+
parts.push(
|
|
350
|
+
`--${boundary}\r\n` +
|
|
351
|
+
`Content-Disposition: form-data; name="${name}"\r\n\r\n` +
|
|
352
|
+
`${value}\r\n`
|
|
353
|
+
);
|
|
354
|
+
},
|
|
355
|
+
addFile: (name, filename, content, contentType) => {
|
|
356
|
+
parts.push(
|
|
357
|
+
`--${boundary}\r\n` +
|
|
358
|
+
`Content-Disposition: form-data; name="${name}"; filename="${filename}"\r\n` +
|
|
359
|
+
`Content-Type: ${contentType || 'application/octet-stream'}\r\n\r\n` +
|
|
360
|
+
`${content}\r\n`
|
|
361
|
+
);
|
|
362
|
+
},
|
|
363
|
+
build: () => {
|
|
364
|
+
return parts.join('') + `--${boundary}--\r\n`;
|
|
365
|
+
},
|
|
366
|
+
getContentType: () => {
|
|
367
|
+
return `multipart/form-data; boundary=${boundary}`;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export default NetworkOperations;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profiler — Профилировщик производительности
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class Profiler {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.records = new Map();
|
|
8
|
+
this.callCounts = new Map();
|
|
9
|
+
this.currentCallStack = [];
|
|
10
|
+
this.startTime = null;
|
|
11
|
+
this.endTime = null;
|
|
12
|
+
this.memorySamples = [];
|
|
13
|
+
this.intervalId = null;
|
|
14
|
+
this.hotspots = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Начать профилирование
|
|
19
|
+
*/
|
|
20
|
+
start(options = {}) {
|
|
21
|
+
this.records.clear();
|
|
22
|
+
this.callCounts.clear();
|
|
23
|
+
this.currentCallStack = [];
|
|
24
|
+
this.startTime = Date.now();
|
|
25
|
+
this.endTime = null;
|
|
26
|
+
this.memorySamples = [];
|
|
27
|
+
this.hotspots.clear();
|
|
28
|
+
|
|
29
|
+
if (options.sampleMemory) {
|
|
30
|
+
const sampleInterval = options.sampleInterval || 100;
|
|
31
|
+
this.intervalId = setInterval(() => {
|
|
32
|
+
this.memorySamples.push({
|
|
33
|
+
timestamp: Date.now(),
|
|
34
|
+
heapUsed: process.memoryUsage().heapUsed,
|
|
35
|
+
heapTotal: process.memoryUsage().heapTotal
|
|
36
|
+
});
|
|
37
|
+
}, sampleInterval);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Завершить профилирование
|
|
43
|
+
*/
|
|
44
|
+
stop() {
|
|
45
|
+
this.endTime = Date.now();
|
|
46
|
+
|
|
47
|
+
if (this.intervalId) {
|
|
48
|
+
clearInterval(this.intervalId);
|
|
49
|
+
this.intervalId = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return this.getResults();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Начать измерение функции
|
|
57
|
+
*/
|
|
58
|
+
enterFunction(functionName, filename, line) {
|
|
59
|
+
const key = `${filename}:${functionName}`;
|
|
60
|
+
|
|
61
|
+
this.currentCallStack.push({
|
|
62
|
+
functionName,
|
|
63
|
+
filename,
|
|
64
|
+
line,
|
|
65
|
+
key,
|
|
66
|
+
startTime: Date.now()
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.callCounts.set(key, (this.callCounts.get(key) || 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Завершить измерение функции
|
|
74
|
+
*/
|
|
75
|
+
exitFunction() {
|
|
76
|
+
if (this.currentCallStack.length === 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const frame = this.currentCallStack.pop();
|
|
81
|
+
const duration = Date.now() - frame.startTime;
|
|
82
|
+
|
|
83
|
+
if (!this.records.has(frame.key)) {
|
|
84
|
+
this.records.set(frame.key, {
|
|
85
|
+
functionName: frame.functionName,
|
|
86
|
+
filename: frame.filename,
|
|
87
|
+
totalTime: 0,
|
|
88
|
+
minTime: Infinity,
|
|
89
|
+
maxTime: 0,
|
|
90
|
+
avgTime: 0,
|
|
91
|
+
callCount: 0
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const record = this.records.get(frame.key);
|
|
96
|
+
record.totalTime += duration;
|
|
97
|
+
record.minTime = Math.min(record.minTime, duration);
|
|
98
|
+
record.maxTime = Math.max(record.maxTime, duration);
|
|
99
|
+
record.callCount = this.callCounts.get(frame.key);
|
|
100
|
+
record.avgTime = record.totalTime / record.callCount;
|
|
101
|
+
|
|
102
|
+
// Обновляем hotspots
|
|
103
|
+
const hotspotKey = `${frame.filename}:${frame.line}`;
|
|
104
|
+
this.hotspots.set(hotspotKey, (this.hotspots.get(hotspotKey) || 0) + duration);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Получить результаты профилирования
|
|
109
|
+
*/
|
|
110
|
+
getResults() {
|
|
111
|
+
const totalTime = this.endTime ? this.endTime - this.startTime : 0;
|
|
112
|
+
|
|
113
|
+
// Сортировка по total time
|
|
114
|
+
const sortedRecords = Array.from(this.records.values())
|
|
115
|
+
.sort((a, b) => b.totalTime - a.totalTime)
|
|
116
|
+
.map(record => ({
|
|
117
|
+
...record,
|
|
118
|
+
percentage: totalTime > 0 ? ((record.totalTime / totalTime) * 100).toFixed(2) + '%' : '0%'
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
// Сортировка hotspots
|
|
122
|
+
const sortedHotspots = Array.from(this.hotspots.entries())
|
|
123
|
+
.sort(([, a], [, b]) => b - a)
|
|
124
|
+
.slice(0, 10)
|
|
125
|
+
.map(([location, time]) => ({
|
|
126
|
+
location,
|
|
127
|
+
time,
|
|
128
|
+
percentage: totalTime > 0 ? ((time / totalTime) * 100).toFixed(2) + '%' : '0%'
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
summary: {
|
|
133
|
+
totalTime: totalTime + 'ms',
|
|
134
|
+
functionCount: this.records.size,
|
|
135
|
+
totalCalls: Array.from(this.callCounts.values()).reduce((a, b) => a + b, 0)
|
|
136
|
+
},
|
|
137
|
+
functions: sortedRecords,
|
|
138
|
+
hotspots: sortedHotspots,
|
|
139
|
+
memory: {
|
|
140
|
+
samples: this.memorySamples,
|
|
141
|
+
initial: this.memorySamples[0] || null,
|
|
142
|
+
final: this.memorySamples[this.memorySamples.length - 1] || null,
|
|
143
|
+
peak: this.memorySamples.reduce((max, sample) =>
|
|
144
|
+
sample.heapUsed > max.heapUsed ? sample : max,
|
|
145
|
+
{ heapUsed: 0 }
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Получить данные о функциях в формате flame graph
|
|
153
|
+
*/
|
|
154
|
+
getFlameGraphData() {
|
|
155
|
+
return Array.from(this.records.values()).map(record => ({
|
|
156
|
+
name: `${record.functionName} (${record.filename})`,
|
|
157
|
+
value: record.totalTime,
|
|
158
|
+
children: []
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Экспорт результатов в JSON
|
|
164
|
+
*/
|
|
165
|
+
exportJSON() {
|
|
166
|
+
return JSON.stringify(this.getResults(), null, 2);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Экспорт результатов в формате для Flamegraph
|
|
171
|
+
*/
|
|
172
|
+
exportFlamegraph() {
|
|
173
|
+
const lines = [];
|
|
174
|
+
|
|
175
|
+
for (const [key, record] of this.records) {
|
|
176
|
+
lines.push(`${record.functionName} ${record.totalTime}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return lines.join('\n');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Получить статистику по памяти
|
|
184
|
+
*/
|
|
185
|
+
getMemoryStats() {
|
|
186
|
+
if (this.memorySamples.length === 0) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const samples = this.memorySamples.map(s => s.heapUsed);
|
|
191
|
+
const min = Math.min(...samples);
|
|
192
|
+
const max = Math.max(...samples);
|
|
193
|
+
const avg = samples.reduce((a, b) => a + b, 0) / samples.length;
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
min,
|
|
197
|
+
max,
|
|
198
|
+
avg,
|
|
199
|
+
growth: this.memorySamples[this.memorySamples.length - 1].heapUsed - this.memorySamples[0].heapUsed,
|
|
200
|
+
samples: this.memorySamples
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Найти функции с высокой задержкой
|
|
206
|
+
*/
|
|
207
|
+
findSlowFunctions(threshold = 100) {
|
|
208
|
+
return Array.from(this.records.values())
|
|
209
|
+
.filter(record => record.avgTime > threshold)
|
|
210
|
+
.sort((a, b) => b.avgTime - a.avgTime);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Найти функции с большим количеством вызовов
|
|
215
|
+
*/
|
|
216
|
+
findFrequentFunctions(threshold = 100) {
|
|
217
|
+
return Array.from(this.records.values())
|
|
218
|
+
.filter(record => record.callCount > threshold)
|
|
219
|
+
.sort((a, b) => b.callCount - a.callCount);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Сравнить два профиля
|
|
224
|
+
*/
|
|
225
|
+
static compare(profile1, profile2) {
|
|
226
|
+
const results1 = profile1.getResults();
|
|
227
|
+
const results2 = profile2.getResults();
|
|
228
|
+
|
|
229
|
+
const comparison = {
|
|
230
|
+
totalTime: {
|
|
231
|
+
before: results1.summary.totalTime,
|
|
232
|
+
after: results2.summary.totalTime,
|
|
233
|
+
change: this.calculateChange(
|
|
234
|
+
parseInt(results1.summary.totalTime),
|
|
235
|
+
parseInt(results2.summary.totalTime)
|
|
236
|
+
)
|
|
237
|
+
},
|
|
238
|
+
functions: []
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Сравнение функций
|
|
242
|
+
const allFunctions = new Set([
|
|
243
|
+
...results1.functions.map(f => f.functionName),
|
|
244
|
+
...results2.functions.map(f => f.functionName)
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
for (const funcName of allFunctions) {
|
|
248
|
+
const func1 = results1.functions.find(f => f.functionName === funcName);
|
|
249
|
+
const func2 = results2.functions.find(f => f.functionName === funcName);
|
|
250
|
+
|
|
251
|
+
comparison.functions.push({
|
|
252
|
+
name: funcName,
|
|
253
|
+
before: func1 ? func1.totalTime : 0,
|
|
254
|
+
after: func2 ? func2.totalTime : 0,
|
|
255
|
+
change: this.calculateChange(
|
|
256
|
+
func1 ? func1.totalTime : 0,
|
|
257
|
+
func2 ? func2.totalTime : 0
|
|
258
|
+
)
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
comparison.functions.sort((a, b) => b.after - a.after);
|
|
263
|
+
|
|
264
|
+
return comparison;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Рассчитать изменение
|
|
269
|
+
*/
|
|
270
|
+
static calculateChange(before, after) {
|
|
271
|
+
if (before === 0) return after > 0 ? '+∞' : '0%';
|
|
272
|
+
const change = ((after - before) / before * 100).toFixed(2);
|
|
273
|
+
return change > 0 ? `+${change}%` : `${change}%`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Очистить данные
|
|
278
|
+
*/
|
|
279
|
+
clear() {
|
|
280
|
+
this.records.clear();
|
|
281
|
+
this.callCounts.clear();
|
|
282
|
+
this.currentCallStack = [];
|
|
283
|
+
this.startTime = null;
|
|
284
|
+
this.endTime = null;
|
|
285
|
+
this.memorySamples = [];
|
|
286
|
+
this.hotspots.clear();
|
|
287
|
+
|
|
288
|
+
if (this.intervalId) {
|
|
289
|
+
clearInterval(this.intervalId);
|
|
290
|
+
this.intervalId = null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export default Profiler;
|