package web import ( "context" "fmt" "strings" "testing" "time" "sourcecraft.dev/bigbes/sr-ht-dolt/beads" "sourcecraft.dev/bigbes/sr-ht-dolt/browse" ) // --- what this measures -------------------------------------------------------- // // The memory pane renders every memory body it shows through memoryLinks.Body: // goldmark parses the markdown, the wikilink parser resolves [[slug]] against // the cross-database index, and the AST transformer scans every text node for // id-shaped tokens and links the ones whose prefix the index knows. That is the // per-memory cost of the page, and a memory list renders many of them per // request. // // The index is built once, outside the timed loop, and built through the real // beads.PrefixesAcross over a fake session rather than hand-assembled: what the // render costs depends on how many prefixes and slugs the index holds, and the // only honest source of an index of that shape is the function that builds one. // benchMemoryDatabases is how many sibling trackers the caller may browse. Each // contributes one issue prefix and a handful of memory slugs, so the scan has // real prefixes to hit and real ones to miss. const benchMemoryDatabases = 6 // benchMemorySlugs is how many memories each of those trackers stores. const benchMemorySlugs = 12 // benchSession is a beads.ReadySession over one canned config table. Only // Branches and Rows are reached by PrefixesAcross; Tables and Log are here to // satisfy the interface and would be a bug to call. type benchSession struct { config *browse.RowPage } func (s *benchSession) Rows(_ context.Context, _, table string, _, _ int) (*browse.RowPage, error) { if table != "config" { return nil, fmt.Errorf("%w: %s", browse.ErrTableNotFound, table) } return s.config, nil } func (s *benchSession) Branches(context.Context) ([]browse.Branch, error) { return []browse.Branch{{Name: "main", Head: "0123456789abcdef"}}, nil } func (s *benchSession) Tables(context.Context, string) ([]browse.TableInfo, error) { return nil, fmt.Errorf("web: Tables is not part of the prefix index read") } func (s *benchSession) Log(context.Context, string, string, int) ([]browse.CommitInfo, string, error) { return nil, "", fmt.Errorf("web: Log is not part of the prefix index read") } func (s *benchSession) Close() error { return nil } // benchPrefixIndex builds the cross-database link index the render resolves // against: benchMemoryDatabases trackers, each naming its own issue prefix and // storing benchMemorySlugs memories. func benchPrefixIndex() *beads.PrefixIndex { dbs := make([]beads.ReadyDatabase, 0, benchMemoryDatabases) configs := make(map[int]*browse.RowPage, benchMemoryDatabases) for i := 0; i < benchMemoryDatabases; i++ { dbs = append(dbs, beads.ReadyDatabase{ ID: i + 1, OwnerName: "bigbes", Name: fmt.Sprintf("sr-ht-%d", i), }) rows := [][]string{{"issue_prefix", fmt.Sprintf("sr-ht-%d", i)}} for j := 0; j < benchMemorySlugs; j++ { rows = append(rows, []string{ fmt.Sprintf("kv.memory.memory-%d-%d", i, j), "stored elsewhere; the index only needs the key", }) } page := &browse.RowPage{Columns: []string{"key", "value"}, Rows: rows} page.Total = len(rows) configs[i+1] = page } open := func(_ context.Context, d beads.ReadyDatabase) (beads.ReadySession, error) { page, ok := configs[d.ID] if !ok { return nil, fmt.Errorf("web: no config for database %s", d.Slug()) } return &benchSession{config: page}, nil } return beads.PrefixesAcross(context.Background(), dbs, open, &beads.PrefixCache{}, time.Now()) } // benchMemoryBody is one memory of the shape the corpus actually holds: a bold // leader, code spans, a bullet list, a fenced recipe, a table, prose carrying // both resolvable and unresolvable ids, wikilinks that hit and wikilinks that // miss, and the angle-bracket placeholders the raw-HTML-as-text rendering // exists for. var benchMemoryBody = strings.Repeat(`**Why:** the pipeline builds tags too, and a tag build reports the tag. - see [[memory-0-3]] for the deploy order, and [[memory-4-7]] for the token - sr-ht-0-46c.2 blocks sr-ht-3-9a1, which is what sr-ht-5-nex was filed against - an id nothing owns, other-repo-12b, stays text `+"```"+`sh ref="${GIT_REF#refs/heads/}" curl -sS --fail-with-body -X POST "$ORIGIN/api/v1/repos/$REPO/reports" `+"```"+` | step | what it does | | ---- | ------------ | | push | `+"`labng push`"+` is the deploy | | wait | the apk index catches up within 15 minutes | The placeholder spellings are SRHT__VER and ~/data/home/, which CommonMark reads as tags and this renderer shows as the text they are. See [[memory-2-11]] and https://dolt.srht.bigb.es/~bigbes/sr-ht-2 for the rest. `, 8) // BenchmarkMemoryBodyRender is one memory body rendered the way the pane // renders it: parsed, its wikilinks resolved against the index, its ids scanned // and linked, and the whole document written out as HTML. func BenchmarkMemoryBodyRender(b *testing.B) { links := &memoryLinks{index: benchPrefixIndex()} b.ReportAllocs() b.SetBytes(int64(len(benchMemoryBody))) for b.Loop() { html := string(links.Body(benchMemoryBody)) // A render that resolved nothing would be the fastest one here, and the // fallback path (an escaped

) is a whole document too. if !strings.Contains(html, `href="/~bigbes/sr-ht-0/view/memory?key=memory-0-3"`) { b.Fatal("the wikilink did not resolve; this is measuring the wrong render") } } } // BenchmarkMemoryBodyRenderNoIndex is the same body with no index at all — // what a pane gets when the database listing failed. Every reference is left as // text, so the difference between the two is what resolution costs. func BenchmarkMemoryBodyRenderNoIndex(b *testing.B) { var links *memoryLinks b.ReportAllocs() b.SetBytes(int64(len(benchMemoryBody))) for b.Loop() { html := string(links.Body(benchMemoryBody)) if strings.Contains(html, "/view/memory?key=") { b.Fatal("an index-less render resolved a memory reference") } } }