package beads import ( "context" "fmt" "net/url" "testing" "sourcecraft.dev/bigbes/sr-ht-dolt/browse" ) // --- what these measure ------------------------------------------------------- // // The board and the detail pane are what a hosted Dolt database costs to look // at. There is no SQL engine behind either: a bare NBS store has no working // set, so every table is read whole and projected in process (see the package // doc), and that projection is the per-request work — the row reads themselves // are the store's, and are the same reads whatever the page does with them. // // So the benchmarks below drive Build through a session that hands the rows // over already read. That is deliberately the seam the projections were given: // what is timed is the bucketing, the dependency walk, the label join, the // filter options and the sorts, and nothing about how fast a disk is that day. // // The corpus is benchIssues issues with a dependency edge for most of them, // which is the size a real bd tracker on this instance reaches; Max is 2000, so // the fixture sits inside the cap and no clip path is being measured instead of // the projection. // benchIssues is how many issues the synthetic tracker holds. Chosen to be a // realistic large tracker while staying under Max (2000), so what is measured // is the projection and not the truncation branch. const benchIssues = 1200 // benchStatuses cycles the three status categories over the corpus so all four // lanes are populated and statusCategory is exercised on both its custom-status // hit and its heuristic fallback. var benchStatuses = []string{"open", "in_progress", "closed", "blocked"} // benchSession builds a fakeSession over a synthetic tracker: issues, the // dependency edges between them, labels, custom statuses, comments and events. // Every table the board and the detail pane read is present, so nothing is // measured through the optional-table degradation path. func benchSession() *fakeSession { issues := &browse.RowPage{ Columns: []string{ "id", "title", "status", "priority", "issue_type", "assignee", "created_at", "started_at", "updated_at", "closed_at", "close_reason", "description", "design", "acceptance_criteria", "notes", "created_by", "owner", "estimated_minutes", "external_ref", "spec_id", "is_blocked", }, } for i := 0; i < benchIssues; i++ { id := fmt.Sprintf("bench-%04d", i) status := benchStatuses[i%len(benchStatuses)] closedAt, closeReason := "", "" if status == "closed" { closedAt = fmt.Sprintf("2026-03-%02d", i%28+1) closeReason = "resolved in review" } issues.Rows = append(issues.Rows, []string{ id, fmt.Sprintf("Issue %d: the projection this benchmark measures", i), status, fmt.Sprint(i % 5), []string{"feature", "bug", "chore", "epic"}[i%4], fmt.Sprintf("dev%d", i%7), fmt.Sprintf("2026-01-%02d", i%28+1), fmt.Sprintf("2026-02-%02d", i%28+1), fmt.Sprintf("2026-02-%02d", i%28+1), closedAt, closeReason, "A description long enough that copying it is not free, written the " + "way an issue body is written and not as a placeholder token.", "The design note, likewise.", "Given a projection, when it runs, then it produces the same lanes.", "Notes.", fmt.Sprintf("dev%d", i%3), fmt.Sprintf("dev%d", i%3), "90", "", "", fmt.Sprint(i % 9 / 8), // roughly one issue in nine carries the flag }) } issues.Total = len(issues.Rows) // A chain of edges: every issue past the first depends on an earlier one, so // the transitive walk in the detail pane has a real tree to descend and the // blocked/blocking counts are non-trivial for most cards. deps := &browse.RowPage{Columns: []string{"id", "issue_id", "depends_on_issue_id", "type"}} for i := 1; i < benchIssues; i++ { deps.Rows = append(deps.Rows, []string{ fmt.Sprintf("d-%04d", i), fmt.Sprintf("bench-%04d", i), fmt.Sprintf("bench-%04d", i/2), // a binary tree, so depth is log2(n) []string{"blocks", "related", "parent-child"}[i%3], }) } deps.Total = len(deps.Rows) labels := &browse.RowPage{Columns: []string{"issue_id", "label"}} for i := 0; i < benchIssues; i++ { labels.Rows = append(labels.Rows, []string{fmt.Sprintf("bench-%04d", i), fmt.Sprintf("area-%d", i%11)}, []string{fmt.Sprintf("bench-%04d", i), fmt.Sprintf("release-%d", i%3)}, ) } labels.Total = len(labels.Rows) statuses := &browse.RowPage{ Columns: []string{"name", "category"}, Rows: [][]string{ {"open", "open"}, {"in_progress", "in_progress"}, {"closed", "closed"}, // "blocked" is deliberately absent, so a quarter of the corpus falls // through to the name heuristics — the branch a tracker with a status // bd does not know about actually takes. }, } statuses.Total = len(statuses.Rows) comments := &browse.RowPage{Columns: []string{"id", "issue_id", "author", "text", "created_at"}} events := &browse.RowPage{Columns: []string{ "id", "issue_id", "actor", "event_type", "old_value", "new_value", "comment", "created_at", }} for i := 0; i < benchIssues; i++ { id := fmt.Sprintf("bench-%04d", i) comments.Rows = append(comments.Rows, []string{ fmt.Sprintf("c-%04d", i), id, fmt.Sprintf("dev%d", i%7), "A comment on this issue, of the length a comment has.", fmt.Sprintf("2026-02-%02d", i%28+1), }) events.Rows = append(events.Rows, []string{ fmt.Sprintf("e-%04d", i), id, fmt.Sprintf("dev%d", i%7), "status_changed", "open", "in_progress", "", fmt.Sprintf("2026-02-%02d", i%28+1), }) } comments.Total = len(comments.Rows) events.Total = len(events.Rows) return &fakeSession{rowsByTable: map[string]*browse.RowPage{ "issues": issues, "dependencies": deps, "labels": labels, "custom_statuses": statuses, "comments": comments, "events": events, }} } // BenchmarkBoardBuild is the whole board: four lanes bucketed out of the issue // rows, the dependency edges aggregated into blocked/blocking counts, the label // join, the filter dropdown options and the per-lane sort. func BenchmarkBoardBuild(b *testing.B) { sess := benchSession() ctx := context.Background() b.ReportAllocs() for b.Loop() { data, err := Build(ctx, sess, "main", url.Values{}) if err != nil { b.Fatalf("build: %v", err) } // Asserted rather than assumed: a projection that silently produced an // empty board would otherwise be the fastest one here. if data.Total != benchIssues { b.Fatalf("board carried %d cards, want %d", data.Total, benchIssues) } } } // BenchmarkBoardBuildFiltered is the same board under the sticky filters, which // is the request a reader who narrowed the board sends: every row is still read // and still matched, and only the survivors are bucketed. func BenchmarkBoardBuildFiltered(b *testing.B) { sess := benchSession() ctx := context.Background() query := url.Values{ "q": {"projection"}, "type": {"bug"}, "label": {"area-3"}, "assignee": {"dev2"}, } b.ReportAllocs() for b.Loop() { data, err := Build(ctx, sess, "main", query) if err != nil { b.Fatalf("build: %v", err) } if data.Total == 0 { b.Fatal("the filtered board matched nothing; the filter is measuring an empty loop") } } } // BenchmarkDetailBuild is one issue's pane: the same four tables plus comments // and events, the transitive dependency walk in both directions, the raw-row // projection and the merged, time-ordered history. func BenchmarkDetailBuild(b *testing.B) { sess := benchSession() ctx := context.Background() // An issue deep in the chain, so the transitive walk has something to walk. query := url.Values{"issue": {fmt.Sprintf("bench-%04d", benchIssues-1)}} b.ReportAllocs() for b.Loop() { data, err := Build(ctx, sess, "main", query) if err != nil { b.Fatalf("build: %v", err) } if data.Issue == nil { b.Fatal("the detail pane resolved no issue") } } }