summaryrefslogtreecommitdiff
path: root/plan_view.go
diff options
context:
space:
mode:
Diffstat (limited to 'plan_view.go')
-rw-r--r--plan_view.go294
1 files changed, 294 insertions, 0 deletions
diff --git a/plan_view.go b/plan_view.go
new file mode 100644
index 0000000..36b58c1
--- /dev/null
+++ b/plan_view.go
@@ -0,0 +1,294 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/rivo/tview"
+
+ "bnbl.io/pgqt/postgres"
+)
+
+type planView struct {
+ *tview.Flex
+
+ tree *tview.TreeView
+ detail *tview.Table
+}
+
+func newPlanView() *planView {
+ f := tview.NewFlex()
+ f.SetBorder(true).SetTitle("Plan")
+ f.SetDirection(tview.FlexRow)
+
+ t := tview.NewTreeView()
+ d := tview.NewTable()
+
+ f.AddItem(t, 0, 50, true)
+ f.AddItem(d, 0, 50, false)
+
+ pv := &planView{
+ Flex: f,
+ tree: t,
+ detail: d,
+ }
+
+ t.SetSelectedFunc(func(node *tview.TreeNode) {
+ go func() {
+ app.QueueUpdateDraw(func() {
+ ref := node.GetReference()
+ if ref == nil {
+ pv.ShowDetail(nil)
+ return
+ }
+ p, ok := ref.(*postgres.Plan)
+ if !ok {
+ pv.ShowDetail(nil)
+ return
+ }
+ log.Printf("selected node named %s", p.NodeType)
+ pv.ShowDetail(p)
+ })
+ }()
+ })
+ return pv
+}
+
+func (p *planView) SetError(err error) {
+ p.tree.SetRoot(tview.NewTreeNode(err.Error()))
+}
+
+func (p *planView) SetPlan(as []*postgres.Explain) {
+ if len(as) == 0 {
+ p.tree.SetRoot(tview.NewTreeNode("No plan results to display"))
+ return
+ }
+
+ if len(as) > 1 {
+ log.Printf("skipping display of more than one analysis result")
+ }
+
+ a := as[0]
+ root := tview.NewTreeNode(fmt.Sprintf("Query Plan (planning=%s execution=%s)", f2d(a.PlanningTime), f2d(a.ExecutionTime)))
+ root.AddChild(buildNode(a.Plan))
+ p.tree.SetRoot(root)
+ p.tree.SetCurrentNode(root)
+}
+
+func buildNode(p *postgres.Plan) *tview.TreeNode {
+ dur := f2d(p.ActualTotalTime)
+ n := tview.NewTreeNode(fmt.Sprintf("%s (%s)", p.NodeType, dur.String()))
+ n.SetSelectable(true)
+ n.SetReference(p)
+ for _, subplan := range p.Plans {
+ n.AddChild(buildNode(subplan))
+ }
+ return n
+}
+
+func (pv *planView) ShowDetail(p *postgres.Plan) {
+ pv.detail.Clear()
+
+ if p == nil {
+ return
+ }
+
+ pv.detail.SetCellSimple(0, 0, "Node Type")
+ pv.detail.SetCellSimple(0, 1, p.NodeType)
+
+ pv.detail.SetCellSimple(1, 0, "Startup Cost")
+ pv.detail.SetCellSimple(1, 1, f2a(p.StartupCost))
+
+ pv.detail.SetCellSimple(2, 0, "Total Cost")
+ pv.detail.SetCellSimple(2, 1, f2a(p.TotalCost))
+
+ pv.detail.SetCellSimple(3, 0, "Plan Rows")
+ pv.detail.SetCellSimple(3, 1, strconv.Itoa(p.PlanRows))
+
+ pv.detail.SetCellSimple(4, 0, "Actual Rows")
+ pv.detail.SetCellSimple(4, 1, strconv.Itoa(p.ActualRows))
+
+ pv.detail.SetCellSimple(5, 0, "Plan Width")
+ pv.detail.SetCellSimple(5, 1, strconv.Itoa(p.PlanWidth))
+
+ pv.detail.SetCellSimple(6, 0, "Actual Startup Time")
+ pv.detail.SetCellSimple(6, 1, f2d(p.ActualStartupTime).String())
+
+ pv.detail.SetCellSimple(7, 0, "Actual Total Time")
+ pv.detail.SetCellSimple(7, 1, f2d(p.ActualTotalTime).String())
+
+ pv.detail.SetCellSimple(8, 0, "Actual Loops")
+ pv.detail.SetCellSimple(8, 1, strconv.Itoa(p.ActualLoops))
+
+ pv.detail.SetCellSimple(9, 0, "Shared Hit Blocks")
+ pv.detail.SetCellSimple(9, 1, strconv.Itoa(p.SharedHitBlocks))
+
+ pv.detail.SetCellSimple(10, 0, "Shared Read Blocks")
+ pv.detail.SetCellSimple(10, 1, strconv.Itoa(p.SharedReadBlocks))
+
+ pv.detail.SetCellSimple(11, 0, "Shared Dirtied Blocks")
+ pv.detail.SetCellSimple(11, 1, strconv.Itoa(p.SharedDirtiedBlocks))
+
+ pv.detail.SetCellSimple(12, 0, "Shared Written Blocks")
+ pv.detail.SetCellSimple(12, 1, strconv.Itoa(p.SharedWrittenBlocks))
+
+ pv.detail.SetCellSimple(13, 0, "Local Hit Blocks")
+ pv.detail.SetCellSimple(13, 1, strconv.Itoa(p.LocalHitBlocks))
+
+ pv.detail.SetCellSimple(14, 0, "Local Read Blocks")
+ pv.detail.SetCellSimple(14, 1, strconv.Itoa(p.LocalReadBlocks))
+
+ pv.detail.SetCellSimple(15, 0, "Local Dirtied Blocks")
+ pv.detail.SetCellSimple(15, 1, strconv.Itoa(p.LocalDirtiedBlocks))
+
+ pv.detail.SetCellSimple(16, 0, "Local Written Blocks")
+ pv.detail.SetCellSimple(16, 1, strconv.Itoa(p.LocalWrittenBlocks))
+
+ pv.detail.SetCellSimple(17, 0, "Temp Read Blocks")
+ pv.detail.SetCellSimple(17, 1, strconv.Itoa(p.TempReadBlocks))
+
+ pv.detail.SetCellSimple(18, 0, "Temp Written Blocks")
+ pv.detail.SetCellSimple(18, 1, strconv.Itoa(p.TempWrittenBlocks))
+
+ pv.detail.SetCellSimple(19, 0, "I/O Read Time")
+ pv.detail.SetCellSimple(19, 1, f2d(p.IOReadTime).String())
+
+ pv.detail.SetCellSimple(20, 0, "I/O Write Time")
+ pv.detail.SetCellSimple(20, 1, f2d(p.IOWriteTime).String())
+
+ idx := 21
+
+ if p.ParentRelationship != nil {
+ pv.detail.SetCellSimple(idx, 0, "Parent Relationship")
+ pv.detail.SetCellSimple(idx, 1, *p.ParentRelationship)
+ idx++
+ }
+
+ if len(p.Output) > 0 {
+ pv.detail.SetCellSimple(idx, 0, "Output")
+ pv.detail.SetCellSimple(idx, 1, strings.Join(p.Output, ", "))
+ idx++
+ }
+
+ if len(p.SortKey) > 0 {
+ pv.detail.SetCellSimple(idx, 0, "Sort Key")
+ pv.detail.SetCellSimple(idx, 1, strings.Join(p.SortKey, ", "))
+ idx++
+ }
+
+ if p.SortMethod != nil {
+ pv.detail.SetCellSimple(idx, 0, "Sort Method")
+ pv.detail.SetCellSimple(idx, 1, *p.SortMethod)
+ idx++
+ }
+
+ if p.SortSpaceUsed != nil {
+ pv.detail.SetCellSimple(idx, 0, "Sort Space Used")
+ pv.detail.SetCellSimple(idx, 1, strconv.Itoa(*p.SortSpaceUsed))
+ idx++
+ }
+
+ if p.SortSpaceType != nil {
+ pv.detail.SetCellSimple(idx, 0, "Sort Space Type")
+ pv.detail.SetCellSimple(idx, 1, *p.SortSpaceType)
+ idx++
+ }
+
+ if p.JoinType != nil {
+ pv.detail.SetCellSimple(idx, 0, "Join Type")
+ pv.detail.SetCellSimple(idx, 1, *p.JoinType)
+ idx++
+ }
+
+ if p.Strategy != nil {
+ pv.detail.SetCellSimple(idx, 0, "Strategy")
+ pv.detail.SetCellSimple(idx, 1, *p.Strategy)
+ idx++
+ }
+
+ if p.RelationName != nil {
+ pv.detail.SetCellSimple(idx, 0, "Relation Name")
+ pv.detail.SetCellSimple(idx, 1, *p.RelationName)
+ idx++
+ }
+
+ if p.Schema != nil {
+ pv.detail.SetCellSimple(idx, 0, "Schema")
+ pv.detail.SetCellSimple(idx, 1, *p.Schema)
+ idx++
+ }
+
+ if p.Alias != nil {
+ pv.detail.SetCellSimple(idx, 0, "Alias")
+ pv.detail.SetCellSimple(idx, 1, *p.Alias)
+ idx++
+ }
+
+ if p.RecheckCond != nil {
+ pv.detail.SetCellSimple(idx, 0, "Recheck Cond")
+ pv.detail.SetCellSimple(idx, 1, *p.RecheckCond)
+ idx++
+ }
+
+ if p.RowsRemovedByIndexRecheck != nil {
+ pv.detail.SetCellSimple(idx, 0, "Rows Removed by Index Recheck")
+ pv.detail.SetCellSimple(idx, 1, strconv.Itoa(*p.RowsRemovedByIndexRecheck))
+ idx++
+ }
+
+ if p.ExactHeapBlocks != nil {
+ pv.detail.SetCellSimple(idx, 0, "Exact Heap Blocks")
+ pv.detail.SetCellSimple(idx, 1, strconv.Itoa(*p.ExactHeapBlocks))
+ idx++
+ }
+
+ if p.LossyHeapBlocks != nil {
+ pv.detail.SetCellSimple(idx, 0, "Lossy Heap Blocks")
+ pv.detail.SetCellSimple(idx, 1, strconv.Itoa(*p.LossyHeapBlocks))
+ idx++
+ }
+
+ if p.IndexName != nil {
+ pv.detail.SetCellSimple(idx, 0, "Index Name")
+ pv.detail.SetCellSimple(idx, 1, *p.IndexName)
+ idx++
+ }
+
+ if p.IndexCond != nil {
+ pv.detail.SetCellSimple(idx, 0, "Index Cond")
+ pv.detail.SetCellSimple(idx, 1, *p.IndexCond)
+ idx++
+ }
+
+ if p.ScanDirection != nil {
+ pv.detail.SetCellSimple(idx, 0, "Scan Direction")
+ pv.detail.SetCellSimple(idx, 1, *p.ScanDirection)
+ idx++
+ }
+
+ if p.ParallelAware != nil {
+ pv.detail.SetCellSimple(idx, 0, "Parallel Aware")
+ pv.detail.SetCellSimple(idx, 1, fmt.Sprintf("%t", *p.ParallelAware))
+ idx++
+ }
+
+ if p.FunctionName != nil {
+ pv.detail.SetCellSimple(idx, 0, "Function Name")
+ pv.detail.SetCellSimple(idx, 1, *p.FunctionName)
+ idx++
+ }
+}
+
+func f2a(f float32) string {
+ return fmt.Sprintf("%f", f)
+}
+
+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)
+}