package server import ( "fmt" "net/http" "sort" "strings" dto "github.com/prometheus/client_model/go" "github.com/uptrace/bunrouter" "github.com/Warky-Devs/vecna.git/pkg/metrics" ) // dashboardHandler returns an HTML metrics dashboard by gathering from the registry. func dashboardHandler(reg *metrics.Registry) bunrouter.HandlerFunc { return func(w http.ResponseWriter, req bunrouter.Request) error { families, err := reg.Prometheus().Gather() if err != nil { http.Error(w, "failed to gather metrics", http.StatusInternalServerError) return nil } // Sort families by name for deterministic output. sort.Slice(families, func(i, j int) bool { return families[i].GetName() < families[j].GetName() }) var b strings.Builder b.WriteString(` Vecna Metrics Dashboard

Vecna Metrics Dashboard

`) for _, fam := range families { name := fam.GetName() help := fam.GetHelp() mtype := fam.GetType() b.WriteString(`
`) fmt.Fprintf(&b, "

%s

", htmlEsc(name)) if help != "" { fmt.Fprintf(&b, `
%s
`, htmlEsc(help)) } switch mtype { case dto.MetricType_COUNTER: renderCounter(&b, fam.GetMetric()) case dto.MetricType_GAUGE: renderGauge(&b, fam.GetMetric()) case dto.MetricType_HISTOGRAM: renderHistogram(&b, fam.GetMetric()) default: renderGeneric(&b, fam.GetMetric()) } b.WriteString(`
`) } b.WriteString("") w.Header().Set("Content-Type", "text/html; charset=utf-8") _, err = fmt.Fprint(w, b.String()) return err } } func renderCounter(b *strings.Builder, ms []*dto.Metric) { b.WriteString("") if len(ms) > 0 && len(ms[0].GetLabel()) > 0 { for _, lp := range ms[0].GetLabel() { fmt.Fprintf(b, "", htmlEsc(lp.GetName())) } } b.WriteString("") for _, m := range ms { b.WriteString("") for _, lp := range m.GetLabel() { val := lp.GetValue() cls := "" if lp.GetName() == "status" { if strings.HasPrefix(val, "2") { cls = ` class="badge b-ok"` } else if strings.HasPrefix(val, "4") || strings.HasPrefix(val, "5") { cls = ` class="badge b-err"` } } if cls != "" { fmt.Fprintf(b, "", cls, htmlEsc(val)) } else { fmt.Fprintf(b, "", htmlEsc(val)) } } fmt.Fprintf(b, ``, m.GetCounter().GetValue()) b.WriteString("") } b.WriteString("
%scount
%s%s%.0f
") } func renderGauge(b *strings.Builder, ms []*dto.Metric) { b.WriteString("") if len(ms) > 0 && len(ms[0].GetLabel()) > 0 { for _, lp := range ms[0].GetLabel() { fmt.Fprintf(b, "", htmlEsc(lp.GetName())) } } b.WriteString("") for _, m := range ms { b.WriteString("") for _, lp := range m.GetLabel() { fmt.Fprintf(b, "", htmlEsc(lp.GetValue())) } fmt.Fprintf(b, ``, m.GetGauge().GetValue()) b.WriteString("") } b.WriteString("
%svalue
%s%.4g
") } func renderHistogram(b *strings.Builder, ms []*dto.Metric) { b.WriteString("") if len(ms) > 0 && len(ms[0].GetLabel()) > 0 { for _, lp := range ms[0].GetLabel() { fmt.Fprintf(b, "", htmlEsc(lp.GetName())) } } b.WriteString("") for _, m := range ms { b.WriteString("") for _, lp := range m.GetLabel() { fmt.Fprintf(b, "", htmlEsc(lp.GetValue())) } h := m.GetHistogram() count := h.GetSampleCount() p50 := histogramQuantile(h, 0.50) p95 := histogramQuantile(h, 0.95) p99 := histogramQuantile(h, 0.99) fmt.Fprintf(b, ``, count) fmt.Fprintf(b, ``, fmtSeconds(p50)) fmt.Fprintf(b, ``, fmtSeconds(p95)) fmt.Fprintf(b, ``, fmtSeconds(p99)) b.WriteString("") } b.WriteString("
%scountp50 (s)p95 (s)p99 (s)
%s%d%s%s%s
") } func renderGeneric(b *strings.Builder, ms []*dto.Metric) { fmt.Fprintf(b, `
%d series
`, len(ms)) } // histogramQuantile computes a linear-interpolated quantile from a cumulative histogram. func histogramQuantile(h *dto.Histogram, q float64) float64 { buckets := h.GetBucket() total := float64(h.GetSampleCount()) if total == 0 || len(buckets) == 0 { return 0 } target := q * total var prevCount float64 var prevBound float64 for _, b := range buckets { count := float64(b.GetCumulativeCount()) bound := b.GetUpperBound() if count >= target { if count == prevCount { return prevBound } // linear interpolation within bucket return prevBound + (bound-prevBound)*(target-prevCount)/(count-prevCount) } prevCount = count prevBound = bound } return prevBound } func fmtSeconds(s float64) string { if s == 0 { return "—" } if s < 0.001 { return fmt.Sprintf("%.3fms", s*1000) } return fmt.Sprintf("%.3fs", s) } func htmlEsc(s string) string { s = strings.ReplaceAll(s, "&", "&") s = strings.ReplaceAll(s, "<", "<") s = strings.ReplaceAll(s, ">", ">") return s }