summaryrefslogtreecommitdiff
path: root/vrsite/bench_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'vrsite/bench_test.go')
-rw-r--r--vrsite/bench_test.go129
1 files changed, 129 insertions, 0 deletions
diff --git a/vrsite/bench_test.go b/vrsite/bench_test.go
new file mode 100644
index 0000000..8950b8f
--- /dev/null
+++ b/vrsite/bench_test.go
@@ -0,0 +1,129 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "testing"
+)
+
+// A realistic Rust chunk (~1.2KB): doc comments, generics, lifetimes, attrs,
+// strings with escapes, macros — the constructs the highlighter works hardest
+// on. Repetition parses fine; tree-sitter is not a compiler.
+var rustChunk = []byte(`//! Module documentation with some text.
+use std::{collections::HashMap, sync::Arc};
+
+/// A cached entry with generics and lifetimes.
+#[derive(Debug, Clone, PartialEq)]
+pub struct CacheEntry<'a, T: Clone + Send> {
+ pub key: &'a str,
+ pub value: Arc<T>,
+ hits: u64,
+ tags: HashMap<String, Vec<u8>>,
+}
+
+pub enum LookupResult<T> {
+ Hit(Arc<T>),
+ Miss { reason: &'static str, code: i32 },
+ Tombstone,
+}
+
+impl<'a, T: Clone + Send> CacheEntry<'a, T> {
+ pub fn new(key: &'a str, value: T) -> Self {
+ let tags = HashMap::new();
+ Self { key, value: Arc::new(value), hits: 0, tags }
+ }
+
+ /// Record a hit and return the running total.
+ pub fn touch(&mut self) -> u64 {
+ self.hits = self.hits.saturating_add(1);
+ if self.hits % 100 == 0 {
+ println!("entry {} hit {} times \"escaped\"", self.key, self.hits);
+ }
+ self.hits
+ }
+
+ pub fn lookup(&self, keys: &[&str]) -> LookupResult<T> {
+ match keys.iter().position(|k| *k == self.key) {
+ Some(_) => LookupResult::Hit(self.value.clone()),
+ None => LookupResult::Miss { reason: "absent <key>", code: -1 },
+ }
+ }
+}
+
+const MAX_ENTRIES: usize = 4096;
+static GREETING: &str = "hello \"quoted\" <world> & friends";
+`)
+
+func synthRust(size int) []byte {
+ var b bytes.Buffer
+ for b.Len() < size {
+ b.Write(rustChunk)
+ }
+ return b.Bytes()
+}
+
+var sizes = []int{128 << 10, 1 << 20, 4 << 20}
+
+func benchHL(b *testing.B) *highlighter {
+ b.Helper()
+ return newHighlighter(loadTheme("", "", "dark"), loadTheme("", "", "light"))
+}
+
+// tree-sitter parse + query + paint only
+func BenchmarkClassify(b *testing.B) {
+ hl := benchHL(b)
+ for _, size := range sizes {
+ src := synthRust(size)
+ b.Run(fmt.Sprintf("%dKB", size>>10), func(b *testing.B) {
+ b.SetBytes(int64(len(src)))
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ hl.classify("bench.rs", src)
+ }
+ })
+ }
+}
+
+// classify + full per-line HTML emit
+func BenchmarkRender(b *testing.B) {
+ hl := benchHL(b)
+ for _, size := range sizes {
+ src := synthRust(size)
+ b.Run(fmt.Sprintf("%dKB", size>>10), func(b *testing.B) {
+ b.SetBytes(int64(len(src)))
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ f := &File{Path: "bench.rs", NoteAt: map[int]int{}}
+ f.render(src, hl)
+ }
+ })
+ }
+}
+
+// template execution for an already-rendered file page
+func BenchmarkExecute(b *testing.B) {
+ hl := benchHL(b)
+ tpl := newTemplates()
+ site := &Site{
+ Title: "bench", Files: map[string]*File{},
+ Theme: loadTheme("", "", "dark"), Light: loadTheme("", "", "light"),
+ Agents: []*Agent{{Name: "bench", Short: "bench", Bit: 1}},
+ }
+ for _, size := range sizes {
+ src := synthRust(size)
+ f := &File{Path: "bench.rs", NoteAt: map[int]int{}, Traced: true, Change: "zzzzzzzz"}
+ f.cover(1, bytes.Count(src, []byte("\n"))/2, 1)
+ f.render(src, hl)
+ p := &page{Site: site, Kind: "file", Title: "bench", Root: "", Current: f.Path, File: f}
+ b.Run(fmt.Sprintf("%dKB", size>>10), func(b *testing.B) {
+ b.SetBytes(int64(len(src)))
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ if err := tpl.Execute(io.Discard, p); err != nil {
+ b.Fatal(err)
+ }
+ }
+ })
+ }
+}