()
| 101 | * Call this at the end of each turn (before processing next user message). |
| 102 | */ |
| 103 | export function logHeadlessProfilerTurn(): void { |
| 104 | // Only log in headless mode |
| 105 | if (!getIsNonInteractiveSession()) return |
| 106 | // Only log if enabled |
| 107 | if (!SHOULD_PROFILE) return |
| 108 | |
| 109 | const perf = getPerformance() |
| 110 | const allMarks = perf.getEntriesByType('mark') |
| 111 | |
| 112 | // Filter to only our headless marks |
| 113 | const marks = allMarks.filter(mark => mark.name.startsWith(MARK_PREFIX)) |
| 114 | if (marks.length === 0) return |
| 115 | |
| 116 | // Build checkpoint lookup (strip prefix for easier access) |
| 117 | const checkpointTimes = new Map<string, number>() |
| 118 | for (const mark of marks) { |
| 119 | const name = mark.name.slice(MARK_PREFIX.length) |
| 120 | checkpointTimes.set(name, mark.startTime) |
| 121 | } |
| 122 | |
| 123 | const turnStart = checkpointTimes.get('turn_start') |
| 124 | if (turnStart === undefined) return |
| 125 | |
| 126 | // Compute phase durations relative to turn_start |
| 127 | const metadata: Record<string, number | string | undefined> = { |
| 128 | turn_number: currentTurnNumber, |
| 129 | } |
| 130 | |
| 131 | // Time to system message from process start (only meaningful for turn 0) |
| 132 | // Use absolute time since perf_hooks startTime is relative to process start |
| 133 | const systemMessageTime = checkpointTimes.get('system_message_yielded') |
| 134 | if (systemMessageTime !== undefined && currentTurnNumber === 0) { |
| 135 | metadata.time_to_system_message_ms = Math.round(systemMessageTime) |
| 136 | } |
| 137 | |
| 138 | // Time to query start |
| 139 | const queryStartTime = checkpointTimes.get('query_started') |
| 140 | if (queryStartTime !== undefined) { |
| 141 | metadata.time_to_query_start_ms = Math.round(queryStartTime - turnStart) |
| 142 | } |
| 143 | |
| 144 | // Time to first response (first chunk from API) |
| 145 | const firstChunkTime = checkpointTimes.get('first_chunk') |
| 146 | if (firstChunkTime !== undefined) { |
| 147 | metadata.time_to_first_response_ms = Math.round(firstChunkTime - turnStart) |
| 148 | } |
| 149 | |
| 150 | // Query overhead (time between query start and API request sent) |
| 151 | const apiRequestTime = checkpointTimes.get('api_request_sent') |
| 152 | if (queryStartTime !== undefined && apiRequestTime !== undefined) { |
| 153 | metadata.query_overhead_ms = Math.round(apiRequestTime - queryStartTime) |
| 154 | } |
| 155 | |
| 156 | // Add checkpoint count for debugging |
| 157 | metadata.checkpoint_count = marks.length |
| 158 | |
| 159 | // Add entrypoint for segmentation (sdk-ts, sdk-py, sdk-cli, or undefined) |
| 160 | if (process.env.CLAUDE_CODE_ENTRYPOINT) { |
no test coverage detected