diff options
-rw-r--r-- | go.sum | 2 | ||||
-rw-r--r-- | plan_view.go | 32 | ||||
-rw-r--r-- | postgres/plan.go (renamed from plan.go) | 2 | ||||
-rw-r--r-- | postgres/postgres.go | 35 |
4 files changed, 62 insertions, 9 deletions
@@ -5,6 +5,7 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= +github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= @@ -33,6 +34,7 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/tview v0.0.0-20200528200248-fe953220389f h1:tRx/LLIP2PSA7johw9xhf+6NUCLC4BbMhpGdm110MGI= diff --git a/plan_view.go b/plan_view.go index 36b58c1..f2badfa 100644 --- a/plan_view.go +++ b/plan_view.go @@ -79,8 +79,29 @@ func (p *planView) SetPlan(as []*postgres.Explain) { } func buildNode(p *postgres.Plan) *tview.TreeNode { - dur := f2d(p.ActualTotalTime) - n := tview.NewTreeNode(fmt.Sprintf("%s (%s)", p.NodeType, dur.String())) + dur := p.EffectiveTotalTime() + var ( + badEstimate string + sortBy string + scanOn string + scanIndex string + ) + if p.IsBadEstimate() { + badEstimate = " [red](bad estimate)[white]" + } + if len(p.SortKey) > 0 { + sortBy = " by " + strings.Join(p.SortKey, ", ") + } + if p.RelationName != nil { + scanOn = " on " + *p.RelationName + } + if p.IndexName != nil { + scanIndex = " using " + *p.IndexName + } + + label := fmt.Sprintf("%s (%s)%s%s%s%s", p.NodeType, dur.String(), + badEstimate, sortBy, scanOn, scanIndex) + n := tview.NewTreeNode(label) n.SetSelectable(true) n.SetReference(p) for _, subplan := range p.Plans { @@ -287,8 +308,7 @@ func f2a(f float32) string { } func f2d(f float32) time.Duration { - return time.Duration(int(f)) * time.Millisecond - // ms := time.Duration(int(f)) * time.Millisecond - // ns := time.Duration(int((f-float32(ms))*1000)) * time.Nanosecond - // return time.Duration(ms + ns) + // f is in milliseconds, multiply by 1k to get microseconds + // (thousandths of milliseconds) + return time.Duration(int(f)*1000) * time.Microsecond } diff --git a/plan.go b/postgres/plan.go index 89606d6..9aa12f8 100644 --- a/plan.go +++ b/postgres/plan.go @@ -1,4 +1,4 @@ -package main +package postgres const testPlan = ` [ diff --git a/postgres/postgres.go b/postgres/postgres.go index 3a5221e..5333f8d 100644 --- a/postgres/postgres.go +++ b/postgres/postgres.go @@ -3,6 +3,8 @@ package postgres import ( "encoding/json" "fmt" + "math" + "time" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" @@ -139,10 +141,39 @@ func (db *DB) Analyze(query string) ([]*Explain, error) { } var exp []*Explain - if err := json.Unmarshal([]byte(plan.Plan), &exp); err != nil { - // if err := json.Unmarshal([]byte(testPlan), &a); err != nil { + // if err := json.Unmarshal([]byte(plan.Plan), &exp); err != nil { + if err := json.Unmarshal([]byte(testPlan), &exp); err != nil { return nil, fmt.Errorf("could not unmarshal plan: %v", err) } return exp, nil } + +func (p *Plan) EffectiveTotalTime() time.Duration { + var childTime time.Duration + for _, p := range p.Plans { + childTime = childTime + f2d(p.ActualTotalTime) + } + return f2d(p.ActualTotalTime) - childTime +} + +func (p *Plan) EstimateWrongness() (string, int) { + plan, actual := float64(p.PlanRows), float64(p.ActualRows) + bigger, smaller := math.Max(plan, actual), math.Min(plan, actual) + ratio := int(bigger / smaller) + if plan > actual { + return "over", ratio + } + return "under", ratio +} + +func (p *Plan) IsBadEstimate() bool { + _, ratio := p.EstimateWrongness() + return ratio >= 100 +} + +func f2d(f float32) time.Duration { + // f is in milliseconds, multiply by 1k to get microseconds + // (thousandths of milliseconds) + return time.Duration(int(f)*1000) * time.Microsecond +} |