mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
feat(scarfmon): chat + transport + sqlite measure points (Phase 2)
Wires ScarfMon measure points into the chat hot path on both targets, plus the underlying SSH transport and remote-SQLite backend. All callsites are surgical adds — no behavior change. Cost when ScarfMon is in `.signpostOnly` (default) is one os_signpost emit per call, elided by the runtime outside an Instruments session. In `.full` mode the same callsites also push samples into the in-memory ring buffer. Render counters (event): - mac.ChatView.body / ios.ChatView.body — full transcript pane re-evals - mac.RichMessageBubble.body / ios.MessageBubble.body — per-bubble re-evals Stream + session (event + interval): - mac.sendViaACP, mac.sendPrompt — user tap → first-byte - mac.acpEvent, mac.handleACPEvent — per-event delivery + handle cost - mac.startACPSession — session boot - ios.send, ios.startResuming — same shape on iOS - ios.acpEvent, ios.handleACPEvent — same per-event split on iOS Transport + SQLite (interval, with byte counts on rows): - ssh.streamScript (Citadel iOS) — SSH round-trip - ssh.run (SSHScriptRunner Mac) — SSH round-trip - sqlite.query, sqlite.queryBatch — Remote SQLite per-call - sqlite.query.rows — row count + stdout bytes per query Disk I/O (interval): - diskIO.loadConfig — config.yaml read + parse - diskIO.loadCronJobs — cron jobs.json decode Body counters use the `let _: Void = ScarfMon.event(...)` pattern at the top of `body` — works inside `@ViewBuilder` and fires on every re-eval, which is exactly the signal we want. To use: Mac: Settings → Advanced → Performance Diagnostics → Full iOS: Settings → Diagnostics → Performance → Full Both panels auto-aggregate by (category, name), surface top 20 by p95, and offer Copy as JSON for sharing in feedback threads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,12 @@ struct ChatView: View {
|
||||
)!
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// ScarfMon body-evaluation counter. Re-render churn during
|
||||
// streaming is one of the load-bearing perf signals; rendering
|
||||
// here costs ~one signpost emit + ring-buffer append (off the
|
||||
// hot path otherwise).
|
||||
let _: Void = ScarfMon.event(.chatRender, "ios.ChatView.body")
|
||||
return VStack(spacing: 0) {
|
||||
connectionBanner
|
||||
errorBanner
|
||||
projectContextBar
|
||||
@@ -1254,6 +1259,12 @@ final class ChatController {
|
||||
/// assistant reply streams back as ACP notifications handled by
|
||||
/// the event task.
|
||||
func send() async {
|
||||
await ScarfMon.measureAsync(.chatStream, "ios.send") {
|
||||
await _sendImpl()
|
||||
}
|
||||
}
|
||||
|
||||
private func _sendImpl() async {
|
||||
guard state == .ready, let client else { return }
|
||||
let text = draft.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
// v0.12+ allows image-only sends — vision models accept "describe
|
||||
@@ -1358,7 +1369,10 @@ final class ChatController {
|
||||
let stream = await client.events
|
||||
for await event in stream {
|
||||
guard !Task.isCancelled else { break }
|
||||
self?.vm.handleACPEvent(event)
|
||||
ScarfMon.event(.chatStream, "ios.acpEvent", count: 1)
|
||||
ScarfMon.measure(.chatStream, "ios.handleACPEvent") {
|
||||
self?.vm.handleACPEvent(event)
|
||||
}
|
||||
}
|
||||
// Stream ended — if we weren't explicitly cancelled the
|
||||
// channel died (EOF on stdin/out, write to dead pipe,
|
||||
@@ -1788,6 +1802,12 @@ final class ChatController {
|
||||
/// to `session/load` if the remote doesn't support `session/resume`
|
||||
/// (Hermes < 0.9.x).
|
||||
func startResuming(sessionID: String) async {
|
||||
await ScarfMon.measureAsync(.sessionLoad, "ios.startResuming") {
|
||||
await _startResumingImpl(sessionID: sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
private func _startResumingImpl(sessionID: String) async {
|
||||
guard await passModelPreflight(intent: .resume(sessionID: sessionID)) else { return }
|
||||
await stop()
|
||||
vm.reset()
|
||||
@@ -1952,6 +1972,11 @@ private struct MessageBubble: View, Equatable {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
// Per-bubble render counter. The streaming bubble
|
||||
// (`message.id == 0`) re-renders on every chunk; tracking the
|
||||
// count here is what tells us if a slow chat is bottlenecked
|
||||
// on body re-eval vs. event-loop delivery.
|
||||
let _: Void = ScarfMon.event(.chatRender, "ios.MessageBubble.body")
|
||||
if message.isToolResult {
|
||||
ToolResultRow(message: message)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user