package stats import ( "encoding/json" "expvar" "fmt" "net/http" "sort" "strings" "html/template" ) var ( page = template.Must(template.New(""). Funcs(template.FuncMap{"path": path, "duration": duration}). Parse(` Metrics report

    __          __
.--------..-----.|  |_ .----.|__|.----..-----.
|        ||  -__||   _||   _||  ||  __||__ --|
|__|__|__||_____||____||__|  |__||____||_____|


{{ range . }}

{{ .name }}

{{ if .type }}
{{ template "table" . }}
{{ else if .interval }}
{{ template "timeseries" . }}
{{ else if .metrics}} {{ range .metrics }}
{{ template "timeseries" . }}
{{ end }} {{ end }}
{{ end }}
{{ define "table" }} {{ if eq .type "c" }} {{ else if eq .type "g" }} {{ else if eq .type "h" }} {{ end }}
count
{{ printf "%.2g" .count }}
meanminmax
{{printf "%.2g" .mean}}{{printf "%.2g" .min}}{{printf "%.2g" .max}}
P.50P.90P.99
{{printf "%.2g" .p50}}{{printf "%.2g" .p90}}{{printf "%.2g" .p99}}
{{ end }} {{ define "timeseries" }} {{ template "table" .total }}
{{ duration .samples .interval }}
{{ if eq (index (index .samples 0) "type") "c" }} {{ range (path .samples "count") }}{{end}} {{ else if eq (index (index .samples 0) "type") "g" }} {{ range (path .samples "min" "max" "mean" ) }}{{end}} {{ else if eq (index (index .samples 0) "type") "h" }} {{ range (path .samples "p50" "p90" "p99") }}{{end}} {{ end }}
{{ end }} `)) ) func path(samples []any, keys ...string) []string { var min, max float64 paths := make([]string, len(keys)) for i := range len(samples) { s := samples[i].(map[string]any) for _, k := range keys { x := s[k].(float64) if i == 0 || x < min { min = x } if i == 0 || x > max { max = x } } } for i := range len(samples) { s := samples[i].(map[string]any) for j, k := range keys { v := s[k].(float64) x := float64(i+1) / float64(len(samples)) y := (v - min) / (max - min) if max == min { y = 0 } if i == 0 { paths[j] = fmt.Sprintf("M%f %f", 0.0, (1-y)*18+1) } paths[j] += fmt.Sprintf(" L%f %f", x*100, (1-y)*18+1) } } return paths } func duration(samples []any, n float64) string { n = n * float64(len(samples)) if n < 60 { return fmt.Sprintf("%d sec", int(n)) } else if n < 60*60 { return fmt.Sprintf("%d min", int(n/60)) } else if n < 24*60*60 { return fmt.Sprintf("%d hrs", int(n/60/60)) } return fmt.Sprintf("%d days", int(n/24/60/60)) } // Handler returns an http.Handler that renders web UI for all provided metrics. func Handler(snapshot func() map[string]Metric) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { type h map[string]any metrics := []h{} for name, metric := range snapshot() { m := h{} b, _ := json.Marshal(metric) json.Unmarshal(b, &m) m["name"] = name metrics = append(metrics, m) } sort.Slice(metrics, func(i, j int) bool { n1 := metrics[i]["name"].(string) n2 := metrics[j]["name"].(string) return strings.Compare(n1, n2) < 0 }) page.Execute(w, metrics) }) } // JSONHandler returns a [http.Handler] that renders the metrics as JSON. func JSONHandler(snapshot func() map[string]Metric) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { type h map[string]any metrics := map[string]h{} for name, metric := range snapshot() { m := h{} b, _ := json.Marshal(metric) json.Unmarshal(b, &m) metrics[name] = m } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(metrics) }) } // Exposed returns a map of exposed metrics (see expvar package). func Exposed() map[string]Metric { m := map[string]Metric{} expvar.Do(func(kv expvar.KeyValue) { if metric, ok := kv.Value.(Metric); ok { m[kv.Key] = metric } }) return m }