@@ -10,8 +10,11 @@ program
1010 . name ( "profile-runs-replication" )
1111 . description ( "Profile RunsReplicationService performance and identify bottlenecks" )
1212 . option ( "-c, --config <file>" , "Config file path (JSON)" )
13+ . option ( "-n, --name <name>" , "Run name/label (e.g., 'baseline', 'optimized-v1')" )
14+ . option ( "--description <text>" , "Run description (what is being tested)" )
1315 . option ( "-t, --throughput <number>" , "Target throughput (records/sec)" , "5000" )
1416 . option ( "-d, --duration <number>" , "Test duration per phase (seconds)" , "60" )
17+ . option ( "-w, --workers <number>" , "Number of producer worker processes" , "1" )
1518 . option ( "--mock-clickhouse" , "Use mock ClickHouse (CPU-only profiling)" )
1619 . option (
1720 "--profile <tool>" ,
@@ -59,6 +62,18 @@ async function loadConfig(options: any): Promise<HarnessConfig> {
5962 }
6063 }
6164
65+ if ( options . workers ) {
66+ config . producer . workerCount = parseInt ( options . workers , 10 ) ;
67+ }
68+
69+ if ( options . name ) {
70+ config . runName = options . name ;
71+ }
72+
73+ if ( options . description ) {
74+ config . runDescription = options . description ;
75+ }
76+
6277 if ( options . mockClickhouse ) {
6378 config . consumer . useMockClickhouse = true ;
6479 }
@@ -76,9 +91,12 @@ async function loadConfig(options: any): Promise<HarnessConfig> {
7691 config . output . verbose = true ;
7792 }
7893
79- // Ensure output directory exists
80- const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, "-" ) . split ( "T" ) [ 0 ] ;
81- const outputDir = path . join ( config . profiling . outputDir , timestamp ) ;
94+ // Organize output directory: profiling-results/[runName]-[timestamp]/
95+ const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, "-" ) . replace ( "T" , "_" ) . split ( "_" ) [ 0 ] ;
96+ const timeWithSeconds = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, "-" ) . replace ( "T" , "_" ) . substring ( 0 , 19 ) ;
97+ const runFolder = `${ config . runName } -${ timeWithSeconds } ` ;
98+ const outputDir = path . join ( config . profiling . outputDir , runFolder ) ;
99+
82100 config . profiling . outputDir = outputDir ;
83101 config . output . metricsFile = path . join ( outputDir , "metrics.json" ) ;
84102
@@ -89,6 +107,10 @@ function printConfig(config: HarnessConfig): void {
89107 console . log ( "\n" + "=" . repeat ( 60 ) ) ;
90108 console . log ( "RunsReplicationService Performance Test Harness" ) ;
91109 console . log ( "=" . repeat ( 60 ) ) ;
110+ console . log ( `\n🏷️ Run: ${ config . runName } ` ) ;
111+ if ( config . runDescription ) {
112+ console . log ( `📝 Description: ${ config . runDescription } ` ) ;
113+ }
92114 console . log ( "\n📋 Configuration:" ) ;
93115 console . log ( ` Profiling DB: ${ config . infrastructure . profilingDatabaseName } ` ) ;
94116 console . log ( ` Output Dir: ${ config . profiling . outputDir } ` ) ;
@@ -102,6 +124,7 @@ function printConfig(config: HarnessConfig): void {
102124 }
103125
104126 console . log ( "\n⚙️ Producer Config:" ) ;
127+ console . log ( ` Worker Processes: ${ config . producer . workerCount } ` ) ;
105128 console . log ( ` Insert/Update: ${ ( config . producer . insertUpdateRatio * 100 ) . toFixed ( 0 ) } % inserts` ) ;
106129 console . log ( ` Batch Size: ${ config . producer . batchSize } ` ) ;
107130 console . log ( ` Payload Size: ${ config . producer . payloadSizeKB } KB` ) ;
@@ -133,6 +156,93 @@ function printSummary(phases: any[]): void {
133156 console . log ( "=" . repeat ( 60 ) + "\n" ) ;
134157}
135158
159+ async function createSummaryReport (
160+ config : HarnessConfig ,
161+ phases : any [ ] ,
162+ outputPath : string
163+ ) : Promise < void > {
164+ const lines : string [ ] = [ ] ;
165+
166+ // Header
167+ lines . push ( `# Performance Test Run: ${ config . runName } ` ) ;
168+ lines . push ( "" ) ;
169+ lines . push ( `**Date**: ${ new Date ( ) . toISOString ( ) } ` ) ;
170+ if ( config . runDescription ) {
171+ lines . push ( `**Description**: ${ config . runDescription } ` ) ;
172+ }
173+ lines . push ( "" ) ;
174+
175+ // Configuration
176+ lines . push ( "## Configuration" ) ;
177+ lines . push ( "" ) ;
178+ lines . push ( `- **Producer Workers**: ${ config . producer . workerCount } ` ) ;
179+ lines . push ( `- **Batch Size**: ${ config . producer . batchSize } ` ) ;
180+ lines . push ( `- **Insert/Update Ratio**: ${ ( config . producer . insertUpdateRatio * 100 ) . toFixed ( 0 ) } % inserts` ) ;
181+ lines . push ( `- **Payload Size**: ${ config . producer . payloadSizeKB } KB` ) ;
182+ lines . push ( `- **Consumer Flush Batch**: ${ config . consumer . flushBatchSize } ` ) ;
183+ lines . push ( `- **Consumer Flush Interval**: ${ config . consumer . flushIntervalMs } ms` ) ;
184+ lines . push ( `- **Consumer Max Concurrency**: ${ config . consumer . maxFlushConcurrency } ` ) ;
185+ lines . push ( `- **ClickHouse Mode**: ${ config . consumer . useMockClickhouse ? "Mock (CPU-only)" : "Real" } ` ) ;
186+ lines . push ( `- **Profiling Tool**: ${ config . profiling . tool } ` ) ;
187+ lines . push ( "" ) ;
188+
189+ // Key Results - highlight most important metrics
190+ lines . push ( "## Key Results" ) ;
191+ lines . push ( "" ) ;
192+
193+ // For flamegraph runs, focus on throughput only
194+ if ( config . profiling . enabled && config . profiling . tool !== "none" ) {
195+ lines . push ( "**Profiling Output**: See flamegraph/analysis files in this directory" ) ;
196+ lines . push ( "" ) ;
197+ lines . push ( "### Throughput" ) ;
198+ lines . push ( "" ) ;
199+ lines . push ( "| Phase | Duration | Producer (rec/sec) | Consumer (rec/sec) |" ) ;
200+ lines . push ( "|-------|----------|--------------------|--------------------|" ) ;
201+ for ( const phase of phases ) {
202+ lines . push (
203+ `| ${ phase . phase } | ${ ( phase . durationMs / 1000 ) . toFixed ( 1 ) } s | ${ phase . producerThroughput . toFixed ( 0 ) } | ${ phase . consumerThroughput . toFixed ( 0 ) } |`
204+ ) ;
205+ }
206+ } else {
207+ // For non-profiling runs, show ELU prominently
208+ lines . push ( "### Throughput & Event Loop Utilization" ) ;
209+ lines . push ( "" ) ;
210+ lines . push ( "| Phase | Duration | Producer (rec/sec) | Consumer (rec/sec) | ELU (%) |" ) ;
211+ lines . push ( "|-------|----------|--------------------|--------------------|---------|" ) ;
212+ for ( const phase of phases ) {
213+ lines . push (
214+ `| ${ phase . phase } | ${ ( phase . durationMs / 1000 ) . toFixed ( 1 ) } s | ${ phase . producerThroughput . toFixed ( 0 ) } | ${ phase . consumerThroughput . toFixed ( 0 ) } | ${ ( phase . eventLoopUtilization * 100 ) . toFixed ( 1 ) } % |`
215+ ) ;
216+ }
217+ }
218+ lines . push ( "" ) ;
219+
220+ // Detailed Metrics
221+ lines . push ( "## Detailed Metrics" ) ;
222+ lines . push ( "" ) ;
223+
224+ for ( const phase of phases ) {
225+ lines . push ( `### ${ phase . phase } ` ) ;
226+ lines . push ( "" ) ;
227+ lines . push ( `- **Duration**: ${ ( phase . durationMs / 1000 ) . toFixed ( 1 ) } s` ) ;
228+ lines . push ( `- **Records Produced**: ${ phase . recordsProduced . toLocaleString ( ) } ` ) ;
229+ lines . push ( `- **Records Consumed**: ${ phase . recordsConsumed . toLocaleString ( ) } ` ) ;
230+ lines . push ( `- **Batches Flushed**: ${ phase . batchesFlushed . toLocaleString ( ) } ` ) ;
231+ lines . push ( `- **Producer Throughput**: ${ phase . producerThroughput . toFixed ( 1 ) } rec/sec` ) ;
232+ lines . push ( `- **Consumer Throughput**: ${ phase . consumerThroughput . toFixed ( 1 ) } rec/sec` ) ;
233+ lines . push ( `- **Event Loop Utilization**: ${ ( phase . eventLoopUtilization * 100 ) . toFixed ( 1 ) } %` ) ;
234+ lines . push ( `- **Heap Used**: ${ phase . heapUsedMB . toFixed ( 1 ) } MB` ) ;
235+ lines . push ( `- **Heap Total**: ${ phase . heapTotalMB . toFixed ( 1 ) } MB` ) ;
236+ lines . push ( `- **Replication Lag P50**: ${ phase . replicationLagP50 . toFixed ( 1 ) } ms` ) ;
237+ lines . push ( `- **Replication Lag P95**: ${ phase . replicationLagP95 . toFixed ( 1 ) } ms` ) ;
238+ lines . push ( `- **Replication Lag P99**: ${ phase . replicationLagP99 . toFixed ( 1 ) } ms` ) ;
239+ lines . push ( "" ) ;
240+ }
241+
242+ // Write to file
243+ await fs . writeFile ( outputPath , lines . join ( "\n" ) ) ;
244+ }
245+
136246async function main ( ) {
137247 const options = program . opts ( ) ;
138248 const config = await loadConfig ( options ) ;
@@ -146,14 +256,20 @@ async function main() {
146256 const phases = await harness . run ( ) ;
147257 await harness . teardown ( ) ;
148258
149- // Export metrics
259+ // Export metrics JSON
150260 await harness . exportMetrics ( config . output . metricsFile ) ;
151261
262+ // Create summary report
263+ const summaryPath = path . join ( config . profiling . outputDir , "SUMMARY.md" ) ;
264+ await createSummaryReport ( config , phases , summaryPath ) ;
265+
152266 // Print summary
153267 printSummary ( phases ) ;
154268
155269 console . log ( "\n✅ Profiling complete!" ) ;
156- console . log ( `📊 Results saved to: ${ config . profiling . outputDir } \n` ) ;
270+ console . log ( `📊 Results saved to: ${ config . profiling . outputDir } ` ) ;
271+ console . log ( `📄 Summary report: ${ summaryPath } ` ) ;
272+ console . log ( `📊 Detailed metrics: ${ config . output . metricsFile } \n` ) ;
157273
158274 if ( config . profiling . enabled && config . profiling . tool !== "none" ) {
159275 console . log ( "🔥 Profiling data:" ) ;
0 commit comments