- commit
- cac049458be88b3dcd336aadb2df18b4dc109ca6
- parent
- 9373b4dd607bfd4f0e389ecc164aa6f07d37c4a6
- author
- Dami <dami.etoile@gmail.com>
- kind
- test risk score 1.5 of 3, Jev's estimate
| M | internal/ | 22 | ++++++++--- |
| M | internal/ | 18 | +-------- |
| M | internal/ | 52 | ++++++++++++++++++++++++++ |
| M | internal/ | 3 | +- |
| A | internal/ | 58 | +++++++++++++++++++++++++++++ |
| A | internal/ | 59 | ++++++++++++++++++++++++++++++ |
| M | internal/ | 10 | +++++ |
@@ -127,6 +127,21 @@ func (h *Host) reopen(s *source) {
127127 h.fresh = append(h.fresh, &Job{kind: jobOpen, name: s.name, spec: s.spec, gen: s.gen, open: h.Open})128128 }129129 130+// Reload reads the source named name again, as when its file changed:131+// what was worked out of it is worked out again.132+func (h *Host) Reload(name string) {133+ s := h.srcs[key(name)]134+ if s == nil || s.opening {135+ return136+ }137+ if s.h != nil {138+ s.h.Close()139+ s.h = nil140+ }141+ s.gen++142+ h.reopen(s)143+}144+130145 // Close lets every source go.131146 func (h *Host) Close() {132147 for _, s := range h.srcs {@@ -305,12 +320,7 @@ func (h *Host) Poll() bool {
305320 case now.Sub(s.seenAt) < h.Debounce:306321 continue307322 }308- if s.h != nil {309- s.h.Close() // jobs reading it fail, and their answers are dropped310- s.h = nil311- }312- s.gen++313- h.reopen(s)323+ h.Reload(s.name) // jobs reading it fail, and their answers are dropped314324 queued = true315325 }316326 return queued@@ -48,23 +48,9 @@ func writeSource(t testing.TB, name string, n int) (sum float64) {
4848 // pumpSources runs what the sources have queued until nothing is.4949 func pumpSources(t *testing.T, m *Model) {5050 t.Helper()51- for range 50 {52- run(m, m.syncSources())53- if m.src.host == nil || !m.src.host.Pending() && m.src.running == 0 && !m.pagesPending() {54- return55- }51+ if !settleSources(m) {52+ t.Fatal("the sources never settled")5653 }57- t.Fatal("the sources never settled")58-}59-60-// pagesPending reports whether a page is being read.61-func (m *Model) pagesPending() bool {62- for _, p := range m.src.pages {63- if p.Building() {64- return true65- }66- }67- return false6854 }6955 7056 // linkSales writes sales.parquet with n rows here and links it.@@ -65,6 +65,56 @@ func outputCase(rows int) speedCase {
6565 }}6666 }6767 68+// sourceFrameCase is Page Down and Page Up in turn through to their69+// frames at 200 x 60, halfway down a linked source's tab, the pages70+// they show already read, as scrolling within what's read is.71+func sourceFrameCase() speedCase {72+ return speedCase{fmt.Sprintf("frame/source-%dx4", speedSourceRows), func() func() {73+ m := speedSourceModel()74+ m.srcView().MoveTo(speedSourceRows/2, 2)75+ keys := [2]tea.KeyPressMsg{{Code: tea.KeyPgDown}, {Code: tea.KeyPgUp}}76+ for _, k := range keys { // both windows read77+ m.Update(k)78+ settleSources(m)79+ }80+ t := newFakeTerm(200, 60)81+ i := 082+ return func() {83+ m.Update(keys[i%2])84+ t.frame(m)85+ i++86+ }87+ }}88+}89+90+// sourceSumCase is a linked source read again and a SUM over one of its91+// columns worked out again, as the file changing makes it, run to the92+// end rather than in the background.93+func sourceSumCase() speedCase {94+ return speedCase{fmt.Sprintf("source/sum-%d", speedSourceRows), func() func() {95+ m := speedSourceModel()96+ info, _ := m.sheet.Source()97+ m.showSheet(m.book().Sheet(0))98+ m.sheet.Set(sheet.Addr{}, "=SUM("+info.Name+"[amount])")99+ settleSources(m)100+ return func() {101+ m.src.host.Reload(info.Name)102+ if !settleSources(m) || m.sheet.Value(sheet.Addr{}).Kind != sheet.Number {103+ panic("the SUM over the source never settled")104+ }105+ }106+ }}107+}108+109+// speedSourceModel is a model showing the speed gate's source.110+func speedSourceModel() *Model {111+ path, err := speedSources()112+ if err != nil {113+ panic(err)114+ }115+ return sourceModel(path, 200, 60)116+}117+68118 // editCase is typing into one cell of a stress shape and the69119 // incremental recalculation that follows, two entries in turn.70120 func editCase(sh stress.Shape) speedCase {@@ -108,6 +158,8 @@ func speedCases() []speedCase {
108158 frameCase("scale-8192x26", scaled),109159 frameCase("charts-8", charts),110160 outputCase(100000),161+ sourceFrameCase(),162+ sourceSumCase(),111163 editCase(fanin),112164 editCase(shape("chain-8192", func() *sheet.Sheet { return stress.Chain(stress.Rows) }, "A1", "2")),113165 editCase(shape("criteria-10xSUMIF8192", func() *sheet.Sheet { return stress.Criteria(stress.Rows, 10) }, "A4001", "7")),@@ -36,8 +36,7 @@ func init() {
3636 enabled: func(m *Model) bool { return m.sheet.IsSource() },3737 run: func(m *Model) tea.Cmd {3838 info, _ := m.sheet.Source()39- m.src.links = "" // linked again, so opened again40- m.src.host.Link(nil)39+ m.src.host.Reload(info.Name)4140 m.note = "Reading " + filepath.Base(info.Source.Path) + " again"4241 return nil4342 }})@@ -0,0 +1,58 @@
1+package ui2+3+import (4+ "os"5+ "path/filepath"6+ "sync"7+8+ "github.com/FelineStateMachine/012/internal/sheet"9+ "github.com/FelineStateMachine/012/internal/stress"10+)11+12+// Linked sources for the speed gate (speed_test.go) and the stress13+// benchmarks (srcstress_test.go): a model showing a source's tab, its14+// jobs run as they come.15+16+// sourceModel is a model w x h showing the tab of the Parquet file at17+// path linked as a source, opened, its first rows read.18+func sourceModel(path string, w, h int) *Model {19+ m := sized(sheet.New(), w, h)20+ m.addSource(sheet.LinkSource{Path: path})21+ settleSources(m)22+ return m23+}24+25+// settleSources runs what the sources have queued, and what that26+// queues, until nothing is, reporting whether it settled.27+func settleSources(m *Model) bool {28+ for range 100 {29+ run(m, m.syncSources())30+ if m.src.host == nil || !m.src.host.Pending() && m.src.running == 0 && !m.pagesPending() {31+ return true32+ }33+ }34+ return false35+}36+37+// pagesPending reports whether a view is being built.38+func (m *Model) pagesPending() bool {39+ for _, p := range m.src.pages {40+ if p.Building() {41+ return true42+ }43+ }44+ return false45+}46+47+// speedSources is the Parquet source of rows rows the speed gate48+// links, made once in a folder of the system's temporary one.49+var speedSources = sync.OnceValues(func() (string, error) {50+ dir := filepath.Join(os.TempDir(), "012-speed-sources")51+ if err := os.MkdirAll(dir, 0o755); err != nil {52+ return "", err53+ }54+ return stress.SalesParquet(dir, speedSourceRows)55+})56+57+// speedSourceRows is the rows of the speed gate's source.58+const speedSourceRows = 200_000@@ -0,0 +1,59 @@
1+//go:build stress2+3+package ui4+5+import (6+ "fmt"7+ "testing"8+9+ tea "charm.land/bubbletea/v2"10+11+ "github.com/FelineStateMachine/012/internal/stress"12+)13+14+// BenchmarkSourceScroll scrolls a linked source of ten million rows15+// (stress.SalesParquet) through to its frames: Page Down and Up within16+// the rows read, halfway down, as scrolling is between pages; and17+// jumping to rows all over the source, as dragging the scrollbar does,18+// reading the pages each jump shows (run to the end here, in the19+// background on the screen) before the frame that shows them. The row20+// numbers and the scrollbar are over every row, whatever the grid's.21+func BenchmarkSourceScroll(b *testing.B) {22+ path, err := stress.SalesParquet(stress.GeneratedDir(b.TempDir()), 10_000_000)23+ if err != nil {24+ b.Fatal(err)25+ }26+ defer stress.BenchLock(b)()27+ cases := []struct {28+ name string29+ keys []tea.KeyPressMsg30+ read bool31+ }{32+ {"pgdown", []tea.KeyPressMsg{{Code: tea.KeyPgDown}, {Code: tea.KeyPgUp}}, false},33+ {"jump", nil, true},34+ }35+ for _, c := range cases {36+ for _, sz := range termSizes[:2] {37+ b.Run(fmt.Sprintf("%s/%dx%d", c.name, sz.w, sz.h), func(b *testing.B) {38+ m := sourceModel(path, sz.w, sz.h)39+ m.srcView().MoveTo(5_000_000, 2)40+ for _, k := range c.keys {41+ m.Update(k)42+ settleSources(m)43+ }44+ t := newFakeTerm(sz.w, sz.h)45+ i := 046+ for b.Loop() {47+ if c.read {48+ m.srcView().MoveTo(int64(i)*1_234_567%10_000_000, 2)49+ settleSources(m)50+ } else {51+ m.Update(c.keys[i%2])52+ }53+ t.frame(m)54+ i++55+ }56+ })57+ }58+ }59+}@@ -47,6 +47,11 @@
4747 "ratio": 2.076,4848 "allocs": 101424949 },50+ "frame/source-200000x4": {51+ "ns": 879000,52+ "ratio": 1.52,53+ "allocs": 372254+ },5055 "recalc/arrays-50xFILTER8192": {5156 "ns": 7554927,5257 "ratio": 12.613,@@ -56,6 +61,11 @@
5661 "ns": 1884083,5762 "ratio": 3.023,5863 "allocs": 827064+ },65+ "source/sum-200000": {66+ "ns": 8033000,67+ "ratio": 11.63,68+ "allocs": 3605969 }6070 }6171 }