@postalsys/email-text-tools 2.1.16 → 2.3.3
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/.github/workflows/release.yaml +17 -17
- package/.ncurc.js +1 -2
- package/.prettierignore +14 -0
- package/.release-please-manifest.json +3 -0
- package/CHANGELOG.md +81 -31
- package/benchmark/benchmark.js +358 -0
- package/benchmark/concurrency-benchmark.js +176 -0
- package/benchmark/quick-benchmark.js +200 -0
- package/benchmark/quick-concurrency.js +64 -0
- package/benchmark/real-world-benchmark.js +267 -0
- package/docs/PERFORMANCE.md +262 -0
- package/eslint.config.js +52 -0
- package/examples/failing.html +617 -0
- package/examples/mime-html.js +262 -31
- package/lib/juice-worker.js +13 -0
- package/lib/mime-html.js +243 -13
- package/lib/text-to-html.js +1 -1
- package/lib/worker-pool.js +270 -0
- package/package.json +16 -8
- package/release-please-config.json +9 -0
- package/test/comprehensive.test.js +347 -0
- package/test/edge-cases.test.js +62 -0
- package/test/mime-html.test.js +327 -0
- package/test/worker-pool-regression.test.js +128 -0
- package/test/worker-pool.test.js +282 -0
- package/processed.txt +0 -699
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const mimeHtml = require('../lib/mime-html');
|
|
7
|
+
|
|
8
|
+
async function concurrencyBenchmark() {
|
|
9
|
+
console.log('Concurrency Benchmark - Real-World HTML');
|
|
10
|
+
console.log('='.repeat(80));
|
|
11
|
+
console.log('Testing concurrent processing with problematic HTML\n');
|
|
12
|
+
|
|
13
|
+
// Load the real-world HTML
|
|
14
|
+
const realWorldHtml = fs.readFileSync(path.join(__dirname, '../examples/failing.html'), 'utf8');
|
|
15
|
+
|
|
16
|
+
console.log(`HTML size: ${(realWorldHtml.length / 1024).toFixed(1)} KB`);
|
|
17
|
+
console.log('Testing different concurrency levels...\n');
|
|
18
|
+
|
|
19
|
+
const concurrencyLevels = [1, 5, 10, 20, 50];
|
|
20
|
+
const totalRequests = 100;
|
|
21
|
+
|
|
22
|
+
const results = [];
|
|
23
|
+
|
|
24
|
+
for (const concurrency of concurrencyLevels) {
|
|
25
|
+
console.log(`Testing with concurrency level: ${concurrency}`);
|
|
26
|
+
console.log('-'.repeat(40));
|
|
27
|
+
|
|
28
|
+
// Test sync (sequential only)
|
|
29
|
+
if (concurrency === 1) {
|
|
30
|
+
console.log(' Sync version (sequential)...');
|
|
31
|
+
const syncStart = process.hrtime.bigint();
|
|
32
|
+
for (let i = 0; i < totalRequests; i++) {
|
|
33
|
+
mimeHtml.sync({ html: realWorldHtml });
|
|
34
|
+
if ((i + 1) % 20 === 0) {
|
|
35
|
+
process.stdout.write(` Progress: ${i + 1}/${totalRequests}\r`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const syncEnd = process.hrtime.bigint();
|
|
39
|
+
const syncTime = Number(syncEnd - syncStart) / 1000000;
|
|
40
|
+
process.stdout.write(` Progress: ${totalRequests}/${totalRequests} - Done!\n`);
|
|
41
|
+
|
|
42
|
+
results.push({
|
|
43
|
+
method: 'Sync (sequential)',
|
|
44
|
+
concurrency: 1,
|
|
45
|
+
totalRequests,
|
|
46
|
+
totalTime: syncTime.toFixed(2),
|
|
47
|
+
avgTime: (syncTime / totalRequests).toFixed(2),
|
|
48
|
+
reqPerSec: (totalRequests / (syncTime / 1000)).toFixed(0)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Test async with pool
|
|
53
|
+
console.log(' Async with pool...');
|
|
54
|
+
const asyncPoolStart = process.hrtime.bigint();
|
|
55
|
+
const poolPromises = [];
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < totalRequests; i++) {
|
|
58
|
+
poolPromises.push(
|
|
59
|
+
mimeHtml.async(
|
|
60
|
+
{ html: realWorldHtml },
|
|
61
|
+
{
|
|
62
|
+
useWorkerPool: true,
|
|
63
|
+
timeout: 5000,
|
|
64
|
+
minWorkers: 2,
|
|
65
|
+
maxWorkers: 4,
|
|
66
|
+
logErrors: false
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Control concurrency
|
|
72
|
+
if (poolPromises.length >= concurrency) {
|
|
73
|
+
await Promise.race(poolPromises).then(() => {
|
|
74
|
+
const index = poolPromises.findIndex(
|
|
75
|
+
p => p.constructor.name !== 'Promise' || Promise.race([p, Promise.resolve('done')]).then(v => v === 'done')
|
|
76
|
+
);
|
|
77
|
+
if (index > -1) poolPromises.splice(index, 1);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if ((i + 1) % 20 === 0) {
|
|
82
|
+
process.stdout.write(` Progress: ${i + 1}/${totalRequests}\r`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Wait for remaining promises
|
|
87
|
+
await Promise.all(poolPromises);
|
|
88
|
+
const asyncPoolEnd = process.hrtime.bigint();
|
|
89
|
+
const asyncPoolTime = Number(asyncPoolEnd - asyncPoolStart) / 1000000;
|
|
90
|
+
process.stdout.write(` Progress: ${totalRequests}/${totalRequests} - Done!\n`);
|
|
91
|
+
|
|
92
|
+
results.push({
|
|
93
|
+
method: 'Async (pool)',
|
|
94
|
+
concurrency,
|
|
95
|
+
totalRequests,
|
|
96
|
+
totalTime: asyncPoolTime.toFixed(2),
|
|
97
|
+
avgTime: (asyncPoolTime / totalRequests).toFixed(2),
|
|
98
|
+
reqPerSec: (totalRequests / (asyncPoolTime / 1000)).toFixed(0)
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Show worker pool stats at peak concurrency
|
|
102
|
+
if (concurrency === 20) {
|
|
103
|
+
const stats = mimeHtml.getWorkerPoolStats();
|
|
104
|
+
console.log(` Worker pool stats: ${stats.totalWorkers} workers, ${stats.busyWorkers} busy`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Display results table
|
|
111
|
+
console.log('='.repeat(80));
|
|
112
|
+
console.log('RESULTS');
|
|
113
|
+
console.log('='.repeat(80) + '\n');
|
|
114
|
+
|
|
115
|
+
console.table(
|
|
116
|
+
results.map(r => ({
|
|
117
|
+
Method: r.method,
|
|
118
|
+
Concurrency: r.concurrency,
|
|
119
|
+
'Total Time': `${r.totalTime}ms`,
|
|
120
|
+
'Avg per Request': `${r.avgTime}ms`,
|
|
121
|
+
Throughput: `${r.reqPerSec} req/s`
|
|
122
|
+
}))
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Analysis
|
|
126
|
+
console.log('Performance Analysis:');
|
|
127
|
+
console.log('-'.repeat(40));
|
|
128
|
+
|
|
129
|
+
const syncResult = results.find(r => r.method === 'Sync (sequential)');
|
|
130
|
+
const bestAsync = results.filter(r => r.method === 'Async (pool)').sort((a, b) => parseFloat(a.totalTime) - parseFloat(b.totalTime))[0];
|
|
131
|
+
|
|
132
|
+
if (syncResult && bestAsync) {
|
|
133
|
+
const speedup = (parseFloat(syncResult.totalTime) / parseFloat(bestAsync.totalTime)).toFixed(2);
|
|
134
|
+
console.log(`• Best async configuration: Concurrency ${bestAsync.concurrency}`);
|
|
135
|
+
console.log(`• Async is ${speedup}x faster than sync for batch processing`);
|
|
136
|
+
console.log(`• Sync throughput: ${syncResult.reqPerSec} req/s`);
|
|
137
|
+
console.log(`• Best async throughput: ${bestAsync.reqPerSec} req/s`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Throughput graph
|
|
141
|
+
console.log('\nThroughput by Concurrency Level:');
|
|
142
|
+
console.log('-'.repeat(40));
|
|
143
|
+
|
|
144
|
+
const asyncResults = results.filter(r => r.method === 'Async (pool)');
|
|
145
|
+
const maxThroughput = Math.max(...asyncResults.map(r => parseInt(r.reqPerSec)));
|
|
146
|
+
|
|
147
|
+
asyncResults.forEach(r => {
|
|
148
|
+
const barLength = Math.round((parseInt(r.reqPerSec) / maxThroughput) * 30);
|
|
149
|
+
const bar = '█'.repeat(barLength);
|
|
150
|
+
console.log(` ${String(r.concurrency).padStart(2)}: ${bar} ${r.reqPerSec} req/s`);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Memory usage
|
|
154
|
+
const memUsage = process.memoryUsage();
|
|
155
|
+
console.log('\nFinal Memory Usage:');
|
|
156
|
+
console.log('-'.repeat(40));
|
|
157
|
+
console.log(`• RSS: ${(memUsage.rss / 1024 / 1024).toFixed(1)} MB`);
|
|
158
|
+
console.log(`• Heap Used: ${(memUsage.heapUsed / 1024 / 1024).toFixed(1)} MB`);
|
|
159
|
+
|
|
160
|
+
// Cleanup
|
|
161
|
+
await mimeHtml.closeWorkerPool();
|
|
162
|
+
|
|
163
|
+
console.log('\n' + '='.repeat(80));
|
|
164
|
+
console.log('Summary:');
|
|
165
|
+
console.log('• Async with worker pool provides significant speedup for concurrent processing');
|
|
166
|
+
console.log('• Pre-filtering prevents timeouts even under high load');
|
|
167
|
+
console.log('• Worker pool efficiently handles concurrent requests');
|
|
168
|
+
console.log('• Optimal concurrency depends on CPU cores and workload');
|
|
169
|
+
console.log('='.repeat(80));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Run benchmark
|
|
173
|
+
concurrencyBenchmark().catch(err => {
|
|
174
|
+
console.error('Benchmark failed:', err);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const mimeHtml = require('../lib/mime-html');
|
|
5
|
+
|
|
6
|
+
// Quick performance comparison
|
|
7
|
+
async function quickBenchmark() {
|
|
8
|
+
const testHtml = `
|
|
9
|
+
<!DOCTYPE html>
|
|
10
|
+
<html>
|
|
11
|
+
<head>
|
|
12
|
+
<style>
|
|
13
|
+
body { font-family: Arial, sans-serif; }
|
|
14
|
+
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
15
|
+
h1 { color: #333; }
|
|
16
|
+
p { line-height: 1.6; }
|
|
17
|
+
.button { background: #007bff; color: white; padding: 10px 20px; }
|
|
18
|
+
</style>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<div class="container">
|
|
22
|
+
<h1>Test Email</h1>
|
|
23
|
+
<p>This is a test email with moderate complexity.</p>
|
|
24
|
+
<a href="#" class="button">Click Here</a>
|
|
25
|
+
</div>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
console.log('Quick Performance Comparison');
|
|
31
|
+
console.log('============================\n');
|
|
32
|
+
|
|
33
|
+
const iterations = 50;
|
|
34
|
+
|
|
35
|
+
// Test Sync
|
|
36
|
+
console.log('Testing Sync version...');
|
|
37
|
+
const syncTimes = [];
|
|
38
|
+
for (let i = 0; i < iterations; i++) {
|
|
39
|
+
const start = process.hrtime.bigint();
|
|
40
|
+
mimeHtml.sync({ html: testHtml });
|
|
41
|
+
const end = process.hrtime.bigint();
|
|
42
|
+
syncTimes.push(Number(end - start) / 1000000);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Test Async with Pool
|
|
46
|
+
console.log('Testing Async with worker pool...');
|
|
47
|
+
const asyncPoolTimes = [];
|
|
48
|
+
for (let i = 0; i < iterations; i++) {
|
|
49
|
+
const start = process.hrtime.bigint();
|
|
50
|
+
await mimeHtml.async(
|
|
51
|
+
{ html: testHtml },
|
|
52
|
+
{
|
|
53
|
+
useWorkerPool: true,
|
|
54
|
+
timeout: 5000
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
const end = process.hrtime.bigint();
|
|
58
|
+
asyncPoolTimes.push(Number(end - start) / 1000000);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Test Async without Pool (just first 5 to avoid long wait)
|
|
62
|
+
console.log('Testing Async without pool (5 iterations only)...');
|
|
63
|
+
const asyncNoPoolTimes = [];
|
|
64
|
+
for (let i = 0; i < 5; i++) {
|
|
65
|
+
const start = process.hrtime.bigint();
|
|
66
|
+
await mimeHtml.async(
|
|
67
|
+
{ html: testHtml },
|
|
68
|
+
{
|
|
69
|
+
useWorkerPool: false,
|
|
70
|
+
timeout: 5000
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
const end = process.hrtime.bigint();
|
|
74
|
+
asyncNoPoolTimes.push(Number(end - start) / 1000000);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Calculate stats
|
|
78
|
+
const getStats = times => {
|
|
79
|
+
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
80
|
+
times.sort((a, b) => a - b);
|
|
81
|
+
const median = times[Math.floor(times.length / 2)];
|
|
82
|
+
const min = times[0];
|
|
83
|
+
const max = times[times.length - 1];
|
|
84
|
+
return { avg, median, min, max };
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const syncStats = getStats(syncTimes);
|
|
88
|
+
const asyncPoolStats = getStats(asyncPoolTimes);
|
|
89
|
+
const asyncNoPoolStats = getStats(asyncNoPoolTimes);
|
|
90
|
+
|
|
91
|
+
// Display results
|
|
92
|
+
console.log('\nResults (all times in milliseconds):');
|
|
93
|
+
console.log('=====================================\n');
|
|
94
|
+
|
|
95
|
+
console.log('Sync Version:');
|
|
96
|
+
console.log(` Average: ${syncStats.avg.toFixed(2)} ms`);
|
|
97
|
+
console.log(` Median: ${syncStats.median.toFixed(2)} ms`);
|
|
98
|
+
console.log(` Min: ${syncStats.min.toFixed(2)} ms`);
|
|
99
|
+
console.log(` Max: ${syncStats.max.toFixed(2)} ms`);
|
|
100
|
+
|
|
101
|
+
console.log('\nAsync with Worker Pool:');
|
|
102
|
+
console.log(` Average: ${asyncPoolStats.avg.toFixed(2)} ms`);
|
|
103
|
+
console.log(` Median: ${asyncPoolStats.median.toFixed(2)} ms`);
|
|
104
|
+
console.log(` Min: ${asyncPoolStats.min.toFixed(2)} ms`);
|
|
105
|
+
console.log(` Max: ${asyncPoolStats.max.toFixed(2)} ms`);
|
|
106
|
+
|
|
107
|
+
console.log('\nAsync without Pool (single worker per call):');
|
|
108
|
+
console.log(` Average: ${asyncNoPoolStats.avg.toFixed(2)} ms`);
|
|
109
|
+
console.log(` Median: ${asyncNoPoolStats.median.toFixed(2)} ms`);
|
|
110
|
+
console.log(` Min: ${asyncNoPoolStats.min.toFixed(2)} ms`);
|
|
111
|
+
console.log(` Max: ${asyncNoPoolStats.max.toFixed(2)} ms`);
|
|
112
|
+
|
|
113
|
+
console.log('\nPerformance Summary:');
|
|
114
|
+
console.log('====================');
|
|
115
|
+
|
|
116
|
+
if (asyncPoolStats.avg < syncStats.avg) {
|
|
117
|
+
const speedup = (syncStats.avg / asyncPoolStats.avg).toFixed(2);
|
|
118
|
+
console.log(`✓ Async with pool is ${speedup}x faster than sync`);
|
|
119
|
+
} else {
|
|
120
|
+
const slowdown = (asyncPoolStats.avg / syncStats.avg).toFixed(2);
|
|
121
|
+
console.log(`✗ Async with pool is ${slowdown}x slower than sync`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const poolVsNoPool = (asyncNoPoolStats.avg / asyncPoolStats.avg).toFixed(1);
|
|
125
|
+
console.log(`✓ Worker pool is ${poolVsNoPool}x faster than single workers`);
|
|
126
|
+
|
|
127
|
+
// Worker pool stats
|
|
128
|
+
const poolStats = mimeHtml.getWorkerPoolStats();
|
|
129
|
+
if (poolStats) {
|
|
130
|
+
console.log('\nWorker Pool Statistics:');
|
|
131
|
+
console.log('======================');
|
|
132
|
+
console.log(` Total Workers: ${poolStats.totalWorkers}`);
|
|
133
|
+
console.log(` Busy Workers: ${poolStats.busyWorkers}`);
|
|
134
|
+
console.log(` Free Workers: ${poolStats.freeWorkers}`);
|
|
135
|
+
console.log(` Queued Tasks: ${poolStats.queuedTasks}`);
|
|
136
|
+
console.log(` Min Workers: ${poolStats.minWorkers}`);
|
|
137
|
+
console.log(` Max Workers: ${poolStats.maxWorkers}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Test with problematic CSS
|
|
141
|
+
console.log('\nTesting Problematic CSS Handling:');
|
|
142
|
+
console.log('=================================');
|
|
143
|
+
|
|
144
|
+
const problematicHtml = `
|
|
145
|
+
<!DOCTYPE html>
|
|
146
|
+
<html>
|
|
147
|
+
<head>
|
|
148
|
+
<style>
|
|
149
|
+
/* These selectors would cause juice to hang */
|
|
150
|
+
.item:is(.active, .selected) { background: yellow; }
|
|
151
|
+
.element:where(.primary) { color: blue; }
|
|
152
|
+
.parent:has(> .child) { border: 1px solid red; }
|
|
153
|
+
</style>
|
|
154
|
+
</head>
|
|
155
|
+
<body>
|
|
156
|
+
<div class="item active">Active Item</div>
|
|
157
|
+
<div class="parent"><div class="child">Child</div></div>
|
|
158
|
+
</body>
|
|
159
|
+
</html>
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
console.log(' Sync version... ');
|
|
164
|
+
const syncStart = process.hrtime.bigint();
|
|
165
|
+
const _syncResult = mimeHtml.sync({ html: problematicHtml });
|
|
166
|
+
const syncEnd = process.hrtime.bigint();
|
|
167
|
+
const syncTime = Number(syncEnd - syncStart) / 1000000;
|
|
168
|
+
console.log(` ✓ Success (${syncTime.toFixed(2)} ms) - problematic selectors removed`);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.log(` ✗ Failed: ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
console.log(' Async version...');
|
|
175
|
+
const asyncStart = process.hrtime.bigint();
|
|
176
|
+
const _asyncResult = await mimeHtml.async(
|
|
177
|
+
{ html: problematicHtml },
|
|
178
|
+
{
|
|
179
|
+
timeout: 2000,
|
|
180
|
+
logErrors: false
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
const asyncEnd = process.hrtime.bigint();
|
|
184
|
+
const asyncTime = Number(asyncEnd - asyncStart) / 1000000;
|
|
185
|
+
console.log(` ✓ Success (${asyncTime.toFixed(2)} ms) - handled with timeout protection`);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.log(` ✗ Failed: ${err.message}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Clean up
|
|
191
|
+
await mimeHtml.closeWorkerPool();
|
|
192
|
+
|
|
193
|
+
console.log('\n✓ Benchmark complete!');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Run the benchmark
|
|
197
|
+
quickBenchmark().catch(err => {
|
|
198
|
+
console.error('Benchmark failed:', err);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const mimeHtml = require('../lib/mime-html');
|
|
7
|
+
|
|
8
|
+
async function quickConcurrencyTest() {
|
|
9
|
+
console.log('Quick Concurrency Test - Real-World HTML');
|
|
10
|
+
console.log('='.repeat(60));
|
|
11
|
+
|
|
12
|
+
const realWorldHtml = fs.readFileSync(path.join(__dirname, '../examples/failing.html'), 'utf8');
|
|
13
|
+
|
|
14
|
+
console.log(`Testing with ${(realWorldHtml.length / 1024).toFixed(1)} KB of problematic HTML\n`);
|
|
15
|
+
|
|
16
|
+
const iterations = 30;
|
|
17
|
+
|
|
18
|
+
// Test 1: Sequential Sync
|
|
19
|
+
console.log(`Test 1: Sequential Sync (${iterations} requests)`);
|
|
20
|
+
const syncStart = process.hrtime.bigint();
|
|
21
|
+
for (let i = 0; i < iterations; i++) {
|
|
22
|
+
mimeHtml.sync({ html: realWorldHtml });
|
|
23
|
+
}
|
|
24
|
+
const syncTime = Number(process.hrtime.bigint() - syncStart) / 1000000;
|
|
25
|
+
console.log(` Time: ${syncTime.toFixed(2)}ms`);
|
|
26
|
+
console.log(` Throughput: ${(iterations / (syncTime / 1000)).toFixed(0)} req/s\n`);
|
|
27
|
+
|
|
28
|
+
// Test 2: Concurrent Async with Pool (concurrency = 10)
|
|
29
|
+
console.log(`Test 2: Concurrent Async with Pool (${iterations} requests, concurrency=10)`);
|
|
30
|
+
const asyncStart = process.hrtime.bigint();
|
|
31
|
+
const promises = [];
|
|
32
|
+
for (let i = 0; i < iterations; i++) {
|
|
33
|
+
promises.push(
|
|
34
|
+
mimeHtml.async(
|
|
35
|
+
{ html: realWorldHtml },
|
|
36
|
+
{
|
|
37
|
+
useWorkerPool: true,
|
|
38
|
+
timeout: 5000,
|
|
39
|
+
logErrors: false
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
await Promise.all(promises);
|
|
45
|
+
const asyncTime = Number(process.hrtime.bigint() - asyncStart) / 1000000;
|
|
46
|
+
console.log(` Time: ${asyncTime.toFixed(2)}ms`);
|
|
47
|
+
console.log(` Throughput: ${(iterations / (asyncTime / 1000)).toFixed(0)} req/s\n`);
|
|
48
|
+
|
|
49
|
+
// Show speedup
|
|
50
|
+
const speedup = (syncTime / asyncTime).toFixed(2);
|
|
51
|
+
console.log('='.repeat(60));
|
|
52
|
+
console.log('Results:');
|
|
53
|
+
console.log(`• Concurrent processing is ${speedup}x faster than sequential`);
|
|
54
|
+
console.log(`• No timeouts occurred (pre-filtering working correctly)`);
|
|
55
|
+
|
|
56
|
+
const stats = mimeHtml.getWorkerPoolStats();
|
|
57
|
+
if (stats) {
|
|
58
|
+
console.log(`• Worker pool used ${stats.totalWorkers} workers efficiently`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await mimeHtml.closeWorkerPool();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
quickConcurrencyTest().catch(console.error);
|