package server import ( "net/http" "strings" "time" "jarl/internal/auth" "jarl/internal/stats" ) // statsQuerier checks permission and that statistics are enabled. func (s *Server) statsQuerier(p *auth.Principal) (*stats.Querier, error) { if err := require(p, auth.Action{Resource: auth.ResStats}); err != nil { return nil, err } if s.statsQ == nil { return nil, &httpError{http.StatusNotFound, "stats_disabled", "query statistics are disabled; set stats.enabled: true in /etc/jarl/jarl.yaml and restart jarl (see docs/stats.md)"} } return s.statsQ, nil } func statsSince(r *http.Request) (time.Duration, error) { d, err := stats.ParseSince(r.URL.Query().Get("since")) if err != nil { return 0, badRequest("%v", err) } return d, nil } func (s *Server) statsStatus(w http.ResponseWriter, r *http.Request, p *auth.Principal) error { if err := require(p, auth.Action{Resource: auth.ResStats}); err != nil { return err } if s.cfg.Stats == nil { writeJSON(w, http.StatusOK, stats.Status{Enabled: false, Warnings: []string{}}) return nil } writeJSON(w, http.StatusOK, s.cfg.Stats.Status()) return nil } func (s *Server) statsSummary(w http.ResponseWriter, r *http.Request, p *auth.Principal) error { q, err := s.statsQuerier(p) if err != nil { return err } since, err := statsSince(r) if err != nil { return err } sum, err := q.Summary(r.Context(), since, queryInt(r, "top", 10)) if err != nil { return err } writeJSON(w, http.StatusOK, sum) return nil } func (s *Server) statsTop(w http.ResponseWriter, r *http.Request, p *auth.Principal) error { q, err := s.statsQuerier(p) if err != nil { return err } since, err := statsSince(r) if err != nil { return err } kind := r.URL.Query().Get("kind") if kind == "" { kind = stats.KindName } limit := min(max(queryInt(r, "limit", 20), 1), 10000) es, err := q.Top(r.Context(), kind, since, limit) if err != nil { return badRequest("%v", err) } writeJSON(w, http.StatusOK, es) return nil } func (s *Server) statsTimeline(w http.ResponseWriter, r *http.Request, p *auth.Principal) error { q, err := s.statsQuerier(p) if err != nil { return err } since, err := statsSince(r) if err != nil { return err } var keys []string for _, k := range r.URL.Query()["key"] { for _, part := range strings.Split(k, ",") { if part = strings.TrimSpace(part); part != "" { keys = append(keys, part) } } } if len(keys) == 0 { keys = []string{"queries"} } if len(keys) > 50 { return badRequest("at most 50 series") } tl, err := q.Timeline(r.Context(), since, r.URL.Query().Get("step"), keys) if err != nil { return badRequest("%v", err) } writeJSON(w, http.StatusOK, tl) return nil } func (s *Server) statsHeatmap(w http.ResponseWriter, r *http.Request, p *auth.Principal) error { q, err := s.statsQuerier(p) if err != nil { return err } since := 28 * 24 * time.Hour if r.URL.Query().Get("since") != "" { if since, err = statsSince(r); err != nil { return err } } hm, err := q.Heatmap(r.Context(), r.URL.Query().Get("key"), since) if err != nil { return err } writeJSON(w, http.StatusOK, hm) return nil } func (s *Server) statsBreakdown(w http.ResponseWriter, r *http.Request, p *auth.Principal) error { q, err := s.statsQuerier(p) if err != nil { return err } since, err := statsSince(r) if err != nil { return err } es, err := q.Breakdown(r.Context(), r.URL.Query().Get("group"), since) if err != nil { return badRequest("%v", err) } writeJSON(w, http.StatusOK, es) return nil }