Skip to content

Commit

Permalink
Record metrics per-site
Browse files Browse the repository at this point in the history
The performance per site is widly different, so record it per site so we
have a bit of a better idea.
  • Loading branch information
arp242 committed Oct 26, 2022
1 parent 01c84c3 commit 891520b
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 200 deletions.
4 changes: 2 additions & 2 deletions bgrun/bgrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
55 changes: 0 additions & 55 deletions bosmang.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ package goatcounter
import (
"context"
"fmt"
"strconv"
"strings"
"time"

"zgo.at/errors"
Expand Down Expand Up @@ -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
Expand Down
73 changes: 7 additions & 66 deletions handlers/bosmang.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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))
}

Expand Down Expand Up @@ -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})
}

Expand Down
2 changes: 2 additions & 0 deletions handlers/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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.
Expand Down
28 changes: 27 additions & 1 deletion metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package metrics

import (
"fmt"
"sort"
"sync"
"time"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
}
13 changes: 12 additions & 1 deletion public/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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')
})
}
})();
3 changes: 3 additions & 0 deletions tpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 5 additions & 15 deletions tpl/bosmang_cache.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<style>
td { vertical-align: top; }
.page { max-width: unset; }
.page >table { max-width: none; }
pre { max-height: 4em; margin: 0; border: none; }
pre:hover { max-height: none; }
Expand All @@ -11,26 +12,15 @@ Caches:
<ul>
{{range $k, $v := .Cache}}
<li>
<span style="display: inline-block; min-width: 9em;">{{$k}}</span> {{len $v.Items}} items · {{$v.Size}}K · <a href="#" class="show-cache">show</a>
<span style="display: inline-block; min-width: 9em;">{{$k}}</span>
<span style="display: inline-block; min-width: 7em;">{{len $v.Items}} items</span>
<span style="display: inline-block; min-width: 7em;">{{$v.Size}}K</span>
<a href="#" class="show-cache">show</a>
<table style="display: none;"><tbody>
{{range $k2, $v2 := $v.Items}}<tr><td><code>{{$k2}}</code> → </td><td><pre>{{$v2}}</pre></td></tr>{{end}}
</tbody></table>
</li>
{{end}}
</ul>

<script crossorigin="anonymous" src="{{.Static}}/jquery.js?v={{.Version}}"></script>
<script>
$('.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')
})
</script>

{{template "_backend_bottom.gohtml" .}}
15 changes: 12 additions & 3 deletions tpl/bosmang_metrics.gohtml
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
{{template "_backend_top.gohtml" .}}

<h1>Metrics</h1>
<p>Sort by:
<a {{if eq .By "sum"}}class="active"{{end}} href="?by=sum">Total</a> ·
<a {{if eq .By "mean"}}class="active"{{end}} href="?by=mean">Mean</a> ·
<a {{if eq .By "median"}}class="active"{{end}} href="?by=median">Median</a> ·
<a {{if eq .By "min"}}class="active"{{end}} href="?by=min">Min</a> ·
<a {{if eq .By "max"}}class="active"{{end}} href="?by=max">Max</a> ·
<a {{if eq .By "len"}}class="active"{{end}} href="?by=len">Num calls</a>
</p>

{{range $v := .Metrics}}
<pre>{{$v.Tag}} (over last {{$v.Times.Len}} invocations)
Total: {{$v.Times.Sum | round_duration}}
<div style="border-top: 1px solid #000; margin-top: 2em; padding-top: 2em;">
<strong>{{$v.Tag}}</strong> (over last {{$v.Times.Len}} invocations)</div>
<pre style="margin-top: 0; background-color: unset;">Total: {{$v.Times.Sum | round_duration}}
Min: {{$v.Times.Min | round_duration}}
Max: {{$v.Times.Max | round_duration}}
Median: {{$v.Times.Median | round_duration}}
Mean: {{$v.Times.Mean | round_duration}}
{{distribute_durations $v.Times 10}}</pre>
{{else}}
Nothing recorded yet.
<p>Nothing recorded yet.</p>
{{end}}

{{template "_backend_bottom.gohtml" .}}
Loading

0 comments on commit 891520b

Please sign in to comment.