mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
feat(scarfmon): instrument Nous model catalog + subscription path (beach-ball investigation)
User reported a remote-context beach-ball when opening the model picker with Nous as the active provider. Existing measure points showed loadProviders + loadModels at ~315ms each (fast). The beach-ball must be in the uninstrumented Nous-overlay branch the picker fires when nous is selected. Adds four measure points covering every blocking call in that path: - nous.subscription.loadState (interval, .diskIO) — auth.json read via NousSubscriptionService.loadState. Already known to do an SSH read; now precisely measurable. - nous.readCache (interval, .diskIO) — nous_models cache read, TWO sequential SSH ops (fileExists + readFile). - nous.bearerToken (interval, .diskIO) — auth.json read AGAIN inside fetchModels. **This is a duplicate read** — loadState already parsed the same file moments earlier. Comment-flagged as a caching candidate. - nous.fetchModels (interval, .transport) + .bytes (event) — HTTP GET against the Nous /v1/models endpoint with the body byte count attached. The most likely beach-ball culprit if the endpoint is slow or hung. After the next capture we'll know which of the four owns the user's wall-clock; if `nous.bearerToken` shows up alongside `nous.subscription.loadState` with similar duration, the duplicate read is also a real cost worth fixing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,7 @@ public struct NousModelCatalogService: Sendable {
|
|||||||
/// malformed cache → nil; the loader treats that as "no cache" and
|
/// malformed cache → nil; the loader treats that as "no cache" and
|
||||||
/// kicks off a fresh fetch.
|
/// kicks off a fresh fetch.
|
||||||
public func readCache() -> NousModelsCache? {
|
public func readCache() -> NousModelsCache? {
|
||||||
|
ScarfMon.measure(.diskIO, "nous.readCache") {
|
||||||
let transport = context.makeTransport()
|
let transport = context.makeTransport()
|
||||||
guard transport.fileExists(cachePath) else { return nil }
|
guard transport.fileExists(cachePath) else { return nil }
|
||||||
do {
|
do {
|
||||||
@@ -113,6 +114,7 @@ public struct NousModelCatalogService: Sendable {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func writeCache(_ cache: NousModelsCache) {
|
private func writeCache(_ cache: NousModelsCache) {
|
||||||
let transport = context.makeTransport()
|
let transport = context.makeTransport()
|
||||||
@@ -148,6 +150,12 @@ public struct NousModelCatalogService: Sendable {
|
|||||||
// The subscription service already checks for `present`; we
|
// The subscription service already checks for `present`; we
|
||||||
// re-read the raw token here because we need the actual string,
|
// re-read the raw token here because we need the actual string,
|
||||||
// not just a Bool. Mirrors the SubscriptionService parse path.
|
// not just a Bool. Mirrors the SubscriptionService parse path.
|
||||||
|
// ScarfMon: separate `nous.bearerToken` measure point because
|
||||||
|
// this is the second auth.json read of the picker's open
|
||||||
|
// sequence (subscriptionService.loadState() did the first).
|
||||||
|
// Together with `nous.subscription.loadState`, total two SSH
|
||||||
|
// round-trips of the same file — candidate for caching.
|
||||||
|
ScarfMon.measure(.diskIO, "nous.bearerToken") {
|
||||||
let transport = context.makeTransport()
|
let transport = context.makeTransport()
|
||||||
guard transport.fileExists(context.paths.authJSON) else { return nil }
|
guard transport.fileExists(context.paths.authJSON) else { return nil }
|
||||||
guard let data = try? transport.readFile(context.paths.authJSON) else { return nil }
|
guard let data = try? transport.readFile(context.paths.authJSON) else { return nil }
|
||||||
@@ -158,12 +166,14 @@ public struct NousModelCatalogService: Sendable {
|
|||||||
guard let token, !token.isEmpty else { return nil }
|
guard let token, !token.isEmpty else { return nil }
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Make the API call. Times out after `requestTimeout` so a hung
|
/// Make the API call. Times out after `requestTimeout` so a hung
|
||||||
/// network doesn't block the picker indefinitely. Returns the raw
|
/// network doesn't block the picker indefinitely. Returns the raw
|
||||||
/// `[NousModel]` on success, throws on any HTTP / decode error so
|
/// `[NousModel]` on success, throws on any HTTP / decode error so
|
||||||
/// the caller can log + fall back.
|
/// the caller can log + fall back.
|
||||||
public func fetchModels() async throws -> [NousModel] {
|
public func fetchModels() async throws -> [NousModel] {
|
||||||
|
try await ScarfMon.measureAsync(.transport, "nous.fetchModels") {
|
||||||
guard let token = bearerToken() else {
|
guard let token = bearerToken() else {
|
||||||
throw NousModelCatalogError.notAuthenticated
|
throw NousModelCatalogError.notAuthenticated
|
||||||
}
|
}
|
||||||
@@ -182,8 +192,10 @@ public struct NousModelCatalogService: Sendable {
|
|||||||
}
|
}
|
||||||
struct Envelope: Decodable { let data: [NousModel] }
|
struct Envelope: Decodable { let data: [NousModel] }
|
||||||
let envelope = try JSONDecoder().decode(Envelope.self, from: data)
|
let envelope = try JSONDecoder().decode(Envelope.self, from: data)
|
||||||
|
ScarfMon.event(.transport, "nous.fetchModels.bytes", count: envelope.data.count, bytes: data.count)
|
||||||
return envelope.data
|
return envelope.data
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Public entry
|
// MARK: - Public entry
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ struct NousSubscriptionService: Sendable {
|
|||||||
/// on any read or parse failure — callers treat "absent" and "can't
|
/// on any read or parse failure — callers treat "absent" and "can't
|
||||||
/// read" the same in UI (show a "not subscribed" CTA).
|
/// read" the same in UI (show a "not subscribed" CTA).
|
||||||
nonisolated func loadState() -> NousSubscriptionState {
|
nonisolated func loadState() -> NousSubscriptionState {
|
||||||
|
ScarfMon.measure(.diskIO, "nous.subscription.loadState") {
|
||||||
guard let data = try? transport.readFile(authJSONPath) else {
|
guard let data = try? transport.readFile(authJSONPath) else {
|
||||||
return .absent
|
return .absent
|
||||||
}
|
}
|
||||||
@@ -108,4 +109,5 @@ struct NousSubscriptionService: Sendable {
|
|||||||
updatedAt: updatedAt
|
updatedAt: updatedAt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user