Skip to content

Commit 3d4e14b

Browse files
committed
feat: add performance benchmarks and npm script
1 parent bc53d56 commit 3d4e14b

File tree

3 files changed

+201
-1
lines changed

3 files changed

+201
-1
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ npm run compile
217217
# Run tests
218218
npm test
219219

220+
# Run benchmarks
221+
npm run benchmark
222+
220223
# Watch mode for development
221224
npm run watch
222225

benchmark.js

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Mock jest and vscode using existing mock structure
2+
global.jest = { fn: (impl) => impl || (() => {}) };
3+
4+
// Use the existing vscode mock pattern
5+
const Module = require('module');
6+
const originalRequire = Module.prototype.require;
7+
8+
Module.prototype.require = function(id) {
9+
if (id === 'vscode') {
10+
const vscode = require('./out/test/mocks/vscode.js');
11+
return vscode.default;
12+
}
13+
return originalRequire.apply(this, arguments);
14+
};
15+
16+
const { parseTemplate } = require('./out/parsers/templateParser.js');
17+
const { isSerilogCall } = require('./out/utils/serilogDetector.js');
18+
const { CacheManager } = require('./out/utils/cacheManager.js');
19+
20+
function benchmark(name, iterations, operation) {
21+
// Warmup
22+
for (let i = 0; i < Math.min(1000, iterations / 10); i++) {
23+
operation();
24+
}
25+
26+
const measurements = [];
27+
for (let run = 0; run < 10; run++) {
28+
const start = process.hrtime.bigint();
29+
for (let i = 0; i < iterations; i++) {
30+
operation();
31+
}
32+
const end = process.hrtime.bigint();
33+
34+
const totalNs = Number(end - start);
35+
const avgNs = totalNs / iterations;
36+
measurements.push(avgNs);
37+
}
38+
39+
measurements.sort((a, b) => a - b);
40+
const mean = measurements.reduce((a, b) => a + b) / measurements.length;
41+
const median = measurements[Math.floor(measurements.length / 2)];
42+
43+
const unit = mean < 1000 ? 'ns' : mean < 1000000 ? 'μs' : 'ms';
44+
const divisor = unit === 'ns' ? 1 : unit === 'μs' ? 1000 : 1000000;
45+
46+
console.log(`${name.padEnd(30)} | Mean: ${(mean / divisor).toFixed(1).padStart(7)} ${unit} | Median: ${(median / divisor).toFixed(1).padStart(7)} ${unit}`);
47+
}
48+
49+
console.log('Serilog Syntax Benchmarks');
50+
console.log('=========================');
51+
console.log();
52+
53+
// Parser Benchmarks
54+
console.log('ParserBenchmarks');
55+
console.log('---------------');
56+
57+
benchmark('ParseSimpleTemplate', 100000, () => {
58+
parseTemplate("Simple template with {Property}");
59+
});
60+
61+
benchmark('ParseComplexTemplate', 100000, () => {
62+
parseTemplate("Complex {@User} with {Count:N0} and {Timestamp:yyyy-MM-dd HH:mm:ss}");
63+
});
64+
65+
benchmark('ParseMultipleProperties', 100000, () => {
66+
parseTemplate("Multiple {Prop1} {Prop2} {Prop3} {Prop4} {Prop5}");
67+
});
68+
69+
benchmark('ParseAllTemplates', 10000, () => {
70+
const templates = [
71+
"Simple template with {Property}",
72+
"Complex {@User} with {Count:N0} and {Timestamp:yyyy-MM-dd HH:mm:ss}",
73+
"Multiple {Prop1} {Prop2} {Prop3} {Prop4} {Prop5}",
74+
"Nested {Outer,10:F2} and {@Inner} with {$String}",
75+
"Positional {0} {1} {2} mixed with {Named}",
76+
"Verbatim string with {Property1} and {Property2}",
77+
"Malformed {Unclosed and {Valid} property",
78+
"Empty {} and {Valid} with {@Destructured}"
79+
];
80+
for (const template of templates) {
81+
parseTemplate(template);
82+
}
83+
});
84+
85+
benchmark('ParseWithErrorRecovery', 100000, () => {
86+
parseTemplate("Malformed {Unclosed and {Valid} property");
87+
});
88+
89+
benchmark('ParseVerbatimString', 100000, () => {
90+
parseTemplate("Verbatim string with {Property1} and {Property2}");
91+
});
92+
93+
console.log();
94+
95+
// Cache Benchmarks
96+
console.log('CacheBenchmarks');
97+
console.log('--------------');
98+
99+
let cache = new CacheManager(100);
100+
let cacheKeys = [];
101+
102+
for (let i = 0; i < 200; i++) {
103+
cacheKeys.push(`Log.Information("Test {Property${i}}")`);
104+
}
105+
106+
for (let i = 0; i < 50; i++) {
107+
cache.set(cacheKeys[i], i % 2 === 0);
108+
}
109+
110+
benchmark('CacheHitPerformance', 50000, () => {
111+
for (let i = 0; i < 50; i++) {
112+
cache.get(cacheKeys[i]);
113+
}
114+
});
115+
116+
benchmark('CacheMissPerformance', 50000, () => {
117+
for (let i = 150; i < 200; i++) {
118+
cache.get(cacheKeys[i]);
119+
}
120+
});
121+
122+
benchmark('CacheAddPerformance', 10000, () => {
123+
const localCache = new CacheManager(100);
124+
for (let i = 0; i < 100; i++) {
125+
localCache.set(cacheKeys[i], true);
126+
}
127+
});
128+
129+
benchmark('CacheEvictionPerformance', 5000, () => {
130+
const localCache = new CacheManager(50);
131+
for (let i = 0; i < 100; i++) {
132+
localCache.set(cacheKeys[i], true);
133+
}
134+
});
135+
136+
benchmark('CacheMixedOperations', 1000, () => {
137+
const localCache = new CacheManager(100);
138+
for (let i = 0; i < 200; i++) {
139+
const key = cacheKeys[i % cacheKeys.length];
140+
if (!localCache.get(key)) {
141+
localCache.set(key, i % 2 === 0);
142+
}
143+
}
144+
});
145+
146+
console.log();
147+
148+
// Call Detector Benchmarks
149+
console.log('CallDetectorBenchmarks');
150+
console.log('---------------------');
151+
152+
const serilogLines = [
153+
"_logger.LogInformation(\"User {UserId} logged in\", userId);",
154+
"Log.Information(\"Processing {Count} items\", count);",
155+
"_logger.LogError(ex, \"Failed to process {ItemId}\", id);",
156+
"logger.BeginScope(\"Operation {OperationId}\", opId);",
157+
".WriteTo.Console(outputTemplate: \"[{Timestamp}] {Message}\")"
158+
];
159+
160+
const nonSerilogLines = [
161+
"Console.WriteLine(\"Hello World\");",
162+
"var result = ProcessData(input);",
163+
"if (condition) { return true; }",
164+
"// This is a comment about logging",
165+
"string message = \"User logged in\";"
166+
];
167+
168+
const mixedLines = [];
169+
for (let i = 0; i < 100; i++) {
170+
if (i % 3 === 0) {
171+
mixedLines.push(serilogLines[i % serilogLines.length]);
172+
} else {
173+
mixedLines.push(nonSerilogLines[i % nonSerilogLines.length]);
174+
}
175+
}
176+
177+
benchmark('DetectSerilogCalls', 50000, () => {
178+
for (const line of serilogLines) {
179+
isSerilogCall(line);
180+
}
181+
});
182+
183+
benchmark('DetectNonSerilogCalls', 50000, () => {
184+
for (const line of nonSerilogLines) {
185+
isSerilogCall(line);
186+
}
187+
});
188+
189+
benchmark('DetectMixedCalls', 5000, () => {
190+
for (const line of mixedLines) {
191+
isSerilogCall(line);
192+
}
193+
});
194+
195+
console.log();
196+
console.log('Benchmark completed');

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@
136136
"pretest": "npm run compile-tests && npm run compile",
137137
"test": "jest",
138138
"test:watch": "jest --watch",
139-
"test:coverage": "jest --coverage"
139+
"test:coverage": "jest --coverage",
140+
"benchmark": "npm run compile-tests && node benchmark.js"
140141
},
141142
"devDependencies": {
142143
"@types/jest": "^30.0.0",

0 commit comments

Comments
 (0)