db

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
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

func DSN(path string) string

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

func Init(cfg *config.Config) (*sql.DB, error)

Init opens the SQLite database at cfg.DBPath, runs schema migrations, and enables WAL mode.

func NormalizeServiceName

func NormalizeServiceName(serviceName string) string

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

func ValidRateBucketMinutes(n int) bool

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

func (r *Repository) Cleanup(ctx context.Context, beforeUnix int64) (int64, error)

Cleanup deletes records older than beforeUnix from all event tables and daily_model_agg.

func (*Repository) CleanupLegacyCodexEventsBatch

func (r *Repository) CleanupLegacyCodexEventsBatch(ctx context.Context, limit int) (int64, error)

CleanupLegacyCodexEventsBatch deletes at most limit compatibility events.

func (*Repository) CleanupRaw

func (r *Repository) CleanupRaw(ctx context.Context, beforeUnix int64) (int64, error)

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

func (r *Repository) CountSessionStats(ctx context.Context, from, to string) (int64, error)

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 (r *Repository) EnqueuePendingTTFTSpan(ctx context.Context, requestID, sessionID, model string, spanEndUnix int64, ttftMs int64, rawJSON string) error

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

func (r *Repository) GetCodexDashboard(ctx context.Context, from, to string) (*Dashboard, error)

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

func (r *Repository) GetDashboardForRange(ctx context.Context, from, to string) (*Dashboard, error)

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.

type SessionStat

type SessionStat struct {
	SessionID    string    `json:"session_id"`
	UserID       string    `json:"user_id"`
	StartTime    time.Time `json:"start_time"`
	RequestCount int64     `json:"request_count"`
	InputTokens  int64     `json:"input_tokens"`
	OutputTokens int64     `json:"output_tokens"`
	CostUSD      float64   `json:"cost_usd"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL