diff --git a/bgrun/bgrun.go b/bgrun/bgrun.go index 73bf2d380..71227d15c 100644 --- a/bgrun/bgrun.go +++ b/bgrun/bgrun.go @@ -48,7 +48,7 @@ var ( } ) -const maxHist = 10_000 +const maxHist = 1_000 // Wait for all goroutines to finish for a maximum of maxWait. func Wait(ctx context.Context) error { @@ -229,7 +229,7 @@ func List() []Job { return l } -// History gets the last 10,000 jobs that ran. +// History gets the last 1,000 jobs that ran. func History() []Job { hist.Lock() defer hist.Unlock() diff --git a/bosmang.go b/bosmang.go index 6e4e3abec..cbdf4df27 100644 --- a/bosmang.go +++ b/bosmang.go @@ -7,8 +7,6 @@ package goatcounter import ( "context" "fmt" - "strconv" - "strings" "time" "zgo.at/errors" @@ -39,59 +37,6 @@ func (a *BosmangStats) List(ctx context.Context) error { return nil } -type BosmangSiteStat struct { - Account Site - Sites Sites - Users Users -} - -// ByID gets stats for a single site. -func (a *BosmangSiteStat) ByID(ctx context.Context, id int64) error { - var s Site - err := s.ByID(ctx, id) - if err != nil { - return errors.Wrap(err, "BosmangSiteStats.ByID") - } - - acc, err := GetAccount(WithSite(ctx, &s)) - if err != nil { - return errors.Wrap(err, "BosmangSiteStats.ByID") - } - a.Account = *acc - err = a.Sites.ForThisAccount(WithSite(ctx, &a.Account), false) - if err != nil { - return errors.Wrap(err, "BosmangSiteStats.ByID") - } - err = a.Users.List(ctx, id) - if err != nil { - return errors.Wrap(err, "BosmangSiteStats.ByID") - } - - return errors.Wrap(err, "BosmangSiteStats.ByID") -} - -// Find gets stats for a single site. -func (a *BosmangSiteStat) Find(ctx context.Context, ident string) error { - id, err := strconv.ParseInt(ident, 10, 64) - switch { - case id > 0: - // Do nothing - case strings.ContainsRune(ident, '@'): - var u User - err = zdb.Get(ctx, &u, `select * from users where lower(email) = lower(?)`, ident) - id = u.Site - default: - var s Site - err = s.ByCode(ctx, ident) - id = s.ID - } - if err != nil { - return err - } - - return a.ByID(ctx, id) -} - func ListCache(ctx context.Context) map[string]struct { Size int64 Items map[string]string diff --git a/handlers/bosmang.go b/handlers/bosmang.go index dc8b094ec..52971c654 100644 --- a/handlers/bosmang.go +++ b/handlers/bosmang.go @@ -6,12 +6,9 @@ package handlers import ( "net/http" - "sort" - "sync" "time" "github.com/go-chi/chi/v5" - "zgo.at/errors" "zgo.at/goatcounter/v2" "zgo.at/goatcounter/v2/bgrun" "zgo.at/goatcounter/v2/cron" @@ -44,7 +41,6 @@ func (h bosmang) mount(r chi.Router, db zdb.DB) { a.Handle("/bosmang/profile*", zprof.NewHandler(zprof.Prefix("/bosmang/profile"))) a.Get("/bosmang/sites", zhttp.Wrap(h.sites)) - a.Get("/bosmang/sites/{id}", zhttp.Wrap(h.site)) a.Post("/bosmang/sites/login/{id}", zhttp.Wrap(h.login)) } @@ -101,82 +97,27 @@ func (h bosmang) runTask(w http.ResponseWriter, r *http.Request) error { } func (h bosmang) metrics(w http.ResponseWriter, r *http.Request) error { + by := "sum" + if b := r.URL.Query().Get("by"); b != "" { + by = b + } return zhttp.Template(w, "bosmang_metrics.gohtml", struct { Globals Metrics metrics.Metrics - }{newGlobals(w, r), metrics.List()}) + By string + }{newGlobals(w, r), metrics.List().Sort(by), by}) } func (h bosmang) sites(w http.ResponseWriter, r *http.Request) error { - var ( - wg sync.WaitGroup - signups []goatcounter.HitListStat - maxSignups int - bgErr = errors.NewGroup(20) - ) - go func() { - var sites goatcounter.Sites - err := sites.UnscopedList(r.Context()) - if bgErr.Append(err) { - return - } - grouped := make(map[string]int) // day → count - cutoff := time.Now().Add(-365 * 24 * time.Hour) - for _, s := range sites { - if s.Parent != nil { - continue - } - if s.CreatedAt.Before(cutoff) { - continue - } - grouped[s.CreatedAt.Format("2006-01-02")]++ - } - - for k, v := range grouped { - if v > maxSignups { - maxSignups = v - } - signups = append(signups, goatcounter.HitListStat{ - Day: k, - Hourly: []int{v}, - HourlyUnique: []int{v}, - }) - } - sort.Slice(signups, func(i, j int) bool { return signups[i].Day < signups[j].Day }) - }() - var a goatcounter.BosmangStats err := a.List(r.Context()) if err != nil { return err } - wg.Wait() - if bgErr.Len() > 0 { - return bgErr - } - return zhttp.Template(w, "bosmang_sites.gohtml", struct { Globals - Stats goatcounter.BosmangStats - Signups []goatcounter.HitListStat - MaxSignups int - }{newGlobals(w, r), a, signups, maxSignups}) -} - -func (h bosmang) site(w http.ResponseWriter, r *http.Request) error { - var a goatcounter.BosmangSiteStat - err := a.Find(r.Context(), chi.URLParam(r, "id")) - if err != nil { - if zdb.ErrNoRows(err) { - return guru.New(404, "no such site") - } - return err - } - - return zhttp.Template(w, "bosmang_site.gohtml", struct { - Globals - Stat goatcounter.BosmangSiteStat + Stats goatcounter.BosmangStats }{newGlobals(w, r), a}) } diff --git a/handlers/dashboard.go b/handlers/dashboard.go index a88f1b89e..f7a404884 100644 --- a/handlers/dashboard.go +++ b/handlers/dashboard.go @@ -33,6 +33,7 @@ const DailyView = 90 func (h backend) dashboard(w http.ResponseWriter, r *http.Request) error { m := metrics.Start("dashboard") + m.AddTag(r.Host) defer m.Done() site := Site(r.Context()) @@ -143,6 +144,7 @@ func (h backend) dashboard(w http.ResponseWriter, r *http.Request) error { getData := func(w widgets.Widget) { m := metrics.Start("dashboard:" + w.Name()) + m.AddTag(r.Host) defer m.Done() // Create context for every goroutine, so we know which timed out. diff --git a/metrics/metrics.go b/metrics/metrics.go index d41e2c35c..07833900c 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -6,6 +6,7 @@ package metrics import ( + "fmt" "sort" "sync" "time" @@ -41,6 +42,31 @@ type Metrics []struct { Times ztime.Durations } +// Sort returns a copy sorted by the given metric. +func (m Metrics) Sort(metric string) Metrics { + var f func(i, j int) bool + switch metric { + case "sum", "total": + f = func(i, j int) bool { return m[i].Times.Sum() > m[j].Times.Sum() } + case "mean": + f = func(i, j int) bool { return m[i].Times.Mean() > m[j].Times.Mean() } + case "median": + f = func(i, j int) bool { return m[i].Times.Median() > m[j].Times.Median() } + case "min": + f = func(i, j int) bool { return m[i].Times.Min() > m[j].Times.Min() } + case "max": + f = func(i, j int) bool { return m[i].Times.Max() > m[j].Times.Max() } + case "len": + f = func(i, j int) bool { return m[i].Times.Len() > m[j].Times.Len() } + default: + panic(fmt.Sprintf("Metrics.Sort: unknown column: %q", metric)) + } + + sort.Slice(m, f) + return m +} + +// List metrics, sorted by name. func List() Metrics { collected.mu.Lock() defer collected.mu.Unlock() @@ -97,5 +123,5 @@ func (t *Metric) Done() { // This will record the cached entries as "hello.cached", separate from the // regular "hello" entries. func (t *Metric) AddTag(tag string) { - t.tag += "." + tag + t.tag += "·" + tag } diff --git a/public/backend.js b/public/backend.js index 3cc133342..82d9f0374 100644 --- a/public/backend.js +++ b/public/backend.js @@ -18,7 +18,7 @@ ;[report_errors, bind_tooltip, bind_confirm, translate_calendar].forEach((f) => f.call()) ;[page_dashboard, page_settings_main, page_user_pref, page_user_dashboard, page_bosmang] - .forEach((f) => document.body.id === f.name.replace(/_/g, '-') && f.call()) + .forEach((f) => document.body.id.match(new RegExp('^' + f.name.replace(/_/g, '-'))) && f.call()) }) // Set up error reporting. @@ -241,5 +241,16 @@ th.closest('table').find('th').attr('data-sort', '0') th.attr('data-sort', is_sort ? '0' : '1') }) + + $('.show-cache').on('click', function(e) { + e.preventDefault() + + let btn = $(this), + tbl = btn.closest('li').find('table'), + vis = tbl.css('display') !== 'none' + + btn.text(vis ? 'show' : 'hide') + tbl.css('display', vis ? 'none' : 'table') + }) } })(); diff --git a/tpl.go b/tpl.go index 8cea43bab..6571f56a2 100644 --- a/tpl.go +++ b/tpl.go @@ -86,6 +86,9 @@ func init() { format := fmt.Sprintf(" ≤ %%%ds ms → %%%dd %%s %%.1f%%%%\n", widthDur, widthNum) l := float64(times.Len()) for _, h := range dist { + if h.Len() == 0 { + continue + } r := int(widthBar / (l / float64(h.Len()))) perc := float64(h.Len()) / l * 100 fmt.Fprintf(b, format, p(h.Max()), h.Len(), strings.Repeat("▬", r), perc) diff --git a/tpl/bosmang_cache.gohtml b/tpl/bosmang_cache.gohtml index ffa1d4a96..883dccc0e 100644 --- a/tpl/bosmang_cache.gohtml +++ b/tpl/bosmang_cache.gohtml @@ -2,6 +2,7 @@ -
{{nformat $s.Total $.User}} | {{nformat $s.LastMonth $.User}} | {{nformat $s.Avg $.User}} | -{{$s.ID}} | +{{$s.ID}} | {{$s.Codes}} | {{tformat $s.CreatedAt "" $.User}} |