// SPDX-Licence-Identifier: EUPL-1.2 package session import ( "io/fs" "path" "testing" core "dappco.re/go/core" ) // BenchmarkParseTranscript benchmarks parsing a ~1MB+ JSONL file. func BenchmarkParseTranscript(b *testing.B) { dir := b.TempDir() path := generateBenchJSONL(b, dir, 5000) // ~1MB+ of JSONL b.ResetTimer() b.ReportAllocs() for b.Loop() { sess, _, err := ParseTranscript(path) if err != nil { b.Fatal(err) } if len(sess.Events) == 0 { b.Fatal("expected events") } } } // BenchmarkParseTranscript_Large benchmarks a larger ~5MB file. func BenchmarkParseTranscript_Large(b *testing.B) { dir := b.TempDir() path := generateBenchJSONL(b, dir, 25000) // ~5MB b.ResetTimer() b.ReportAllocs() for b.Loop() { _, _, err := ParseTranscript(path) if err != nil { b.Fatal(err) } } } // BenchmarkListSessions benchmarks listing sessions in a directory. func BenchmarkListSessions(b *testing.B) { dir := b.TempDir() // Create 20 session files for range 20 { generateBenchJSONL(b, dir, 100) } b.ResetTimer() b.ReportAllocs() for b.Loop() { sessions, err := ListSessions(dir) if err != nil { b.Fatal(err) } if len(sessions) == 0 { b.Fatal("expected sessions") } } } // BenchmarkSearch benchmarks searching across multiple sessions. func BenchmarkSearch(b *testing.B) { dir := b.TempDir() // Create 10 session files with varied content for range 10 { generateBenchJSONL(b, dir, 500) } b.ResetTimer() b.ReportAllocs() for b.Loop() { _, err := Search(dir, "echo") if err != nil { b.Fatal(err) } } } // generateBenchJSONL creates a synthetic JSONL file with the given number of tool pairs. // Returns the file path. func generateBenchJSONL(b testing.TB, dir string, numTools int) string { b.Helper() sb := core.NewBuilder() baseTS := "2026-02-20T10:00:00Z" // Opening user message sb.WriteString(core.Sprintf(`{"type":"user","timestamp":"%s","sessionId":"bench","message":{"role":"user","content":[{"type":"text","text":"Start benchmark session"}]}}`, baseTS)) sb.WriteByte('\n') for i := range numTools { toolID := core.Sprintf("tool-%d", i) offset := i * 2 // Alternate between different tool types for realistic distribution var toolUse, toolResult string switch i % 5 { case 0: // Bash toolUse = core.Sprintf(`{"type":"assistant","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"assistant","content":[{"type":"tool_use","name":"Bash","id":"%s","input":{"command":"echo iteration %d","description":"echo test"}}]}}`, offset/60, offset%60, toolID, i) toolResult = core.Sprintf(`{"type":"user","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"%s","content":"iteration %d output line one\niteration %d output line two","is_error":false}]}}`, (offset+1)/60, (offset+1)%60, toolID, i, i) case 1: // Read toolUse = core.Sprintf(`{"type":"assistant","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"assistant","content":[{"type":"tool_use","name":"Read","id":"%s","input":{"file_path":"/tmp/bench/file-%d.go"}}]}}`, offset/60, offset%60, toolID, i) toolResult = core.Sprintf(`{"type":"user","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"%s","content":"package main\n\nfunc main() {\n\tfmt.Println(%d)\n}","is_error":false}]}}`, (offset+1)/60, (offset+1)%60, toolID, i) case 2: // Edit toolUse = core.Sprintf(`{"type":"assistant","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"assistant","content":[{"type":"tool_use","name":"Edit","id":"%s","input":{"file_path":"/tmp/bench/file-%d.go","old_string":"old","new_string":"new"}}]}}`, offset/60, offset%60, toolID, i) toolResult = core.Sprintf(`{"type":"user","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"%s","content":"ok","is_error":false}]}}`, (offset+1)/60, (offset+1)%60, toolID) case 3: // Grep toolUse = core.Sprintf(`{"type":"assistant","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"assistant","content":[{"type":"tool_use","name":"Grep","id":"%s","input":{"pattern":"TODO","path":"/tmp/bench"}}]}}`, offset/60, offset%60, toolID) toolResult = core.Sprintf(`{"type":"user","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"%s","content":"/tmp/bench/file.go:10: // TODO fix this","is_error":false}]}}`, (offset+1)/60, (offset+1)%60, toolID) case 4: // Glob toolUse = core.Sprintf(`{"type":"assistant","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"assistant","content":[{"type":"tool_use","name":"Glob","id":"%s","input":{"pattern":"**/*.go"}}]}}`, offset/60, offset%60, toolID) toolResult = core.Sprintf(`{"type":"user","timestamp":"2026-02-20T10:%02d:%02dZ","sessionId":"bench","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"%s","content":"/tmp/a.go\n/tmp/b.go\n/tmp/c.go","is_error":false}]}}`, (offset+1)/60, (offset+1)%60, toolID) } sb.WriteString(toolUse) sb.WriteByte('\n') sb.WriteString(toolResult) sb.WriteByte('\n') } // Closing assistant message sb.WriteString(core.Sprintf(`{"type":"assistant","timestamp":"2026-02-20T12:00:00Z","sessionId":"bench","message":{"role":"assistant","content":[{"type":"text","text":"Benchmark session complete."}]}}%s`, "\n")) name := core.Sprintf("bench-%d.jsonl", numTools) filePath := path.Join(dir, name) writeResult := hostFS.Write(filePath, sb.String()) if !writeResult.OK { b.Fatal(resultError(writeResult)) } statResult := hostFS.Stat(filePath) if !statResult.OK { b.Fatal(resultError(statResult)) } info, ok := statResult.Value.(fs.FileInfo) if !ok { b.Fatal("expected fs.FileInfo from Stat") } b.Logf("Generated %s: %d bytes, %d tool pairs", name, info.Size(), numTools) return filePath }