cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Time recalculations and pivot refreshes in their workbook's trace

Dami committed 503499f
A workbook holds its owner's trace, opaque to the engine (SetTrace,
ReadTraced), and hands it to the hooks, which now pair up: OnBegin as a
recalculation or pivot refresh starts, OnRecalc or OnPivot as it ends.
A recalculation now includes the pivot refreshes it causes, so cmd/012
nests pivot spans in recalc spans in whatever the owner has open.
commit
503499f2cb9d14df6a39999f23a8d4e0af6b4554
parent
801c083827a1b477e7e3c620aecd55456857c87e
author
Dami <dami.etoile@gmail.com>
9 files changed, +107 -24
Mcmd/012/telemetry.go27+++++++++++++++++++----
Minternal/sheet/file.go13+++++++++---
Minternal/sheet/observe.go35++++++++++++++++++++++++------
Ainternal/sheet/observe_test.go34+++++++++++++++++++++++++++++
Minternal/sheet/pivot_test.go2+-
Minternal/sheet/pivotlayout.go9+++++---
Minternal/sheet/recalc.go8+++---
Minternal/sheet/workbook.go1+
Minternal/ui/jev_test.go2+-
M cmd/012/telemetry.go +22 -5
@@ -4,6 +4,7 @@ import (
44 "log/slog"55 "os"66 "runtime/debug"7+ "time"78 89 "github.com/FelineStateMachine/012/internal/config"910 "github.com/FelineStateMachine/012/internal/sheet"
@@ -37,7 +38,8 @@ func telemetryFlags(args []string) (telemetry.Config, []string, error) {
3738 return telemetryConfig(config.Load(path, os.Getenv, flags)), rest, nil3839 }3940 40-// startTelemetry opens the log and reports every recalculation to it.41+// startTelemetry opens the log and reports every recalculation and pivot42+// refresh to it, as spans nested in their workbook's trace.4143 func startTelemetry(c telemetry.Config) (func() error, error) {4244 if info, ok := debug.ReadBuildInfo(); ok {4345 c.Version = info.Main.Version
@@ -51,15 +53,30 @@ func startTelemetry(c telemetry.Config) (func() error, error) {
5153 if err != nil || !telemetry.Enabled() {5254 return stop, err5355 }54- sheet.OnRecalc = func(i sheet.RecalcInfo) {56+ sheet.OnBegin = func(trace any, op string) {57+ if t, ok := trace.(*telemetry.Trace); ok {58+ t.Begin(op)59+ }60+ }61+ sheet.OnRecalc = func(trace any, i sheet.RecalcInfo) {5562 telemetry.Set("cells", int64(i.Cells))56- telemetry.Event("recalc", i.Duration,63+ endSpan(trace, "recalc", i.Duration,5764 slog.Bool("full", i.Full), slog.Int("evaluated", i.Evaluated), slog.Int("cells", i.Cells),5865 slog.Int("volatile", i.Volatile), slog.Bool("circular", i.Circular))5966 }60- sheet.OnPivot = func(i sheet.PivotInfo) {61- telemetry.Event("pivot", i.Duration,67+ sheet.OnPivot = func(trace any, i sheet.PivotInfo) {68+ endSpan(trace, "pivot", i.Duration,6269 slog.Int("records", i.Records), slog.Int("groups", i.Groups), slog.Int("cells", i.Cells), slog.Bool("failed", i.Failed))6370 }6471 return stop, nil6572 }73+74+// endSpan ends the span sheet.OnBegin began in the workbook's trace, or75+// logs the operation as a root span of its own when the workbook has no76+// trace (one read before the UI took it, say).77+func endSpan(trace any, name string, d time.Duration, attrs ...slog.Attr) {78+ if t, ok := trace.(*telemetry.Trace); ok && t.End(attrs...) {79+ return80+ }81+ telemetry.Event(name, d, attrs...)82+}
M internal/sheet/file.go +10 -3
M internal/sheet/observe.go +28 -7
@@ -10,20 +10,41 @@ type RecalcInfo struct {
1010 Cells int // cells stored on every sheet, including formatting-only ones1111 Volatile int // volatile formulas on every sheet, recomputed every time1212 Circular bool13- Duration time.Duration13+ // Duration includes the pivot tables the recalculation refreshed,14+ // and the recalculation of what reads their results.15+ Duration time.Duration1416 }1517 16-// OnRecalc, when set, is called after every recalculation of any workbook.17-// The engine knows nothing of logging; cmd/012 points this at telemetry.18-// It runs on the goroutine that changed the sheet and must be cheap.19-var OnRecalc func(RecalcInfo)18+// The engine knows nothing of logging; cmd/012 points these hooks at19+// telemetry. They run on the goroutine that changed the workbook and20+// must be cheap. trace is the workbook's (see SetTrace), handed back so21+// the engine's work is timed as spans nested in whatever the workbook's22+// owner has open: a command, an import, a macro run.23+var (24+ // OnBegin, when set, is called as a recalculation ("recalc") or a25+ // pivot table refresh ("pivot") begins; OnRecalc or OnPivot as it26+ // ends. They pair up like parentheses: a recalculation holds the27+ // pivot refreshes it causes, which follow its own evaluation.28+ OnBegin func(trace any, op string)29+ // OnRecalc, when set, is called after every recalculation of any30+ // workbook.31+ OnRecalc func(trace any, i RecalcInfo)32+)33+34+// SetTrace gives the workbook its owner's trace, opaque to the engine:35+// a *telemetry.Trace from the program or import that holds the workbook,36+// which only its goroutine uses, as with the workbook itself.37+func (w *Workbook) SetTrace(trace any) { w.trace = trace }2038 2139 // recalcStart is when a recalculation began, or zero when nobody is2240 // watching, so an unobserved recalculation doesn't read the clock.23-func recalcStart() time.Time {41+func (w *Workbook) recalcStart() time.Time {2442 if OnRecalc == nil {2543 return time.Time{}2644 }45+ if OnBegin != nil {46+ OnBegin(w.trace, "recalc")47+ }2748 return time.Now()2849 }2950
@@ -37,5 +58,5 @@ func (w *Workbook) observe(full bool, start time.Time, evaluated int) {
3758 info.Volatile += len(s.volatile)3859 }3960 info.Duration = time.Since(start)40- OnRecalc(info)61+ OnRecalc(w.trace, info)4162 }
A internal/sheet/observe_test.go +34 -0
M internal/sheet/pivot_test.go +1 -1
M internal/sheet/pivotlayout.go +6 -3
M internal/sheet/recalc.go +4 -4
M internal/sheet/workbook.go +1 -0
M internal/ui/jev_test.go +1 -1