From c1dcb9245f288ddef7a213b7d2fd8a8ebe5d3ab3 Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Tue, 2 Jun 2020 21:36:52 -0400 Subject: Reorganize files --- plan_view.go | 294 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 plan_view.go (limited to 'plan_view.go') 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) +} -- cgit v1.2.3