parseEventsJSONLMetrics parses a Copilot events.jsonl file and extracts log metrics. events.jsonl provides precise, structured data about a Copilot CLI session: - "session.start": session metadata (sessionId, copilotVersion) - "user.message": one per conversation turn (used to co
(path string, verbose bool)
| 171 | // Returns the extracted metrics and nil on success, or empty metrics and an |
| 172 | // error if the file cannot be read or contains no recognizable events. |
| 173 | func parseEventsJSONLMetrics(path string, verbose bool) (workflow.LogMetrics, error) { |
| 174 | copilotEventsJSONLLog.Printf("Parsing events.jsonl from: %s", path) |
| 175 | |
| 176 | var metrics workflow.LogMetrics |
| 177 | |
| 178 | // Sanitize path to prevent traversal |
| 179 | cleanPath := filepath.Clean(path) |
| 180 | |
| 181 | file, err := os.Open(cleanPath) |
| 182 | if err != nil { |
| 183 | return metrics, fmt.Errorf("failed to open events.jsonl: %w", err) |
| 184 | } |
| 185 | defer file.Close() |
| 186 | |
| 187 | toolCallMap := make(map[string]*workflow.ToolCallInfo) |
| 188 | var currentSequence []string |
| 189 | turns := 0 |
| 190 | totalTokens := 0 |
| 191 | foundAnyEvent := false |
| 192 | |
| 193 | // Per-turn timestamps used to compute Time Between Turns (TBT) |
| 194 | var turnTimestamps []time.Time |
| 195 | |
| 196 | scanner := bufio.NewScanner(file) |
| 197 | buf := make([]byte, maxScannerBufferSize) |
| 198 | scanner.Buffer(buf, maxScannerBufferSize) |
| 199 | |
| 200 | for scanner.Scan() { |
| 201 | line := strings.TrimSpace(scanner.Text()) |
| 202 | if line == "" || !strings.HasPrefix(line, "{") { |
| 203 | continue |
| 204 | } |
| 205 | |
| 206 | var entry copilotEventsJSONLEntry |
| 207 | if err := json.Unmarshal([]byte(line), &entry); err != nil { |
| 208 | copilotEventsJSONLLog.Printf("Skipping malformed events.jsonl line: %v", err) |
| 209 | continue |
| 210 | } |
| 211 | |
| 212 | foundAnyEvent = true |
| 213 | |
| 214 | switch entry.Type { |
| 215 | case "session.start": |
| 216 | copilotEventsJSONLLog.Printf("session.start: sessionId=%s copilotVersion=%s", |
| 217 | entry.Data.SessionID, entry.Data.CopilotVersion) |
| 218 | |
| 219 | case "user.message": |
| 220 | // Each user message represents one conversation turn. |
| 221 | // Save the current tool sequence before starting a new turn. |
| 222 | turns++ |
| 223 | if len(currentSequence) > 0 { |
| 224 | metrics.ToolSequences = append(metrics.ToolSequences, currentSequence) |
| 225 | currentSequence = []string{} |
| 226 | } |
| 227 | // Record the timestamp for TBT computation. |
| 228 | if entry.Timestamp != "" { |
| 229 | if ts, parseErr := time.Parse(time.RFC3339Nano, entry.Timestamp); parseErr == nil { |
| 230 | turnTimestamps = append(turnTimestamps, ts) |