mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 12:14:40 +00:00
Co-authored-by: Bruno Clermont <bruno.clermont@gmail.com>
149 lines
4.4 KiB
Go
149 lines
4.4 KiB
Go
package tracing
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func TestUsageCatchUpStartHourRefreshesLatestClosedBucket(t *testing.T) {
|
|
target := time.Date(2026, 7, 3, 1, 0, 0, 0, time.UTC)
|
|
latest := target
|
|
|
|
got := usageCatchUpStartHour(&latest, target)
|
|
want := target.Add(-recentUsageRefreshWindow)
|
|
if !got.Equal(want) {
|
|
t.Fatalf("start hour = %s, want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestUsageCatchUpStartHourContinuesLargeBacklog(t *testing.T) {
|
|
target := time.Date(2026, 7, 3, 6, 0, 0, 0, time.UTC)
|
|
latest := target.Add(-12 * time.Hour)
|
|
|
|
got := usageCatchUpStartHour(&latest, target)
|
|
want := latest.Add(time.Hour)
|
|
if !got.Equal(want) {
|
|
t.Fatalf("start hour = %s, want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestUsageCatchUpStartHourWithoutSnapshotsComputesPreviousHourOnly(t *testing.T) {
|
|
target := time.Date(2026, 7, 3, 1, 0, 0, 0, time.UTC)
|
|
|
|
got := usageCatchUpStartHour(nil, target)
|
|
if !got.Equal(target) {
|
|
t.Fatalf("start hour = %s, want %s", got, target)
|
|
}
|
|
}
|
|
|
|
// TestMergeTraceAndSpanRowsDedupesToolCallProviderModelRow guards against
|
|
// SQLSTATE 21000 ("ON CONFLICT DO UPDATE command cannot affect row a second
|
|
// time"): a span row that resolves to Provider=="" && Model=="" (e.g. from
|
|
// tool_call spans, which never carry provider/model) must be merged into the
|
|
// existing totals row for that (agent_id, channel) instead of appended as a
|
|
// second row sharing the same upsert conflict-target key.
|
|
func TestMergeTraceAndSpanRowsDedupesToolCallProviderModelRow(t *testing.T) {
|
|
bucketStart := time.Date(2026, 7, 3, 1, 0, 0, 0, time.UTC)
|
|
agentID := uuid.New()
|
|
|
|
traceRows := []traceAggregate{
|
|
{
|
|
AgentID: &agentID,
|
|
Channel: "web",
|
|
RequestCount: 10,
|
|
ErrorCount: 1,
|
|
UniqueUsers: 3,
|
|
InputTokens: 100,
|
|
OutputTokens: 200,
|
|
TotalCost: 1.5,
|
|
ToolCallCount: 4,
|
|
AvgDurationMS: 500,
|
|
},
|
|
}
|
|
|
|
spanRows := []spanAggregate{
|
|
// Simulates leftover/edge-case span row with empty provider/model
|
|
// (e.g. tool_call-only spans) that would otherwise collide with the
|
|
// totals row's conflict-target key.
|
|
{
|
|
AgentID: &agentID,
|
|
Channel: "web",
|
|
Provider: "",
|
|
Model: "",
|
|
LLMCallCount: 2,
|
|
InputTokens: 10,
|
|
OutputTokens: 20,
|
|
TotalCost: 0.1,
|
|
CacheReadTokens: 1,
|
|
CacheCreateTokens: 2,
|
|
ThinkingTokens: 3,
|
|
},
|
|
{
|
|
AgentID: &agentID,
|
|
Channel: "web",
|
|
Provider: "anthropic",
|
|
Model: "claude-sonnet-4",
|
|
LLMCallCount: 5,
|
|
InputTokens: 50,
|
|
OutputTokens: 60,
|
|
TotalCost: 0.9,
|
|
CacheReadTokens: 4,
|
|
CacheCreateTokens: 5,
|
|
ThinkingTokens: 6,
|
|
},
|
|
}
|
|
|
|
snapshots := mergeTraceAndSpanRows(bucketStart, traceRows, spanRows, nil, nil)
|
|
|
|
var totalsRows []int
|
|
for i, snap := range snapshots {
|
|
if snap.AgentID != nil && *snap.AgentID == agentID && snap.Channel == "web" &&
|
|
snap.Provider == "" && snap.Model == "" {
|
|
totalsRows = append(totalsRows, i)
|
|
}
|
|
}
|
|
|
|
if len(totalsRows) != 1 {
|
|
t.Fatalf("expected exactly 1 totals row with Provider==\"\" && Model==\"\" for (agentID, web), got %d", len(totalsRows))
|
|
}
|
|
|
|
totals := snapshots[totalsRows[0]]
|
|
if want := 2; totals.LLMCallCount != want {
|
|
t.Fatalf("LLMCallCount = %d, want %d", totals.LLMCallCount, want)
|
|
}
|
|
if want := int64(10); totals.InputTokens != want {
|
|
t.Fatalf("InputTokens = %d, want %d", totals.InputTokens, want)
|
|
}
|
|
if want := int64(20); totals.OutputTokens != want {
|
|
t.Fatalf("OutputTokens = %d, want %d", totals.OutputTokens, want)
|
|
}
|
|
if want := 0.1; totals.TotalCost != want {
|
|
t.Fatalf("TotalCost = %v, want %v", totals.TotalCost, want)
|
|
}
|
|
if want := int64(1); totals.CacheReadTokens != want {
|
|
t.Fatalf("CacheReadTokens = %d, want %d", totals.CacheReadTokens, want)
|
|
}
|
|
if want := int64(2); totals.CacheCreateTokens != want {
|
|
t.Fatalf("CacheCreateTokens = %d, want %d", totals.CacheCreateTokens, want)
|
|
}
|
|
if want := int64(3); totals.ThinkingTokens != want {
|
|
t.Fatalf("ThinkingTokens = %d, want %d", totals.ThinkingTokens, want)
|
|
}
|
|
|
|
// The distinct provider/model detail row must still exist separately.
|
|
var detailFound bool
|
|
for _, snap := range snapshots {
|
|
if snap.Provider == "anthropic" && snap.Model == "claude-sonnet-4" {
|
|
detailFound = true
|
|
if snap.LLMCallCount != 5 {
|
|
t.Fatalf("detail row LLMCallCount = %d, want 5", snap.LLMCallCount)
|
|
}
|
|
}
|
|
}
|
|
if !detailFound {
|
|
t.Fatal("expected detail row for provider=anthropic model=claude-sonnet-4")
|
|
}
|
|
}
|