Documentation
¶
Index ¶
- Constants
- func DSN(path string) string
- func Init(cfg *config.Config) (*sql.DB, error)
- func NormalizeServiceName(serviceName string) string
- func ValidRateBucketMinutes(n int) bool
- type APIRequest
- type CalendarDay
- type CodexAPIRequest
- type CodexEvent
- type CodexTokenUpdate
- type DailyModelSummary
- type DailySummary
- type Dashboard
- type DurationStat
- type Event
- type HourlyModelSummary
- type IntradayModelSummary
- type RateBucket
- type Repository
- func (r *Repository) BackfillTTFTByRequestID(ctx context.Context, requestID string, ttftMs int64) (bool, error)
- func (r *Repository) BackfillTTFTNearest(ctx context.Context, sessionID, promptID, model string, spanEndUnix int64, ...) (bool, error)
- func (r *Repository) BackfillTTFTNearestLoose(ctx context.Context, sessionID, model string, spanEndUnix int64, ...) (bool, error)
- func (r *Repository) Cleanup(ctx context.Context, beforeUnix int64) (int64, error)
- func (r *Repository) CleanupLegacyCodexEventsBatch(ctx context.Context, limit int) (int64, error)
- func (r *Repository) CleanupRaw(ctx context.Context, beforeUnix int64) (int64, error)
- func (r *Repository) Close()
- func (r *Repository) CountDailyStatsByModel(ctx context.Context, from, to string, granularity string) (int64, error)
- func (r *Repository) CountRecentRequests(ctx context.Context, model, from, to string) (int64, error)
- func (r *Repository) CountSessionStats(ctx context.Context, from, to string) (int64, error)
- func (r *Repository) DB() *sql.DB
- func (r *Repository) EnqueuePendingTTFTSpan(ctx context.Context, requestID, sessionID, model string, spanEndUnix int64, ...) error
- func (r *Repository) GetCalendarDays(ctx context.Context, from, to string) ([]CalendarDay, error)
- func (r *Repository) GetCodexCalendarDays(ctx context.Context, from, to string) ([]CalendarDay, error)
- func (r *Repository) GetCodexDailyStatsByModel(ctx context.Context, from, to string, limit, offset int, granularity string) ([]DailyModelSummary, int64, error)
- func (r *Repository) GetCodexDashboard(ctx context.Context, from, to string) (*Dashboard, error)
- func (r *Repository) GetCodexDurationStatsByModel(ctx context.Context, model, from, to string) ([]DurationStat, error)
- func (r *Repository) GetCodexIntradayStatsByModel(ctx context.Context, fromYMD, toYMD string, bucketMinutes int, model string) ([]IntradayModelSummary, error)
- func (r *Repository) GetCodexRecentRequests(ctx context.Context, limit, offset int, model, from, to string) ([]CodexAPIRequest, int64, error)
- func (r *Repository) GetCodexSessionStats(ctx context.Context, from, to string, limit, offset int) ([]SessionStat, int64, error)
- func (r *Repository) GetDailyStats(ctx context.Context, from, to string) ([]DailySummary, error)
- func (r *Repository) GetDailyStatsByModel(ctx context.Context, from, to string, limit, offset int, granularity string) ([]DailyModelSummary, error)
- func (r *Repository) GetDashboard(ctx context.Context) (*Dashboard, error)
- func (r *Repository) GetDashboardForRange(ctx context.Context, from, to string) (*Dashboard, error)
- func (r *Repository) GetDistinctModels(ctx context.Context) ([]string, error)
- func (r *Repository) GetDurationStatsByModel(ctx context.Context, model, from, to string, limit int) ([]DurationStat, error)
- func (r *Repository) GetHourlyStatsByModel(ctx context.Context, date string, model string) ([]HourlyModelSummary, error)
- func (r *Repository) GetIntradayStatsByModel(ctx context.Context, fromYMD, toYMD string, bucketMinutes int, model string) ([]IntradayModelSummary, error)
- func (r *Repository) GetRateOverTime(ctx context.Context, fromYMD, toYMD string, bucketMinutes int, model string) ([]RateBucket, error)
- func (r *Repository) GetRecentRequests(ctx context.Context, limit, offset int, model, from, to string) ([]APIRequest, error)
- func (r *Repository) GetSessionRecentMinuteRate(ctx context.Context, sessionID string) (*SessionRateSnapshot, error)
- func (r *Repository) GetSessionStats(ctx context.Context, from, to string, limit, offset int) ([]SessionStat, error)
- func (r *Repository) InsertAPIError(ctx context.Context, e *Event) error
- func (r *Repository) InsertCodexAPIRequest(ctx context.Context, req *CodexAPIRequest) (int64, error)
- func (r *Repository) InsertCodexRawEvent(ctx context.Context, eventType string, timestampUnix int64, rawJSON string) error
- func (r *Repository) InsertCodexToolDecision(ctx context.Context, e *CodexEvent) error
- func (r *Repository) InsertCodexToolResult(ctx context.Context, e *CodexEvent) error
- func (r *Repository) InsertCodexUserPrompt(ctx context.Context, e *CodexEvent) error
- func (r *Repository) InsertEvent(ctx context.Context, e *Event) error
- func (r *Repository) InsertMetricPoint(ctx context.Context, timestampUnix int64, metricName string, value float64, ...) error
- func (r *Repository) InsertRawEvent(ctx context.Context, eventType string, timestamp int64, rawJSON string) error
- func (r *Repository) InsertRequest(ctx context.Context, req *APIRequest) (bool, error)
- func (r *Repository) InsertToolDecision(ctx context.Context, e *Event) error
- func (r *Repository) InsertToolResult(ctx context.Context, e *Event) error
- func (r *Repository) InsertUserPrompt(ctx context.Context, e *Event) error
- func (r *Repository) NeedsAggRebuild(ctx context.Context) (bool, error)
- func (r *Repository) Ping(ctx context.Context) error
- func (r *Repository) RebuildDailyAggregates(ctx context.Context) error
- func (r *Repository) UpdateCodexAPIRequestTokens(ctx context.Context, u *CodexTokenUpdate) (bool, error)
- func (r *Repository) UpdateCodexRequestDuration(ctx context.Context, requestRowID int64, sessionID, model string, ts time.Time, ...) (bool, error)
- func (r *Repository) UpdateCodexRequestTTFT(ctx context.Context, requestRowID int64, sessionID, model string, ts time.Time, ...) error
- type SessionRateSnapshot
- type SessionStat
Constants ¶
const DefaultServiceName = "claude-code"
DefaultServiceName is the telemetry client attributed to rows that arrive without an OTLP resource service.name. Historically every row in these tables came from Claude Code, so that stays the back-compatible default.
Variables ¶
This section is empty.
Functions ¶
func DSN ¶ added in v0.1.1
DSN builds the go-sqlite3 driver DSN for a database path.
With CC_OTEL_TEST_NOSYNC set (CI test matrices), synchronous=OFF is applied per connection via the driver's _pragma DSN parameter — test databases are throwaway temp files, and on CI Windows runners a single FlushFileBuffers can stall for minutes while Defender scans a fresh .db file, which blew go test's default 10m package timeout (v0.1.0 tag CI flake, 2026-08-16). The pragma must ride the DSN so it is in effect during Init's migrations, which run before Init's own PRAGMA statements.
Production never sets the env, so its DSN is the plain path unchanged. :memory: always passes through untouched — its lifetime is the connection, not the disk, and URI-form DSNs would break the single-shared-db assumption of in-memory tests.
func Init ¶
Init opens the SQLite database at cfg.DBPath, runs schema migrations, and enables WAL mode.
func NormalizeServiceName ¶
NormalizeServiceName folds a missing/blank service_name onto DefaultServiceName. Every write path (receiver, merge/import, aggregate rebuild) must agree on this, otherwise the same client shows up as both ” and 'claude-code'.
func ValidRateBucketMinutes ¶
ValidRateBucketMinutes reports whether n is an allowed rate-chart bucket size.
Types ¶
type APIRequest ¶
type APIRequest struct {
ID int64 `json:"id"`
Timestamp time.Time `json:"timestamp"`
SessionID string `json:"session_id"`
UserID string `json:"user_id"`
PromptID string `json:"prompt_id"`
PromptLength int64 `json:"prompt_length"`
Model string `json:"model"`
ActualModel string `json:"actual_model"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheCreationTokens int64 `json:"cache_creation_tokens"`
CostUSD float64 `json:"cost_usd"`
DurationMs int64 `json:"duration_ms"`
TTFTMs int64 `json:"ttft_ms"`
RequestID string `json:"request_id"`
EventName string `json:"event_name"`
EventSequence int64 `json:"event_sequence"`
Speed string `json:"speed"`
TerminalType string `json:"terminal_type"`
ToolName string `json:"tool_name"`
Decision string `json:"decision"`
Source string `json:"source"`
ServiceName string `json:"service_name"`
ServiceVersion string `json:"service_version"`
HostArch string `json:"host_arch"`
OSType string `json:"os_type"`
OSVersion string `json:"os_version"`
ErrorType string `json:"error_type"`
ErrorMessage string `json:"error_message"`
ErrorCode int64 `json:"error_code"`
ErrorRetryable int `json:"error_retryable"`
}
APIRequest represents a single Claude Code API request record stored in the database.
type CalendarDay ¶
type CalendarDay struct {
Date string `json:"date"`
TotalTokens int64 `json:"total_tokens"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheCreationTokens int64 `json:"cache_creation_tokens"`
CostUSD float64 `json:"cost_usd"`
RequestCount int64 `json:"request_count"`
TopModel string `json:"top_model"`
}
CalendarDay holds one local-day aggregate for the usage calendar heatmap.
type CodexAPIRequest ¶
type CodexAPIRequest struct {
ID int64 `json:"id"`
Timestamp time.Time `json:"timestamp"`
SessionID string `json:"session_id"`
UserID string `json:"user_id"`
ConversationID string `json:"conversation_id"`
Model string `json:"model"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheCreationTokens int64 `json:"cache_creation_tokens"`
ReasoningTokens int64 `json:"reasoning_tokens"`
TotalTokens int64 `json:"total_tokens"`
CostUSD float64 `json:"cost_usd"`
DurationMs int64 `json:"duration_ms"`
TTFTMs int64 `json:"ttft_ms"`
HTTPStatus int64 `json:"http_status"`
Endpoint string `json:"endpoint"`
EventName string `json:"event_name"`
EventSequence int64 `json:"event_sequence"`
TerminalType string `json:"terminal_type"`
ServiceName string `json:"service_name"`
ServiceVersion string `json:"service_version"`
HostArch string `json:"host_arch"`
OSType string `json:"os_type"`
OSVersion string `json:"os_version"`
ErrorMessage string `json:"error_message"`
}
CodexAPIRequest mirrors APIRequest but is sourced from Codex CLI telemetry. Token data arrives via codex.sse_event(kind=response.completed); network data arrives via codex.api_request. The two are correlated post-hoc by the repository's UpdateCodexAPIRequestTokens.
type CodexEvent ¶
type CodexEvent struct {
Timestamp time.Time
SessionID string
ConversationID string
EventName string // codex.user_prompt / codex.tool_decision / ...
EventKind string // for codex.sse_event sub-types
EventSequence int64
Model string
DurationMs int64
ErrorMessage string
// user_prompt
PromptText string
PromptLength int64
// tool_decision / tool_result
ToolName string
CallID string
Decision string
Source string
Success int
// tool_result
ArgumentsLength int64
OutputLength int64
ToolOrigin string
MCPServer string
TerminalType string
ServiceName string
ServiceVersion string
RawAttrsJSON string
}
CodexEvent is a parsed Codex log record. Carries fields used by all secondary inserts; unrelated fields are zero.
type CodexTokenUpdate ¶
type CodexTokenUpdate struct {
RequestRowID int64
SessionID string
Model string
Timestamp time.Time
InputTokens int64
OutputTokens int64
CacheReadTokens int64
CacheCreationTokens int64
ReasoningTokens int64
TotalTokens int64
CostUSD float64
DurationMs int64
TTFTMs int64
}
CodexTokenUpdate is the payload for UpdateCodexAPIRequestTokens.
CostUSD is computed by the receiver via the local pricing registry (Codex never reports cost_usd itself). Pass 0 to leave cost untouched on both the row and the daily aggregate.
type DailyModelSummary ¶
type DailyModelSummary struct {
Date string `json:"date"`
Model string `json:"model"`
CostUSD float64 `json:"cost_usd"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheCreationTokens int64 `json:"cache_creation_tokens"`
RequestCount int64 `json:"request_count"`
}
DailyModelSummary holds per-day, per-model aggregated token and cost statistics.
type DailySummary ¶
type DailySummary struct {
Date string `json:"date"`
TotalInputTokens int64 `json:"total_input_tokens"`
TotalOutputTokens int64 `json:"total_output_tokens"`
TotalCostUSD float64 `json:"total_cost_usd"`
RequestCount int64 `json:"request_count"`
CacheHitRate float64 `json:"cache_hit_rate"`
}
DailySummary holds per-day aggregated token and cost statistics.
type Dashboard ¶
type Dashboard struct {
TotalCostUSD float64 `json:"total_cost_usd"`
TotalInputTokens int64 `json:"total_input_tokens"` // SUM(input+cache_read+cache_creation) — input-side total (matches chart KPI Input)
TotalCacheReadTokens int64 `json:"total_cache_read_tokens"` // SUM(cache_read_tokens) — matches column "Cache Read"
TotalOutputTokens int64 `json:"total_output_tokens"`
CacheHitRate float64 `json:"cache_hit_rate"`
RequestCount int64 `json:"request_count"`
}
Dashboard holds aggregated token usage and cost statistics for a date range.
type DurationStat ¶
type DurationStat struct {
Model string `json:"model"`
RequestCount int64 `json:"request_count"`
AvgDurationMs float64 `json:"avg_duration_ms"`
AvgTTFTMs float64 `json:"avg_ttft_ms"`
AvgOutTokensPS float64 `json:"avg_out_tokens_per_s"`
AvgTotTokensPS float64 `json:"avg_total_tokens_per_s"`
WeightedOutTokensPS float64 `json:"weighted_out_tokens_per_s"`
WeightedTotTokensPS float64 `json:"weighted_total_tokens_per_s"`
MaxDurationMs int64 `json:"max_duration_ms"`
MinDurationMs int64 `json:"min_duration_ms"`
}
DurationStat holds per-model latency and throughput stats for a time range.
duration_ms is the end-to-end Claude API request latency reported by Claude Code: it covers server-side queueing + TTFT + streaming generation, but NOT local tool execution (Claude Code reports tool time on a separate tool_result event/table). So every tokens/s figure below inherently EXCLUDES local tool execution time.
Two throughput flavors are provided for the same output/total token counts:
- Avg*TokensPS = AVG(tokens*1000/duration_ms): arithmetic mean of each request's tok/s; every request weighted equally.
- Weighted*TokensPS = SUM(tokens)*1000/SUM(duration_ms): overall throughput, i.e. total tokens / total time (long/slow queued requests dominate).
type Event ¶
type Event struct {
ID int64 `json:"id"`
Timestamp time.Time `json:"timestamp"`
SessionID string `json:"session_id"`
UserID string `json:"user_id"`
PromptID string `json:"prompt_id"`
PromptText string `json:"prompt_text"`
PromptLength int64 `json:"prompt_length"`
EventName string `json:"event_name"` // user_prompt / api_request / tool_decision / tool_result
EventSequence int64 `json:"event_sequence"`
Model string `json:"model"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheCreationTokens int64 `json:"cache_creation_tokens"`
CostUSD float64 `json:"cost_usd"`
DurationMs int64 `json:"duration_ms"`
TTFTMs int64 `json:"ttft_ms"`
Speed string `json:"speed"` // normal / fast
TerminalType string `json:"terminal_type"`
ToolName string `json:"tool_name"` // Agent / Bash / Read / Write ...
Decision string `json:"decision"` // accept / deny
Source string `json:"source"` // config / user
DecisionSource string `json:"decision_source"` // tool_result (e.g. config)
DecisionType string `json:"decision_type"` // tool_result (e.g. accept)
Success int `json:"success"` // 1/0 for tool_result
ToolResultSizeBytes int64 `json:"tool_result_size_bytes"`
ErrorType string `json:"error_type"` // for api_error events
ErrorMessage string `json:"error_message"` // for api_error events
ErrorCode int64 `json:"error_code"` // HTTP status / error code
RequestID string `json:"request_id"`
ErrorRetryable int `json:"error_retryable"` // 1/0 for api_error
ServiceName string `json:"service_name"`
ServiceVersion string `json:"service_version"`
HostArch string `json:"host_arch"`
OSType string `json:"os_type"`
OSVersion string `json:"os_version"`
}
Event represents any OTEL event (user_prompt, api_request, tool_decision, tool_result). All unrecognized event names are stored here; known types go to dedicated tables.
type HourlyModelSummary ¶
type HourlyModelSummary struct {
Hour int `json:"hour"` // 0..23 in local time
Model string `json:"model"`
CostUSD float64 `json:"cost_usd"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheCreationTokens int64 `json:"cache_creation_tokens"`
RequestCount int64 `json:"request_count"`
}
HourlyModelSummary holds per-(hour, model) aggregated token and cost statistics for a single local day.
type IntradayModelSummary ¶
type IntradayModelSummary struct {
BucketStartUnix int64 `json:"bucket_start_unix"`
BucketLabel string `json:"bucket_label"`
BucketMinutes int `json:"bucket_minutes"`
Model string `json:"model"`
CostUSD float64 `json:"cost_usd"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheCreationTokens int64 `json:"cache_creation_tokens"`
RequestCount int64 `json:"request_count"`
}
IntradayModelSummary holds per-(time-bucket, model) aggregated stats for the intraday bar-chart view, supporting 5/10/15/30/60-minute buckets and a span of up to 7 days. BucketStartUnix is the inclusive start of the bucket (UTC seconds); BucketLabel is the same instant rendered in local time as "MM-DD HH:MM" and is what the frontend displays.
type RateBucket ¶
type RateBucket struct {
BucketStartUnix int64 `json:"bucket_start_unix"`
BucketLabel string `json:"bucket_label"`
BucketMinutes int `json:"bucket_minutes"`
Model string `json:"model"`
RequestCount int64 `json:"request_count"`
OutTokens int64 `json:"out_tokens"`
TotalTokens int64 `json:"total_tokens"`
DurationMsSum int64 `json:"duration_ms_sum"`
AvgOutPerS float64 `json:"avg_out_per_s"`
AvgTotalPerS float64 `json:"avg_total_per_s"`
WeightedOutPerS float64 `json:"weighted_out_per_s"`
WeightedTotalPerS float64 `json:"weighted_total_per_s"`
}
RateBucket holds per-(time-bucket, model) token throughput for the rate-over-time line chart. Only requests with duration_ms > 0 are counted, matching the durations table. duration_ms is the Claude API request latency (server queue + TTFT + streaming); local tool execution is a separate event, so it is excluded from every rate here.
Two flavors are provided for each of output/total tokens:
- Avg*PerS = AVG(tokens*1000/duration_ms) within the bucket: mean of each request's tok/s, every request weighted equally.
- Weighted*PerS = SUM(tokens)*1000/SUM(duration_ms): the bucket's overall throughput.
type Repository ¶
type Repository struct {
// contains filtered or unexported fields
}
Repository provides data access methods for the SQLite database.
func NewRepository ¶
func NewRepository(db *sql.DB) *Repository
NewRepository returns a Repository backed by the given database connection. Prepared statements are created for the hot-path InsertRequest to avoid repeated SQL parsing on every call.
func (*Repository) BackfillTTFTByRequestID ¶
func (r *Repository) BackfillTTFTByRequestID(ctx context.Context, requestID string, ttftMs int64) (bool, error)
BackfillTTFTByRequestID updates api_requests.ttft_ms by the exact upstream request id.
func (*Repository) BackfillTTFTNearest ¶
func (r *Repository) BackfillTTFTNearest(ctx context.Context, sessionID, promptID, model string, spanEndUnix int64, ttftMs int64) (bool, error)
BackfillTTFTNearest best-effort updates api_requests.ttft_ms for the request that most likely corresponds to the given trace span.
Strategy: match on (session_id, prompt_id, model) and choose the row whose timestamp is closest to spanEndUnix (seconds). Only updates when ttft_ms is currently 0 and ttftMs > 0.
func (*Repository) BackfillTTFTNearestLoose ¶
func (r *Repository) BackfillTTFTNearestLoose(ctx context.Context, sessionID, model string, spanEndUnix int64, windowSec int64, ttftMs int64) (bool, error)
BackfillTTFTNearestLoose is a fallback when trace spans don't include prompt_id. It matches on (session_id, model) within a small time window around spanEndUnix (seconds), then picks the closest request. Only updates when ttft_ms is currently 0.
func (*Repository) Cleanup ¶
Cleanup deletes records older than beforeUnix from all event tables and daily_model_agg.
func (*Repository) CleanupLegacyCodexEventsBatch ¶
CleanupLegacyCodexEventsBatch deletes at most limit compatibility events.
func (*Repository) CleanupRaw ¶
CleanupRaw deletes records older than beforeUnix from raw event tables only. These tables store the full OTLP JSON and grow quickly; they are safe to prune aggressively since the structured tables already hold the parsed data.
func (*Repository) Close ¶
func (r *Repository) Close()
Close releases prepared statements held by the repository.
func (*Repository) CountDailyStatsByModel ¶
func (r *Repository) CountDailyStatsByModel(ctx context.Context, from, to string, granularity string) (int64, error)
CountDailyStatsByModel returns the number of distinct (date, model) groups.
func (*Repository) CountRecentRequests ¶
func (r *Repository) CountRecentRequests(ctx context.Context, model, from, to string) (int64, error)
CountRecentRequests returns the total number of API request records matching the filters.
func (*Repository) CountSessionStats ¶
CountSessionStats returns the number of distinct sessions in the date range.
func (*Repository) DB ¶
func (r *Repository) DB() *sql.DB
DB returns the underlying *sql.DB for ad-hoc queries (e.g. tests, custom admin endpoints). Production code should prefer the typed methods.
func (*Repository) EnqueuePendingTTFTSpan ¶
func (*Repository) GetCalendarDays ¶
func (r *Repository) GetCalendarDays(ctx context.Context, from, to string) ([]CalendarDay, error)
GetCalendarDays returns compact per-day aggregates for the dashboard usage calendar.
func (*Repository) GetCodexCalendarDays ¶
func (r *Repository) GetCodexCalendarDays(ctx context.Context, from, to string) ([]CalendarDay, error)
GetCodexCalendarDays returns compact per-day aggregates for the Codex usage calendar.
func (*Repository) GetCodexDailyStatsByModel ¶
func (r *Repository) GetCodexDailyStatsByModel(ctx context.Context, from, to string, limit, offset int, granularity string) ([]DailyModelSummary, int64, error)
GetCodexDailyStatsByModel returns per-(date, model) rollup rows with total count. Reads codex_api_requests directly. codex_daily_model_agg is now kept in sync by the write paths but the read side hasn't switched to it yet — doing so would require a startup rebuild step like RebuildDailyAggregates.
func (*Repository) GetCodexDashboard ¶
GetCodexDashboard mirrors GetDashboardForRange but reads from codex_api_requests. Codex input_tokens already includes cached input; cache_read_tokens is a subset.
func (*Repository) GetCodexDurationStatsByModel ¶
func (r *Repository) GetCodexDurationStatsByModel(ctx context.Context, model, from, to string) ([]DurationStat, error)
GetCodexDurationStatsByModel returns per-model latency stats. Codex does not emit token-throughput, so AvgOutTokensPS/AvgTotTokensPS stay at 0.
func (*Repository) GetCodexIntradayStatsByModel ¶
func (r *Repository) GetCodexIntradayStatsByModel(ctx context.Context, fromYMD, toYMD string, bucketMinutes int, model string) ([]IntradayModelSummary, error)
GetCodexIntradayStatsByModel returns per-(bucket, model) Codex stats. Codex input_tokens already includes cached input; cache_read_tokens is a subset.
func (*Repository) GetCodexRecentRequests ¶
func (r *Repository) GetCodexRecentRequests(ctx context.Context, limit, offset int, model, from, to string) ([]CodexAPIRequest, int64, error)
GetCodexRecentRequests returns paginated CodexAPIRequest rows in the date range, optionally filtered by model.
func (*Repository) GetCodexSessionStats ¶
func (r *Repository) GetCodexSessionStats(ctx context.Context, from, to string, limit, offset int) ([]SessionStat, int64, error)
GetCodexSessionStats reuses the existing SessionStat type. StartTime maps to MIN(timestamp); InputTokens / OutputTokens / CostUSD aggregate per session.
func (*Repository) GetDailyStats ¶
func (r *Repository) GetDailyStats(ctx context.Context, from, to string) ([]DailySummary, error)
GetDailyStats returns per-day aggregated token and cost statistics.
func (*Repository) GetDailyStatsByModel ¶
func (r *Repository) GetDailyStatsByModel(ctx context.Context, from, to string, limit, offset int, granularity string) ([]DailyModelSummary, error)
GetDailyStatsByModel returns per-(date, model) token and cost breakdown with pagination.
func (*Repository) GetDashboard ¶
func (r *Repository) GetDashboard(ctx context.Context) (*Dashboard, error)
GetDashboard returns today's aggregated KPIs.
func (*Repository) GetDashboardForRange ¶
GetDashboardForRange returns aggregated KPIs (cost, tokens, cache hit, requests) for a date range.
func (*Repository) GetDistinctModels ¶
func (r *Repository) GetDistinctModels(ctx context.Context) ([]string, error)
GetDistinctModels returns all unique model names in the database.
func (*Repository) GetDurationStatsByModel ¶
func (r *Repository) GetDurationStatsByModel(ctx context.Context, model, from, to string, limit int) ([]DurationStat, error)
GetDurationStatsByModel returns per-model duration stats for API requests in the given date range. If model is non-empty, results are restricted to that model.
func (*Repository) GetHourlyStatsByModel ¶
func (r *Repository) GetHourlyStatsByModel(ctx context.Context, date string, model string) ([]HourlyModelSummary, error)
GetHourlyStatsByModel returns per-(local-hour, model) aggregated stats for a single local day. date must be YYYY-MM-DD (local time). Optional model filter narrows rows for the given model.
func (*Repository) GetIntradayStatsByModel ¶
func (r *Repository) GetIntradayStatsByModel(ctx context.Context, fromYMD, toYMD string, bucketMinutes int, model string) ([]IntradayModelSummary, error)
GetIntradayStatsByModel returns per-(bucket, model) stats for [fromYMD, toYMD] (inclusive local days), using a fixed bucket size in minutes (5, 10, 15, 30, or 60). Buckets are computed by flooring api_requests.timestamp to the nearest bucket boundary in UTC seconds; for whole-hour-offset timezones (the common case) this aligns to local clock buckets exactly. The label is rendered in the server's time.Local zone so the frontend gets a display-ready string.
func (*Repository) GetRateOverTime ¶
func (r *Repository) GetRateOverTime(ctx context.Context, fromYMD, toYMD string, bucketMinutes int, model string) ([]RateBucket, error)
GetRateOverTime returns per-(bucket, model) token throughput for [fromYMD, toYMD] (inclusive local days) with a fixed bucket size in minutes (5, 10, 15, 30, or 60). Only requests with duration_ms > 0 participate. Empty buckets are omitted. If model is non-empty, results are restricted to that model.
func (*Repository) GetRecentRequests ¶
func (r *Repository) GetRecentRequests(ctx context.Context, limit, offset int, model, from, to string) ([]APIRequest, error)
GetRecentRequests returns individual API request records with optional model and date filters.
func (*Repository) GetSessionRecentMinuteRate ¶
func (r *Repository) GetSessionRecentMinuteRate(ctx context.Context, sessionID string) (*SessionRateSnapshot, error)
GetSessionRecentMinuteRate returns throughput for the latest 1-minute bucket in which sessionID had at least one request with duration_ms > 0. Returns (nil, nil) when the session has no qualifying activity.
func (*Repository) GetSessionStats ¶
func (r *Repository) GetSessionStats(ctx context.Context, from, to string, limit, offset int) ([]SessionStat, error)
GetSessionStats returns per-session aggregated stats ordered by cost descending.
func (*Repository) InsertAPIError ¶
func (r *Repository) InsertAPIError(ctx context.Context, e *Event) error
InsertAPIError stores an api_error event into the dedicated table.
func (*Repository) InsertCodexAPIRequest ¶
func (r *Repository) InsertCodexAPIRequest(ctx context.Context, req *CodexAPIRequest) (int64, error)
InsertCodexAPIRequest inserts a single codex_api_requests row and upserts the matching codex_daily_model_agg row in the same transaction. Returns the new row's id.
Token columns may be zero (when called for codex.api_request) or non-zero (fallback path from UpdateCodexAPIRequestTokens). Either way request_count is incremented by 1 — the SSE-completed update path uses a separate token-only UPSERT so a logical request is counted exactly once.
func (*Repository) InsertCodexRawEvent ¶
func (r *Repository) InsertCodexRawEvent(ctx context.Context, eventType string, timestampUnix int64, rawJSON string) error
InsertCodexRawEvent inserts the raw OTLP payload into codex_raw_otlp_events.
func (*Repository) InsertCodexToolDecision ¶
func (r *Repository) InsertCodexToolDecision(ctx context.Context, e *CodexEvent) error
InsertCodexToolDecision inserts a row into codex_tool_decision_events.
func (*Repository) InsertCodexToolResult ¶
func (r *Repository) InsertCodexToolResult(ctx context.Context, e *CodexEvent) error
InsertCodexToolResult inserts a row into codex_tool_result_events.
func (*Repository) InsertCodexUserPrompt ¶
func (r *Repository) InsertCodexUserPrompt(ctx context.Context, e *CodexEvent) error
InsertCodexUserPrompt inserts a row into codex_user_prompt_events.
func (*Repository) InsertEvent ¶
func (r *Repository) InsertEvent(ctx context.Context, e *Event) error
InsertEvent stores any OTEL event into the events table.
func (*Repository) InsertMetricPoint ¶
func (r *Repository) InsertMetricPoint(ctx context.Context, timestampUnix int64, metricName string, value float64, sessionID, userID, terminalType, model, attrType string) error
InsertMetricPoint stores one OTLP sum metric data point (all Claude Code metrics use sum).
func (*Repository) InsertRawEvent ¶
func (r *Repository) InsertRawEvent(ctx context.Context, eventType string, timestamp int64, rawJSON string) error
InsertRawEvent stores the complete original OTEL event as JSON for future re-processing.
func (*Repository) InsertRequest ¶
func (r *Repository) InsertRequest(ctx context.Context, req *APIRequest) (bool, error)
InsertRequest stores a single API request record, ignoring duplicates by request_id. It also upserts the daily_model_agg pre-aggregation table within the same transaction. Returns true if a new row was inserted (false for duplicates). InsertRequest persists a request and its daily aggregate in one transaction, retrying on SQLITE_BUSY(_SNAPSHOT).
The tx is deferred and the insert reads before it writes (request_id dedup), so when a concurrent writer — an online import merging into the live DB — commits between this tx's snapshot and its write, SQLite returns BUSY immediately: busy_timeout never applies to that snapshot upgrade. A fresh transaction re-snapshots, and the request_id dedup makes the whole insert idempotent, so retrying is safe. The race window was effectively unreachable until CC_OTEL_TEST_NOSYNC sped test timing up (2026-08-17).
func (*Repository) InsertToolDecision ¶
func (r *Repository) InsertToolDecision(ctx context.Context, e *Event) error
InsertToolDecision stores a tool_decision event into the dedicated table.
func (*Repository) InsertToolResult ¶
func (r *Repository) InsertToolResult(ctx context.Context, e *Event) error
InsertToolResult stores a tool_result event into the dedicated table.
func (*Repository) InsertUserPrompt ¶
func (r *Repository) InsertUserPrompt(ctx context.Context, e *Event) error
InsertUserPrompt stores a user_prompt event into the dedicated table.
func (*Repository) NeedsAggRebuild ¶
func (r *Repository) NeedsAggRebuild(ctx context.Context) (bool, error)
NeedsAggRebuild returns true when daily_model_agg is empty but api_requests has data.
func (*Repository) Ping ¶
func (r *Repository) Ping(ctx context.Context) error
Ping verifies the database connection is alive.
func (*Repository) RebuildDailyAggregates ¶
func (r *Repository) RebuildDailyAggregates(ctx context.Context) error
RebuildDailyAggregates drops and rebuilds daily_model_agg from api_requests.
func (*Repository) UpdateCodexAPIRequestTokens ¶
func (r *Repository) UpdateCodexAPIRequestTokens(ctx context.Context, u *CodexTokenUpdate) (bool, error)
UpdateCodexAPIRequestTokens finalizes a Codex request row with completion accounting. When u.RequestRowID > 0 it targets that exact row first (and only that row), otherwise it falls back to the newest zero-token row matching (session_id, model) within the last 5 minutes. The matched row is updated with token columns, cost, full duration, and direct TTFT, and the same deltas are added to codex_daily_model_agg (date keyed off the *row's* timestamp so midnight drift lands on the correct day, request count unchanged).
An exact row that already carries token data is treated as already-finalized: the transaction commits as a no-op and returns true without re-applying deltas. If no pending row exists at all, a token-only fallback row is inserted via InsertCodexAPIRequest (which bumps request_count) and the method returns false.
Returns true for an exact/fallback UPDATE or an idempotent exact-row no-op, and false for the fallback INSERT. Either value lets the receiver drop the matched in-memory tracker state.
func (*Repository) UpdateCodexRequestDuration ¶
func (r *Repository) UpdateCodexRequestDuration( ctx context.Context, requestRowID int64, sessionID, model string, ts time.Time, durationMs int64, ) (bool, error)
UpdateCodexRequestDuration sets duration_ms for a Codex request row. When requestRowID > 0 it targets that exact (id, session_id, model) row first, replacing duration only when it is still zero; a positive existing duration is treated as authoritative and left untouched. If no exact row matches it falls back to the newest zero-duration row for (session_id, model) within 10 minutes of ts. Returns true when an exact identity was handled (updated or already authoritative) or a fallback row was updated; false only for a genuine no-match, which lets the caller decide to insert a duration-only row.
func (*Repository) UpdateCodexRequestTTFT ¶
func (r *Repository) UpdateCodexRequestTTFT( ctx context.Context, requestRowID int64, sessionID, model string, ts time.Time, ttftMs int64, ) error
UpdateCodexRequestTTFT sets ttft_ms for a Codex request row. When requestRowID > 0 it targets that exact (id, session_id, model) row first, filling TTFT only when it is zero/null; a positive existing TTFT is authoritative and left untouched. Otherwise it falls back to the newest zero/null-TTFT row for (session_id, model) within 10 minutes of ts.
type SessionRateSnapshot ¶
type SessionRateSnapshot struct {
SessionID string `json:"session_id"`
BucketStartUnix int64 `json:"bucket_start_unix"`
BucketLabel string `json:"bucket_label"`
BucketMinutes int `json:"bucket_minutes"`
LastActiveUnix int64 `json:"last_active_unix"`
RequestCount int64 `json:"request_count"`
OutTokens int64 `json:"out_tokens"`
TotalTokens int64 `json:"total_tokens"`
DurationMsSum int64 `json:"duration_ms_sum"`
AvgOutPerS float64 `json:"avg_out_per_s"`
AvgTotalPerS float64 `json:"avg_total_per_s"`
WeightedOutPerS float64 `json:"weighted_out_per_s"`
WeightedTotalPerS float64 `json:"weighted_total_per_s"`
}
SessionRateSnapshot is the token throughput for a session's most recent 1-minute window that had API activity (duration_ms > 0). Rates exclude local tool time, matching GetRateOverTime / GetDurationStatsByModel semantics.