~bigbes/sr-ht-spec

ref: 5bb0bb134d608263da3197df9fb4f1d8a3fe42db sr-ht-spec/doc/bench_test.go -rw-r--r-- 8.0 KiB
5bb0bb13 — Eugene Blikh graph: serve /query on the anonymous router with a bearer credential 2 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package doc

import (
	"crypto/sha1"
	"fmt"
	"strings"
	"testing"

	"github.com/go-git/go-git/v5/plumbing"

	"sourcecraft.dev/bigbes/sr-ht-spec/core"
	"sourcecraft.dev/bigbes/sr-ht-spec/gitx"
)

// --- what these measure --------------------------------------------------------
//
// Serving one revision of a space is two pieces of work over the whole corpus,
// and neither of them touches git: FromDocuments turns the blobs a caller
// already read into an Archive — headers parsed, ids contested, hierarchy
// linked, aliases and stems indexed — and LinkPass renders every document
// through the markdown renderer to fill in the link graph the backlinks,
// orphan and catalog views read.
//
// Both grow with the corpus rather than with the request, which is exactly why
// they are worth a number: a space that doubles in size doubles what every page
// of it costs, and nothing about a single document says so.
//
// The corpus below is the shape a specification space actually has: sections of
// documents with frontmatter ids, a parent hierarchy, aliases, wikilinks that
// resolve and wikilinks that do not, relative links, tables and fenced blocks.

// benchSections and benchPerSection give benchDocs documents in all — a space
// larger than any this instance holds today, so the number says what growth
// costs rather than what today costs.
const (
	benchSections   = 8
	benchPerSection = 25
	benchDocs       = benchSections * benchPerSection
)

var benchSpace = core.SpaceRef{Owner: "bigbes", Name: "rfcs"}

// benchRev is a plausible revision string; nothing here resolves it.
const benchRev = "3f786850e387550fdab836ed7e6dc881de23001b"

// benchBlob derives a stable, distinct blob hash per path. FromDocuments only
// carries it through to Page.Blob, but a shared zero hash across every document
// would be a corpus no git tree could produce.
func benchBlob(path string) plumbing.Hash {
	return plumbing.Hash(sha1.Sum([]byte(path)))
}

// benchDocuments builds the corpus: one index per section plus benchPerSection
// documents under it, each carrying a header, prose, links and a code block.
func benchDocuments() []gitx.Document {
	docs := make([]gitx.Document, 0, benchDocs+benchSections)

	add := func(path, body string) {
		docs = append(docs, gitx.Document{
			Path: path,
			Blob: benchBlob(path),
			Data: []byte(body),
		})
	}

	for s := 0; s < benchSections; s++ {
		section := fmt.Sprintf("section-%d", s)
		indexPath := section + "/index.md"
		add(indexPath, fmt.Sprintf(`---
id: SPEC-%04d
title: Section %d
status: approved
aliases:
  - sec-%d
---

# Section %d

The index of this section. It links every document under it, which is what
makes it a catalog and what keeps its own backlinks out of the orphan count.

%s
`, 9000+s, s, s, s, sectionIndexList(s)))

		for i := 0; i < benchPerSection; i++ {
			n := s*benchPerSection + i
			path := fmt.Sprintf("%s/doc-%03d.md", section, i)
			add(path, benchDocument(s, i, n))
		}
	}
	return docs
}

// sectionIndexList is the bullet list a section index carries: a wikilink per
// document under it.
func sectionIndexList(s int) string {
	var b strings.Builder
	for i := 0; i < benchPerSection; i++ {
		fmt.Fprintf(&b, "- [[doc-%03d]] — the %dth document of this section\n", i, i)
	}
	return b.String()
}

// benchDocument is one document of the corpus.
func benchDocument(section, i, n int) string {
	var b strings.Builder
	fmt.Fprintf(&b, `---
id: SPEC-%04d
title: Document %d of section %d
status: %s
parent: "[[index]]"
aliases:
  - d-%d-%d
tags:
  - benchmark
  - section-%d
---

# Document %d of section %d

`, n, i, section, []string{"draft", "review", "approved"}[n%3], section, i, section, i, section)

	b.WriteString(`This document is prose of the length a specification chapter has,
hard-wrapped the way one is written, so the renderer walks a paragraph
of several lines rather than a single long one.

`)

	// Links: two that resolve inside this section, one that resolves in another
	// section, one relative link, one external, and one that resolves to nothing
	// — which is the case the renderer marks rather than drops, and the case a
	// space accumulates as it grows.
	fmt.Fprintf(&b, "See [[doc-%03d]] and [[doc-%03d]] for the neighbouring rules, "+
		"and [[index]] for the section itself.\n\n",
		(i+1)%benchPerSection, (i+2)%benchPerSection)
	fmt.Fprintf(&b, "Across sections: [[../section-%d/doc-%03d]] and the alias [[d-%d-%d]].\n\n",
		(section+1)%benchSections, i, (section+1)%benchSections, i)
	fmt.Fprintf(&b, "A relative link to [the sibling](doc-%03d.md), an external one to\n"+
		"<https://spec.srht.bigb.es/>, and [[a-document-nobody-wrote-%d]] which\n"+
		"resolves to nothing at all.\n\n", (i+3)%benchPerSection, i)

	b.WriteString(`## Requirements

1. The reader must be able to address a revision by its hash.
2. The writer must not be able to rewrite the approved branch.
3. A proposal must name the revision it was cut from.

| field | required | note |
| ----- | -------- | ---- |
| id | yes | stable across revisions |
| title | yes | shown in every listing |
| status | no | draft when absent |

` + "```yaml" + `
id: SPEC-0000
title: the example
status: draft
` + "```" + `

> A block quote, because a specification always has one.
`)
	return b.String()
}

// benchBodies is the path → raw markdown map LinkPass reads, which is the map
// a caller already holds from the same read that produced the documents.
func benchBodies(docs []gitx.Document) map[string][]byte {
	bodies := make(map[string][]byte, len(docs))
	for _, d := range docs {
		bodies[d.Path] = d.Data
	}
	return bodies
}

// BenchmarkFromDocuments builds the archive: every header parsed, the id
// contest decided over the whole revision, the hierarchy linked, and the alias
// and stem indexes filled.
func BenchmarkFromDocuments(b *testing.B) {
	docs := benchDocuments()

	b.ReportAllocs()
	for b.Loop() {
		arc := FromDocuments(benchSpace, benchRev, docs)
		// A build that indexed nothing would be the fastest one here.
		if len(arc.All()) != len(docs) {
			b.Fatalf("archive holds %d pages, want %d", len(arc.All()), len(docs))
		}
	}
}

// BenchmarkLinkPass renders every document in the archive through the markdown
// renderer and resolves its links — the pass that fills the link graph the
// backlink and orphan views read.
func BenchmarkLinkPass(b *testing.B) {
	docs := benchDocuments()
	bodies := benchBodies(docs)
	r := NewRenderer()

	b.ReportAllocs()
	for b.Loop() {
		b.StopTimer()
		// A fresh archive per iteration: LinkPass writes Page.Links, so reusing
		// one would measure the second pass over an already-linked graph.
		arc := FromDocuments(benchSpace, benchRev, docs)
		b.StartTimer()

		if err := arc.LinkPass(r, bodies); err != nil {
			b.Fatalf("link pass: %v", err)
		}
		linked := 0
		for _, p := range arc.All() {
			linked += len(p.Links)
		}
		if linked == 0 {
			b.Fatal("the link pass resolved no outbound link at all")
		}
	}
}

// BenchmarkRenderDocument is one document rendered on its own, against the
// archive as resolver — the per-request half of the two passes above, and what
// a reader opening a single page pays.
func BenchmarkRenderDocument(b *testing.B) {
	docs := benchDocuments()
	arc := FromDocuments(benchSpace, benchRev, docs)
	r := NewRenderer()

	// Named rather than indexed: a document from the middle of the corpus, and
	// deliberately not a section index — an index carries only wikilinks that
	// resolve, so it would leave the missing-link path of the renderer unmeasured.
	want := fmt.Sprintf("section-%d/doc-%03d.md", benchSections/2, benchPerSection/2)
	var src gitx.Document
	for _, d := range docs {
		if d.Path == want {
			src = d
			break
		}
	}
	if src.Data == nil {
		b.Fatalf("no document at %s in the corpus", want)
	}
	_, body := ParseFront(src.Data)
	dir := DirOf(src.Path)

	b.ReportAllocs()
	b.SetBytes(int64(len(body)))
	for b.Loop() {
		res := r.Render(body, dir, arc)
		if len(res.LinkedIDs) == 0 {
			b.Fatal("the document resolved no outbound link; the resolver is not being exercised")
		}
		if len(res.MissingWikilinks) == 0 {
			b.Fatal("the deliberately unresolvable wikilink resolved; the fixture drifted")
		}
	}
}