|
21 | 21 | TURN_COUNT_PATTERN = re.compile(r"--- Start of group: Sending request to the AI model ---") |
22 | 22 |
|
23 | 23 |
|
| 24 | +def _parse_token_count(s: str) -> int: |
| 25 | + if s.endswith("m"): |
| 26 | + return int(float(s[:-1]) * 1000000) |
| 27 | + if s.endswith("k"): |
| 28 | + return int(float(s[:-1]) * 1000) |
| 29 | + return int(float(s)) |
| 30 | + |
| 31 | + |
24 | 32 | def parse_session_log(log_path: Path) -> tuple[dict[str, int], int]: |
25 | 33 | """Parse tool usage and step count from a single Copilot CLI log file. |
26 | 34 |
|
@@ -49,7 +57,12 @@ def parse_metrics(output_lines: Sequence[str], session_log_path: Path | None = N |
49 | 57 | output_lines: Lines from Copilot CLI stderr output |
50 | 58 | session_log_path: Optional path to session log file for tool usage parsing |
51 | 59 |
|
52 | | - Expected output format at the end: |
| 60 | + Expected output format (new, v1.0.2+): |
| 61 | + Changes +17 -0 |
| 62 | + Requests 0.33 Premium (1m 45s) |
| 63 | + Tokens ↑ 317.5k • ↓ 4.3k • 255.0k (cached) |
| 64 | +
|
| 65 | + Legacy output format: |
53 | 66 | Total usage est: 0.33 Premium requests |
54 | 67 | API time spent: 2m 10.145s |
55 | 68 | Total session time: 2m 41.651s |
@@ -85,35 +98,40 @@ def parse_metrics(output_lines: Sequence[str], session_log_path: Path | None = N |
85 | 98 | turn_count = None |
86 | 99 |
|
87 | 100 | try: |
88 | | - # Parse LLM duration (API time) |
| 101 | + # Parse LLM duration (API time) — legacy format |
89 | 102 | llm_duration_match = re.search(r"API time spent:\s*(?:(\d+)m\s*)?(\d+(?:\.\d+)?)s", output_text) |
90 | 103 | if llm_duration_match: |
91 | 104 | minutes = int(llm_duration_match.group(1)) if llm_duration_match.group(1) else 0 |
92 | 105 | seconds = float(llm_duration_match.group(2)) |
93 | 106 | llm_duration = minutes * 60 + seconds |
94 | 107 |
|
95 | | - # Parse wall clock duration |
| 108 | + # Parse wall clock duration — legacy format |
96 | 109 | duration_match = re.search(r"Total session time:\s*(?:(\d+)m\s*)?(\d+(?:\.\d+)?)s", output_text) |
97 | 110 | if duration_match: |
98 | 111 | minutes = int(duration_match.group(1)) if duration_match.group(1) else 0 |
99 | 112 | seconds = float(duration_match.group(2)) |
100 | 113 | execution_time = minutes * 60 + seconds |
101 | 114 |
|
102 | | - # Token usage: "1.3m in, 11.6k out" |
| 115 | + # New format: "Requests 0.33 Premium (1m 45s)" — extract session time from parenthesized duration |
| 116 | + if execution_time is None: |
| 117 | + requests_match = re.search(r"Requests\s+[\d.]+\s+Premium\s+\((?:(\d+)m\s*)?(\d+(?:\.\d+)?)s\)", output_text) |
| 118 | + if requests_match: |
| 119 | + minutes = int(requests_match.group(1)) if requests_match.group(1) else 0 |
| 120 | + seconds = float(requests_match.group(2)) |
| 121 | + execution_time = minutes * 60 + seconds |
| 122 | + |
| 123 | + # Token usage — legacy format: "1.3m in, 11.6k out" |
103 | 124 | usage_match = re.search(r"(\d+(?:\.\d+)?[km]?)\s+in,\s*(\d+(?:\.\d+)?[km]?)\s+out", output_text) |
104 | 125 | if usage_match: |
105 | | - input_str = usage_match.group(1) |
106 | | - output_str = usage_match.group(2) |
107 | | - |
108 | | - def parse_token_count(s: str) -> int: |
109 | | - if s.endswith("m"): |
110 | | - return int(float(s[:-1]) * 1000000) |
111 | | - if s.endswith("k"): |
112 | | - return int(float(s[:-1]) * 1000) |
113 | | - return int(float(s)) |
114 | | - |
115 | | - prompt_tokens = parse_token_count(input_str) |
116 | | - completion_tokens = parse_token_count(output_str) |
| 126 | + prompt_tokens = _parse_token_count(usage_match.group(1)) |
| 127 | + completion_tokens = _parse_token_count(usage_match.group(2)) |
| 128 | + |
| 129 | + # New format: "Tokens ↑ 317.5k • ↓ 4.3k • 255.0k (cached)" |
| 130 | + if prompt_tokens is None: |
| 131 | + tokens_match = re.search(r"Tokens\s+[^\d]*(\d+(?:\.\d+)?[km]?)\s*[•·]\s*[^\d]*(\d+(?:\.\d+)?[km]?)", output_text) |
| 132 | + if tokens_match: |
| 133 | + prompt_tokens = _parse_token_count(tokens_match.group(1)) |
| 134 | + completion_tokens = _parse_token_count(tokens_match.group(2)) |
117 | 135 |
|
118 | 136 | if execution_time is not None or llm_duration is not None or prompt_tokens is not None or completion_tokens is not None or tool_usage is not None or turn_count is not None: |
119 | 137 | return AgentMetrics( |
|
0 commit comments