package main import ( "encoding/json" "fmt" "log" "math" "os" "os/exec" "path/filepath" "sort" "strings" "time" "codeberg.org/elisiei/sysbench/internal/hyperfine" ) const ( svgW = 900 padTop = 48 padBottom = 80 padLeft = 200 padRight = 60 barHeight = 28 barGap = 14 tickCount = 6 cornerR = 0 ) var palette = []string{ "#5E81AC", "#88C0D0", "#A3BE8C", "#EBCB8B", "#D08770", "#BF616A", "#B48EAD", "#8FBCBB", } func colour(i int) string { return palette[i%len(palette)] } func main() { dir := resolveDir() src := filepath.Join(dir, "results.json") dst := filepath.Join(dir, "plot.svg") data, err := os.ReadFile(src) if err != nil { log.Fatalf("read %s: %v", src, err) } var result hyperfine.Result if err := json.Unmarshal(data, &result); err != nil { log.Fatalf("parse: %v", err) } if len(result.Results) == 0 { log.Fatal("no benchmark results found") } svg := render(result.Results, dir) if err := os.WriteFile(dst, []byte(svg), 0o644); err != nil { log.Fatalf("write %s: %v", dst, err) } fmt.Printf("plot written to %s\n", dst) pngPath := filepath.Join(dir, "plot.png") cmd := exec.Command("magick", "-density", "192", "-background", "none", dst, pngPath, ) if err := cmd.Run(); err != nil { log.Fatalf("convert to png: %v", err) } fmt.Printf("png written to %s\n", pngPath) } func resolveDir() string { if len(os.Args) > 1 { return os.Args[1] } return filepath.Join("results", time.Now().Format("2006-01-02")) } func render(bs []hyperfine.Benchmark, date string) string { sort.Slice(bs, func(i, j int) bool { return bs[i].Mean < bs[j].Mean }) n := len(bs) chartH := n*(barHeight+barGap) + barGap height := padTop + chartH + padBottom maxMean := 0.0 for _, b := range bs { if b.Mean > maxMean { maxMean = b.Mean } } scale := niceMax(maxMean, tickCount) chartW := svgW - padLeft - padRight var sb strings.Builder fmt.Fprintf(&sb, ``, svgW, height) fmt.Fprintln(&sb) fmt.Fprintf(&sb, ` `, svgW, height) fmt.Fprintln(&sb) title := fmt.Sprintf("system fetch benchmark — %s", filepath.Base(date)) fmt.Fprintf(&sb, ` %s`, svgW/2, title) fmt.Fprintln(&sb) for i := 0; i <= tickCount; i++ { val := scale * float64(i) / float64(tickCount) x := padLeft + int(float64(chartW)*val/scale) yTop := padTop yBot := padTop + chartH fmt.Fprintf(&sb, ` `, x, yTop, x, yBot) fmt.Fprintln(&sb) label := fmtTime(val) fmt.Fprintf(&sb, ` %s`, x, padTop+chartH+18, label) fmt.Fprintln(&sb) } fmt.Fprintf(&sb, ` time (s)`, padLeft+chartW/2, padTop+chartH+38) fmt.Fprintln(&sb) for i, b := range bs { y := padTop + barGap + i*(barHeight+barGap) barW := int(float64(chartW) * b.Mean / scale) if barW < 1 { barW = 1 } col := colour(i) label := trimCommand(b.Command, 28) fmt.Fprintf(&sb, ` %s`, padLeft-8, y+barHeight-8, label) fmt.Fprintln(&sb) fmt.Fprintf(&sb, ` `, padLeft, y, barW, barHeight, cornerR, col) fmt.Fprintln(&sb) fmt.Fprintf(&sb, ` %s`, padLeft+barW+8+(int(float64(chartW)*b.Stddev/scale)), y+barHeight-8, fmtTime(b.Mean)) fmt.Fprintln(&sb) } fmt.Fprintln(&sb, ``) return sb.String() } func niceMax(max float64, ticks int) float64 { raw := max / float64(ticks) exp := math.Floor(math.Log10(raw)) mag := math.Pow(10, exp) nice := math.Ceil(raw/mag) * mag return nice * float64(ticks) } func fmtTime(s float64) string { switch { case s == 0: return "0" case s < 0.001: return fmt.Sprintf("%.0fµs", s*1e6) case s < 1: return fmt.Sprintf("%.0fms", s*1e3) default: return fmt.Sprintf("%.2fs", s) } } func trimCommand(cmd string, max int) string { parts := strings.Fields(cmd) base := parts[0] if idx := strings.LastIndexAny(base, "/\\"); idx >= 0 { base = base[idx+1:] } if len(cmd) <= max { return cmd } if len(base) <= max { return base } return base[:max-1] + "..." }