88 * Run with: npm run benchmark
99 */
1010
11+ /* eslint-disable no-console */
12+
1113const Parse = require ( 'parse/node' ) ;
1214const { performance, PerformanceObserver } = require ( 'perf_hooks' ) ;
1315const { MongoClient } = require ( 'mongodb' ) ;
@@ -17,7 +19,7 @@ const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/parse_
1719const SERVER_URL = 'http://localhost:1337/parse' ;
1820const APP_ID = 'benchmark-app-id' ;
1921const MASTER_KEY = 'benchmark-master-key' ;
20- const ITERATIONS = parseInt ( process . env . BENCHMARK_ITERATIONS || '100 ' , 10 ) ;
22+ const ITERATIONS = parseInt ( process . env . BENCHMARK_ITERATIONS || '1000 ' , 10 ) ;
2123
2224// Parse Server instance
2325let parseServer ;
@@ -39,6 +41,8 @@ async function initializeParseServer() {
3941 serverURL : SERVER_URL ,
4042 silent : true ,
4143 allowClientClassCreation : true ,
44+ logLevel : 'error' , // Minimal logging for performance
45+ verbose : false ,
4246 } ) ;
4347
4448 app . use ( '/parse' , parseServer . app ) ;
@@ -84,33 +88,52 @@ async function cleanupDatabase() {
8488
8589/**
8690 * Measure average time for an async operation over multiple iterations
91+ * Uses warmup iterations, median metric, and outlier filtering for robustness
8792 */
8893async function measureOperation ( name , operation , iterations = ITERATIONS ) {
94+ const warmupCount = Math . floor ( iterations * 0.2 ) ; // 20% warmup iterations
8995 const times = [ ] ;
9096
97+ // Warmup phase - stabilize JIT compilation and caches
98+ for ( let i = 0 ; i < warmupCount ; i ++ ) {
99+ await operation ( ) ;
100+ }
101+
102+ // Measurement phase
91103 for ( let i = 0 ; i < iterations ; i ++ ) {
92104 const start = performance . now ( ) ;
93105 await operation ( ) ;
94106 const end = performance . now ( ) ;
95107 times . push ( end - start ) ;
96108 }
97109
98- // Calculate statistics
110+ // Sort times for percentile calculations
99111 times . sort ( ( a , b ) => a - b ) ;
100- const sum = times . reduce ( ( acc , val ) => acc + val , 0 ) ;
101- const mean = sum / times . length ;
102- const p50 = times [ Math . floor ( times . length * 0.5 ) ] ;
103- const p95 = times [ Math . floor ( times . length * 0.95 ) ] ;
104- const p99 = times [ Math . floor ( times . length * 0.99 ) ] ;
105- const min = times [ 0 ] ;
106- const max = times [ times . length - 1 ] ;
112+
113+ // Filter outliers using Interquartile Range (IQR) method
114+ const q1Index = Math . floor ( times . length * 0.25 ) ;
115+ const q3Index = Math . floor ( times . length * 0.75 ) ;
116+ const q1 = times [ q1Index ] ;
117+ const q3 = times [ q3Index ] ;
118+ const iqr = q3 - q1 ;
119+ const lowerBound = q1 - 1.5 * iqr ;
120+ const upperBound = q3 + 1.5 * iqr ;
121+
122+ const filtered = times . filter ( t => t >= lowerBound && t <= upperBound ) ;
123+
124+ // Calculate statistics on filtered data
125+ const median = filtered [ Math . floor ( filtered . length * 0.5 ) ] ;
126+ const p95 = filtered [ Math . floor ( filtered . length * 0.95 ) ] ;
127+ const p99 = filtered [ Math . floor ( filtered . length * 0.99 ) ] ;
128+ const min = filtered [ 0 ] ;
129+ const max = filtered [ filtered . length - 1 ] ;
107130
108131 return {
109132 name,
110- value : mean ,
133+ value : median , // Use median (p50) as primary metric for stability in CI
111134 unit : 'ms' ,
112135 range : `${ min . toFixed ( 2 ) } - ${ max . toFixed ( 2 ) } ` ,
113- extra : `p50 : ${ p50 . toFixed ( 2 ) } ms, p95 : ${ p95 . toFixed ( 2 ) } ms, p99: ${ p99 . toFixed ( 2 ) } ms ` ,
136+ extra : `p95 : ${ p95 . toFixed ( 2 ) } ms, p99 : ${ p99 . toFixed ( 2 ) } ms, n= ${ filtered . length } / ${ times . length } ` ,
114137 } ;
115138}
116139
@@ -225,7 +248,7 @@ async function benchmarkBatchSave() {
225248 }
226249
227250 await Parse . Object . saveAll ( objects ) ;
228- } , Math . floor ( ITERATIONS / BATCH_SIZE ) ) ; // Fewer iterations for batch operations
251+ } ) ;
229252}
230253
231254/**
@@ -241,7 +264,7 @@ async function benchmarkUserSignup() {
241264 user . set ( 'password' , 'benchmark_password' ) ;
242265 user . set ( 'email' , `benchmark${ counter } @example.com` ) ;
243266 await user . signUp ( ) ;
244- } , Math . floor ( ITERATIONS / 10 ) ) ; // Fewer iterations for user operations
267+ } ) ;
245268}
246269
247270/**
@@ -267,22 +290,21 @@ async function benchmarkUserLogin() {
267290 const userCreds = users [ counter ++ % users . length ] ;
268291 await Parse . User . logIn ( userCreds . username , userCreds . password ) ;
269292 await Parse . User . logOut ( ) ;
270- } , Math . floor ( ITERATIONS / 10 ) ) ; // Fewer iterations for user operations
293+ } ) ;
271294}
272295
273296/**
274297 * Run all benchmarks
275298 */
276299async function runBenchmarks ( ) {
277- console . error ( 'Starting Parse Server Performance Benchmarks...' ) ;
278- console . error ( `Iterations per benchmark: ${ ITERATIONS } ` ) ;
279- console . error ( '' ) ;
300+ console . log ( 'Starting Parse Server Performance Benchmarks...' ) ;
301+ console . log ( `Iterations per benchmark: ${ ITERATIONS } ` ) ;
280302
281303 let server ;
282304
283305 try {
284306 // Initialize Parse Server
285- console . error ( 'Initializing Parse Server...' ) ;
307+ console . log ( 'Initializing Parse Server...' ) ;
286308 server = await initializeParseServer ( ) ;
287309
288310 // Wait for server to be ready
@@ -291,43 +313,42 @@ async function runBenchmarks() {
291313 const results = [ ] ;
292314
293315 // Run each benchmark with database cleanup
294- console . error ( 'Running Object Create benchmark...' ) ;
316+ console . log ( 'Running Object Create benchmark...' ) ;
295317 await cleanupDatabase ( ) ;
296318 results . push ( await benchmarkObjectCreate ( ) ) ;
297319
298- console . error ( 'Running Object Read benchmark...' ) ;
320+ console . log ( 'Running Object Read benchmark...' ) ;
299321 await cleanupDatabase ( ) ;
300322 results . push ( await benchmarkObjectRead ( ) ) ;
301323
302- console . error ( 'Running Object Update benchmark...' ) ;
324+ console . log ( 'Running Object Update benchmark...' ) ;
303325 await cleanupDatabase ( ) ;
304326 results . push ( await benchmarkObjectUpdate ( ) ) ;
305327
306- console . error ( 'Running Simple Query benchmark...' ) ;
328+ console . log ( 'Running Simple Query benchmark...' ) ;
307329 await cleanupDatabase ( ) ;
308330 results . push ( await benchmarkSimpleQuery ( ) ) ;
309331
310- console . error ( 'Running Batch Save benchmark...' ) ;
332+ console . log ( 'Running Batch Save benchmark...' ) ;
311333 await cleanupDatabase ( ) ;
312334 results . push ( await benchmarkBatchSave ( ) ) ;
313335
314- console . error ( 'Running User Signup benchmark...' ) ;
336+ console . log ( 'Running User Signup benchmark...' ) ;
315337 await cleanupDatabase ( ) ;
316338 results . push ( await benchmarkUserSignup ( ) ) ;
317339
318- console . error ( 'Running User Login benchmark...' ) ;
340+ console . log ( 'Running User Login benchmark...' ) ;
319341 await cleanupDatabase ( ) ;
320342 results . push ( await benchmarkUserLogin ( ) ) ;
321343
322- // Output results in github-action-benchmark format
344+ // Output results in github-action-benchmark format (stdout)
323345 console . log ( JSON . stringify ( results , null , 2 ) ) ;
324346
325- console . error ( '' ) ;
326- console . error ( 'Benchmarks completed successfully!' ) ;
327- console . error ( '' ) ;
328- console . error ( 'Summary:' ) ;
347+ // Output summary to stderr for visibility
348+ console . log ( 'Benchmarks completed successfully!' ) ;
349+ console . log ( 'Summary:' ) ;
329350 results . forEach ( result => {
330- console . error ( ` ${ result . name } : ${ result . value . toFixed ( 2 ) } ${ result . unit } (${ result . extra } )` ) ;
351+ console . log ( ` ${ result . name } : ${ result . value . toFixed ( 2 ) } ${ result . unit } (${ result . extra } )` ) ;
331352 } ) ;
332353
333354 } catch ( error ) {
0 commit comments