1
1
#!/usr/bin/env node
2
2
3
3
const fs = require ( "fs" ) ;
4
- const { exec } = require ( "child_process" ) ;
4
+ const { execSync } = require ( "child_process" ) ;
5
5
const path = require ( "path" ) ;
6
6
7
- // Helper functions
8
- const extractMetric = ( file , metric ) => {
9
- const content = fs . readFileSync ( file , "utf-8" ) ;
10
- const regex = new RegExp ( `${ metric } \\s+(\\d+\\.?\\d*)` ) ;
11
- const match = content . match ( regex ) ;
12
- return match ? parseFloat ( match [ 1 ] ) : null ;
13
- } ;
7
+ function extractMetric ( file , metric ) {
8
+ try {
9
+ const command = `grep "${ metric } " "${ file } " | awk '{print $2}' | sed 's/ms//'` ;
10
+ const result = execSync ( command , { encoding : "utf-8" } ) . trim ( ) ;
11
+ return result ;
12
+ } catch ( error ) {
13
+ console . error ( `Error extracting metric from ${ file } : ${ error . message } ` ) ;
14
+ return null ;
15
+ }
16
+ }
14
17
15
- const average = ( values ) =>
16
- values . reduce ( ( sum , value ) => sum + value , 0 ) / values . length ;
18
+ function average ( values ) {
19
+ const sum = values . reduce ( ( a , b ) => parseFloat ( a ) + parseFloat ( b ) , 0 ) ;
20
+ return sum / values . length ;
21
+ }
17
22
18
23
const formattedServerNames = {
19
24
tailcall : "Tailcall" ,
@@ -40,14 +45,16 @@ const resultFiles = process.argv.slice(2);
40
45
const avgReqSecs = { } ;
41
46
const avgLatencies = { } ;
42
47
43
- // Extract metrics and calculate averages
44
48
servers . forEach ( ( server , idx ) => {
49
+ const startIdx = idx * 3 ;
45
50
const reqSecVals = [ ] ;
46
51
const latencyVals = [ ] ;
47
52
for ( let j = 0 ; j < 3 ; j ++ ) {
48
- const fileIdx = idx * 3 + j ;
49
- reqSecVals . push ( extractMetric ( resultFiles [ fileIdx ] , "Requests/sec" ) ) ;
50
- latencyVals . push ( extractMetric ( resultFiles [ fileIdx ] , "Latency" ) ) ;
53
+ const fileIdx = startIdx + j ;
54
+ const reqSec = extractMetric ( resultFiles [ fileIdx ] , "Requests/sec" ) ;
55
+ const latency = extractMetric ( resultFiles [ fileIdx ] , "Latency" ) ;
56
+ if ( reqSec !== null ) reqSecVals . push ( reqSec ) ;
57
+ if ( latency !== null ) latencyVals . push ( latency ) ;
51
58
}
52
59
avgReqSecs [ server ] = average ( reqSecVals ) ;
53
60
avgLatencies [ server ] = average ( latencyVals ) ;
@@ -57,24 +64,17 @@ servers.forEach((server, idx) => {
57
64
const reqSecData = "/tmp/reqSec.dat" ;
58
65
const latencyData = "/tmp/latency.dat" ;
59
66
60
- const writeDataFile = ( filePath , data ) => {
61
- const content = [
62
- "Server Value" ,
63
- ...data . map ( ( { server, value } ) => `${ server } ${ value } ` ) ,
64
- ] . join ( "\n" ) ;
65
- fs . writeFileSync ( filePath , content ) ;
66
- } ;
67
-
68
- writeDataFile (
67
+ fs . writeFileSync (
69
68
reqSecData ,
70
- servers . map ( ( server ) => ( { server, value : avgReqSecs [ server ] } ) )
69
+ "Server Value\n" +
70
+ servers . map ( ( server ) => `${ server } ${ avgReqSecs [ server ] } ` ) . join ( "\n" )
71
71
) ;
72
- writeDataFile (
72
+ fs . writeFileSync (
73
73
latencyData ,
74
- servers . map ( ( server ) => ( { server, value : avgLatencies [ server ] } ) )
74
+ "Server Value\n" +
75
+ servers . map ( ( server ) => `${ server } ${ avgLatencies [ server ] } ` ) . join ( "\n" )
75
76
) ;
76
77
77
- // Determine which benchmark to use
78
78
let whichBench = 1 ;
79
79
if ( resultFiles [ 0 ] . startsWith ( "bench2" ) ) {
80
80
whichBench = 2 ;
@@ -85,37 +85,68 @@ if (resultFiles[0].startsWith("bench2")) {
85
85
const reqSecHistogramFile = `req_sec_histogram${ whichBench } .png` ;
86
86
const latencyHistogramFile = `latency_histogram${ whichBench } .png` ;
87
87
88
- // Plotting using gnuplot
89
- const plotWithGnuplot = ( outputFile , title , dataFile ) => {
90
- const script = `
91
- set term pngcairo size 1280,720 enhanced font "Courier,12"
92
- set output "${ outputFile } "
93
- set style data histograms
94
- set style histogram cluster gap 1
95
- set style fill solid border -1
96
- set xtics rotate by -45
97
- set boxwidth 0.9
98
- set title "${ title } "
99
- stats "${ dataFile } " using 2 nooutput
100
- set yrange [0:STATS_max*1.2]
101
- set key outside right top
102
- plot "${ dataFile } " using 2:xtic(1) title "${ title . split ( " " ) [ 0 ] } "
103
- ` ;
104
- exec ( `gnuplot -e '${ script } '` ) ;
105
- } ;
88
+ function getMaxValue ( data ) {
89
+ return Math . max (
90
+ ...data
91
+ . split ( "\n" )
92
+ . slice ( 1 )
93
+ . map ( ( line ) => parseFloat ( line . split ( " " ) [ 1 ] ) )
94
+ ) ;
95
+ }
106
96
107
- plotWithGnuplot ( reqSecHistogramFile , "Requests/Sec" , reqSecData ) ;
108
- plotWithGnuplot ( latencyHistogramFile , "Latency (in ms)" , latencyData ) ;
97
+ const reqSecMax = getMaxValue ( fs . readFileSync ( reqSecData , "utf-8" ) ) * 1.2 ;
98
+ const latencyMax = getMaxValue ( fs . readFileSync ( latencyData , "utf-8" ) ) * 1.2 ;
99
+
100
+ const gnuplotScript = `
101
+ set term pngcairo size 1280,720 enhanced font 'Courier,12'
102
+ set output '${ reqSecHistogramFile } '
103
+ set style data histograms
104
+ set style histogram cluster gap 1
105
+ set style fill solid border -1
106
+ set xtics rotate by -45
107
+ set boxwidth 0.9
108
+ set title 'Requests/Sec'
109
+ set yrange [0:${ reqSecMax } ]
110
+ set key outside right top
111
+ plot '${ reqSecData } ' using 2:xtic(1) title 'Req/Sec'
112
+ set output '${ latencyHistogramFile } '
113
+ set title 'Latency (in ms)'
114
+ set yrange [0:${ latencyMax } ]
115
+ plot '${ latencyData } ' using 2:xtic(1) title 'Latency'
116
+ ` ;
117
+
118
+ const gnuplotScriptFile = "/tmp/gnuplot_script.gp" ;
119
+ fs . writeFileSync ( gnuplotScriptFile , gnuplotScript ) ;
120
+
121
+ try {
122
+ execSync ( `gnuplot ${ gnuplotScriptFile } ` , { stdio : "inherit" } ) ;
123
+ console . log ( "Gnuplot executed successfully" ) ;
124
+ } catch ( error ) {
125
+ console . error ( "Error executing gnuplot:" , error . message ) ;
126
+ process . exit ( 1 ) ;
127
+ }
109
128
110
- // Move PNGs to assets
111
129
const assetsDir = path . join ( __dirname , "assets" ) ;
112
130
if ( ! fs . existsSync ( assetsDir ) ) {
113
131
fs . mkdirSync ( assetsDir ) ;
114
132
}
115
- fs . renameSync ( reqSecHistogramFile , path . join ( assetsDir , reqSecHistogramFile ) ) ;
116
- fs . renameSync ( latencyHistogramFile , path . join ( assetsDir , latencyHistogramFile ) ) ;
117
133
118
- // Calculate relative performance and build the results table
134
+ function moveFile ( source , destination ) {
135
+ try {
136
+ if ( fs . existsSync ( source ) ) {
137
+ fs . renameSync ( source , destination ) ;
138
+ console . log ( `Moved ${ source } to ${ destination } ` ) ;
139
+ } else {
140
+ console . log ( `Source file ${ source } does not exist` ) ;
141
+ }
142
+ } catch ( error ) {
143
+ console . error ( `Error moving file ${ source } : ${ error . message } ` ) ;
144
+ }
145
+ }
146
+
147
+ moveFile ( reqSecHistogramFile , path . join ( assetsDir , reqSecHistogramFile ) ) ;
148
+ moveFile ( latencyHistogramFile , path . join ( assetsDir , latencyHistogramFile ) ) ;
149
+
119
150
const serverRPS = { } ;
120
151
servers . forEach ( ( server ) => {
121
152
serverRPS [ server ] = avgReqSecs [ server ] ;
@@ -127,14 +158,28 @@ const sortedServers = Object.keys(serverRPS).sort(
127
158
const lastServer = sortedServers [ sortedServers . length - 1 ] ;
128
159
const lastServerReqSecs = avgReqSecs [ lastServer ] ;
129
160
161
+ const resultsFile = "results.md" ;
162
+
163
+ if (
164
+ ! fs . existsSync ( resultsFile ) ||
165
+ fs . readFileSync ( resultsFile , "utf8" ) . trim ( ) === ""
166
+ ) {
167
+ fs . writeFileSync (
168
+ resultsFile ,
169
+ `<!-- PERFORMANCE_RESULTS_START -->
170
+ | Query | Server | Requests/sec | Latency (ms) | Relative |
171
+ |-------:|--------:|--------------:|--------------:|---------:|`
172
+ ) ;
173
+ }
174
+
130
175
let resultsTable = "" ;
131
176
132
177
if ( whichBench === 1 ) {
133
- resultsTable += `<!-- PERFORMANCE_RESULTS_START -->\n\n| Query | Server | Requests/sec | Latency (ms) | Relative |\n|-------:|--------:|--------------:|--------------:|---------:| \n| ${ whichBench } | \`{ posts { id userId title user { id name email }}}\` |` ;
178
+ resultsTable += `\n| ${ whichBench } | \`{ posts { id userId title user { id name email }}}\` |` ;
134
179
} else if ( whichBench === 2 ) {
135
- resultsTable += `| ${ whichBench } | \`{ posts { title }}\` |` ;
180
+ resultsTable += `\n | ${ whichBench } | \`{ posts { title }}\` |` ;
136
181
} else if ( whichBench === 3 ) {
137
- resultsTable += `| ${ whichBench } | \`{ greet }\` |` ;
182
+ resultsTable += `\n | ${ whichBench } | \`{ greet }\` |` ;
138
183
}
139
184
140
185
sortedServers . forEach ( ( server ) => {
@@ -153,14 +198,11 @@ sortedServers.forEach((server) => {
153
198
resultsTable += `\n|| [${ formattedServerNames [ server ] } ] | \`${ formattedReqSecs } \` | \`${ formattedLatencies } \` | \`${ relativePerformance } x\` |` ;
154
199
} ) ;
155
200
156
- if ( whichBench === 3 ) {
157
- resultsTable += `\n\n<!-- PERFORMANCE_RESULTS_END -->` ;
158
- }
159
-
160
- const resultsFile = "results.md" ;
161
- fs . writeFileSync ( resultsFile , resultsTable ) ;
201
+ fs . appendFileSync ( resultsFile , resultsTable ) ;
162
202
163
203
if ( whichBench === 3 ) {
204
+ fs . appendFileSync ( resultsFile , "\n\n<!-- PERFORMANCE_RESULTS_END -->" ) ;
205
+
164
206
const finalResults = fs
165
207
. readFileSync ( resultsFile , "utf-8" )
166
208
. replace ( / ( \r \n | \n | \r ) / gm, "\\n" ) ;
0 commit comments