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_<NAME>_VER and ~/data/home/<repo>, 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 <p class="mem-raw">) 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")
}
}
}