diff --git a/eth/tracers/live/perf.go b/eth/tracers/live/perf.go index 51c6861987..bda674d34f 100644 --- a/eth/tracers/live/perf.go +++ b/eth/tracers/live/perf.go @@ -17,7 +17,6 @@ package live import ( - "encoding/csv" "encoding/json" "errors" "fmt" @@ -36,20 +35,21 @@ func init() { } type perfTracerConfig struct { - CSVPath string `json:"csvPath"` + Path string `json:"path"` } // perfTracer is a live tracer that measures and records transaction processing performance metrics. // It tracks total processing time, IO time (account and storage reads), and EVM execution time for -// each transaction. The metrics are written to a CSV file. +// each transaction. The metrics are written to a JSONL file. type perfTracer struct { - csvPath string - writer *csv.Writer + path string file *os.File + encoder *json.Encoder // Block context currentBlock *types.Block currentBlockHash common.Hash + blockStartTime time.Time // Transaction tracking txStartTime time.Time @@ -59,6 +59,9 @@ type perfTracer struct { prevAccountReads time.Duration prevStorageReads time.Duration + // Transaction data collection + txData []map[string]interface{} + statedb tracing.StateDB } @@ -67,36 +70,20 @@ func newPerfTracer(cfg json.RawMessage) (*tracing.Hooks, error) { if err := json.Unmarshal(cfg, &config); err != nil { return nil, fmt.Errorf("failed to parse config: %v", err) } - if config.CSVPath == "" { - return nil, errors.New("csv path is required") + if config.Path == "" { + return nil, errors.New("path is required") } - // Open CSV file - file, err := os.OpenFile(config.CSVPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + // Open JSONL file + file, err := os.OpenFile(config.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - return nil, fmt.Errorf("failed to open CSV file: %v", err) - } - writer := csv.NewWriter(file) - - // Write header if file is empty - info, err := file.Stat() - if err != nil { - file.Close() - return nil, fmt.Errorf("failed to get file info: %v", err) - } - if info.Size() == 0 { - header := []string{"block_number", "block_hash", "tx_index", "tx_hash", "total_time_ns", "io_time_ns", "evm_time_ns", "gas_used"} - if err := writer.Write(header); err != nil { - file.Close() - return nil, fmt.Errorf("failed to write CSV header: %v", err) - } - writer.Flush() + return nil, fmt.Errorf("failed to open JSONL file: %v", err) } t := &perfTracer{ - csvPath: config.CSVPath, - writer: writer, + path: config.Path, file: file, + encoder: json.NewEncoder(file), } return &tracing.Hooks{ @@ -112,6 +99,8 @@ func (t *perfTracer) OnBlockStart(event tracing.BlockEvent) { t.currentBlock = event.Block t.currentBlockHash = event.Block.Hash() t.txIndex = 0 + t.txData = make([]map[string]interface{}, 0) + t.blockStartTime = time.Now() // Reset previous IO measurements for the new block t.prevAccountReads = 0 t.prevStorageReads = 0 @@ -143,36 +132,49 @@ func (t *perfTracer) OnTxEnd(receipt *types.Receipt, err error) { evmTime = totalTime - ioTime } - row := []string{ - t.currentBlock.Number().String(), - t.currentBlockHash.Hex(), - fmt.Sprintf("%d", t.txIndex), - receipt.TxHash.Hex(), - fmt.Sprintf("%d", totalTime.Nanoseconds()), - fmt.Sprintf("%d", ioTime.Nanoseconds()), - fmt.Sprintf("%d", evmTime.Nanoseconds()), - fmt.Sprintf("%d", receipt.GasUsed), - } - if err := t.writer.Write(row); err != nil { - fmt.Printf("Failed to write CSV row: %v\n", err) + txRecord := map[string]interface{}{ + "txIndex": fmt.Sprintf("0x%x", t.txIndex), + "txHash": receipt.TxHash.Hex(), + "gasUsed": fmt.Sprintf("0x%x", receipt.GasUsed), + "totalTime": fmt.Sprintf("0x%x", totalTime.Nanoseconds()), + "ioTime": fmt.Sprintf("0x%x", ioTime.Nanoseconds()), + "evmTime": fmt.Sprintf("0x%x", evmTime.Nanoseconds()), } + t.txData = append(t.txData, txRecord) + t.prevAccountReads = accumulatedIO.AccountReads t.prevStorageReads = accumulatedIO.StorageReads t.txIndex++ } -// OnBlockEnd implements tracing.BlockEndHook func (t *perfTracer) OnBlockEnd(err error) { - if t.writer != nil { - t.writer.Flush() + // Calculate block-level timings + totalTime := time.Since(t.blockStartTime) + blockEndIO := t.statedb.GetAccumulatedIOMeasurements() + ioTime := blockEndIO.AccountReads + blockEndIO.StorageReads + evmTime := totalTime - ioTime + + blockRecord := map[string]interface{}{ + "blockNumber": fmt.Sprintf("0x%x", t.currentBlock.Number()), + "blockHash": t.currentBlockHash.Hex(), + "gasUsed": fmt.Sprintf("0x%x", t.currentBlock.GasUsed()), + "totalTime": fmt.Sprintf("0x%x", totalTime.Nanoseconds()), + "ioTime": fmt.Sprintf("0x%x", ioTime.Nanoseconds()), + "evmTime": fmt.Sprintf("0x%x", evmTime.Nanoseconds()), + "transactions": t.txData, + } + + if err := t.encoder.Encode(blockRecord); err != nil { + fmt.Printf("Failed to write block record: %v\n", err) + } + + if t.file != nil { + t.file.Sync() } } func (t *perfTracer) OnClose() { - if t.writer != nil { - t.writer.Flush() - } if t.file != nil { t.file.Close() }