cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / ui / followstress_test.go
followstress_test.go 3.9K
1//go:build stress23package ui45import (6 "context"7 "fmt"8 "os"9 "path/filepath"10 "slices"11 "testing"12 "time"1314 "src.rest/cat/012/internal/fileio"15 "src.rest/cat/012/internal/live"16 "src.rest/cat/012/internal/sheet"17)1819// BenchmarkFollow follows a CSV log while another goroutine appends20// 10,000 rows a second to it for three seconds, polling as the UI does21// (every live.Interval, at once while more is waiting), applying each22// update to the workbook, with formulas over the region, and drawing a23// frame after each. It reports the slowest and the 99th percentile of24// an update applied (writing the cells and recalculating what reads25// them) and of a frame, and the rows a second that came in: an update26// must stay a small part of a frame's budget, however fast the file27// grows. The window cases keep the last rows, rewriting the region on28// every update; "all" keeps every row.29func BenchmarkFollow(b *testing.B) {30 for _, window := range []int{0, 1000, 10000} {31 name := "all"32 if window > 0 {33 name = fmt.Sprintf("window-%d", window)34 }35 b.Run(name, func(b *testing.B) {36 for b.Loop() {37 r := followRun(b, window)38 b.ReportMetric(r.applyMax, "apply-max-ms")39 b.ReportMetric(r.applyP99, "apply-p99-ms")40 b.ReportMetric(r.frameMax, "frame-max-ms")41 b.ReportMetric(r.frameP99, "frame-p99-ms")42 b.ReportMetric(r.rate, "rows/s")43 }44 })45 }46}4748type followResult struct{ applyMax, applyP99, frameMax, frameP99, rate float64 }4950const (51 followRate = 10000 // rows appended a second52 followSeconds = 353 followBatchGap = 10 * time.Millisecond54)5556func followRun(b *testing.B, window int) followResult {57 b.StopTimer()58 path := filepath.Join(b.TempDir(), "access.csv")59 os.WriteFile(path, []byte("time,level,path,ms\n"), 0o644)60 m := sized(sheet.New(), 200, 60)61 id, err := m.sheet.AddLinked(sheet.Addr{}, sheet.LinkSource{Path: "access.csv", Window: window})62 if err != nil {63 b.Fatal(err)64 }65 m.sheet.Set(sheet.Addr{Col: 6}, "=SUM(D:D)")66 m.sheet.Set(sheet.Addr{Col: 6, Row: 1}, `=COUNTIF(B:B,"error")`)67 m.sheet.Set(sheet.Addr{Col: 6, Row: 2}, "=AVERAGE(D2:D100000)")68 src := live.NewFile("access.csv", path, fileio.CSV, fileio.Options{})69 defer src.Close()70 t := newFakeTerm(200, 60)71 done := make(chan int)72 go appendLog(path, done)73 b.StartTimer()7475 var applies, frames []float6476 start, total := time.Now(), 077 writing := true78 for {79 select {80 case total = <-done:81 writing = false82 default:83 }84 u, ok := src.Poll(context.Background())85 if ok {86 at := time.Now()87 if err := m.book().ApplyLive(u.Op(id)); err != nil {88 b.Fatal(err)89 }90 applies = append(applies, ms(time.Since(at)))91 at = time.Now()92 t.frame(m)93 frames = append(frames, ms(time.Since(at)))94 }95 switch {96 case u.More:97 case writing:98 time.Sleep(live.Interval)99 default:100 if r, _ := m.book().LinkedRegion(id); r.Rows+r.Dropped < total {101 continue102 }103 return followResult{applyMax: slices.Max(applies), applyP99: p99(applies), frameMax: slices.Max(frames),104 frameP99: p99(frames), rate: float64(total) / time.Since(start).Seconds()}105 }106 }107}108109// appendLog appends followRate rows a second to path, in batches, for110// followSeconds, then sends how many it wrote.111func appendLog(path string, done chan<- int) {112 f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0)113 if err != nil {114 panic(err)115 }116 defer f.Close()117 levels := []string{"info", "info", "info", "warn", "error"}118 per := followRate * int(followBatchGap) / int(time.Second)119 n := 0120 for range followSeconds * int(time.Second/followBatchGap) {121 var batch []byte122 for range per {123 batch = fmt.Appendf(batch, "%d,%s,/api/%d,%d\n", n, levels[n%len(levels)], n%97, n%1300)124 n++125 }126 f.Write(batch)127 time.Sleep(followBatchGap)128 }129 done <- n130}131132func ms(d time.Duration) float64 { return float64(d) / float64(time.Millisecond) }133134func p99(xs []float64) float64 {135 s := slices.Clone(xs)136 slices.Sort(s)137 return s[min(len(s)-1, len(s)*99/100)]138}