1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
package postgres
import (
"encoding/json"
"fmt"
"math"
"time"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type DB struct {
*sqlx.DB
}
func Open(conn string) (*DB, error) {
db, err := sqlx.Open("postgres", conn)
if err != nil {
return nil, err
}
return &DB{db}, nil
}
type Schema struct {
SchemaName string `db:"schema_name"`
}
func (db *DB) GetSchemata() ([]*Schema, error) {
var schemata []*Schema
if err := db.Select(&schemata, "SELECT schema_name FROM information_schema.schemata"); err != nil {
return nil, err
}
return schemata, nil
}
type PgStatStatement struct {
UserID int `db:"userid"`
DBID int `db:"dbid"`
QueryID int64 `db:"queryid"`
Query string `db:"query"`
Calls int `db:"calls"`
TotalTime float32 `db:"total_time"`
MinTime float32 `db:"min_time"`
MaxTime float32 `db:"max_time"`
MeanTime float32 `db:"mean_time"`
StddevTime float32 `db:"stddev_time"`
Rows int64 `db:"rows"`
SharedBlksHit int64 `db:"shared_blks_hit"`
SharedBlksRead int64 `db:"shared_blks_read"`
SharedBlksDirtied int64 `db:"shared_blks_dirtied"`
SharedBlksWritten int64 `db:"shared_blks_written"`
LocalBlksHit int64 `db:"local_blks_hit"`
LocalBlksRead int64 `db:"local_blks_read"`
LocalBlksDirtied int64 `db:"local_blks_dirtied"`
LocalBlksWritten int64 `db:"local_blks_written"`
TempBlksRead int64 `db:"temp_blks_read"`
TempBlksWritten int64 `db:"temp_blks_written"`
BlkReadTime float32 `db:"blk_read_time"`
BlkWriteTime float32 `db:"blk_write_time"`
}
func (db *DB) GetPgStatStatements(schema string) ([]*PgStatStatement, error) {
var stmts []*PgStatStatement
if err := db.Select(&stmts, fmt.Sprintf("SELECT * FROM %s.pg_stat_statements ORDER BY total_time DESC", schema)); err != nil {
return nil, err
}
return stmts, nil
}
type Explain struct {
Plan *Plan `json:"Plan"`
PlanningTime float32 `json:"Planning Time"`
// Triggers []string `json:"Triggers"`
ExecutionTime float32 `json:"Execution Time"`
}
type Plan struct {
NodeType string `json:"Node Type"`
StartupCost float32 `json:"Startup Cost"`
TotalCost float32 `json:"Total Cost"`
PlanRows int `json:"Plan Rows"`
PlanWidth int `json:"Plan Width"`
ActualStartupTime float32 `json:"Actual Startup Time"`
ActualTotalTime float32 `json:"Actual Total Time"`
ActualRows int `json:"Actual Rows"`
ActualLoops int `json:"Actual Loops"`
Output []string `json:"Output"`
SharedHitBlocks int `json:"Shared Hit Blocks"`
SharedReadBlocks int `json:"Shared Read Blocks"`
SharedDirtiedBlocks int `json:"Shared Dirtied Blocks"`
SharedWrittenBlocks int `json:"Shared Written Blocks"`
LocalHitBlocks int `json:"Local Hit Blocks"`
LocalReadBlocks int `json:"Local Read Blocks"`
LocalDirtiedBlocks int `json:"Local Dirtied Blocks"`
LocalWrittenBlocks int `json:"Local Written Blocks"`
TempReadBlocks int `json:"Temp Read Blocks"`
TempWrittenBlocks int `json:"Temp Written Blocks"`
IOReadTime float32 `json:"I/O Read Time"`
IOWriteTime float32 `json:"I/O Write Time"`
ParentRelationship *string `json:"Parent Relationship"`
SortKey []string `json:"Sort Key"`
SortMethod *string `json:"Sort Method"`
SortSpaceUsed *int `json:"Sort Space Used"`
SortSpaceType *string `json:"Sort Space Type"`
JoinType *string `json:"Join Type"`
Strategy *string `json:"Strategy"`
RelationName *string `json:"Relation Name"`
Schema *string `json:"Schema"`
Alias *string `json:"Alias"`
RecheckCond *string `json:"Recheck Cond"`
RowsRemovedByIndexRecheck *int `json:"Rows Removed by Index Recheck"`
RowsRemovedByFilter *int `json:"Rows Removed by Filter"`
ExactHeapBlocks *int `json:"Exact Heap Blocks"`
LossyHeapBlocks *int `json:"Lossy Heap Blocks"`
IndexName *string `json:"Index Name"`
IndexCond *string `json:"Index Cond"`
ScanDirection *string `json:"Scan Direction"`
ParallelAware *bool `json:"Parallel Aware"`
FunctionName *string `json:"Function Name"`
TableFunctionName *string `json:"Table Function Name"`
CTEName *string `json:"CTE Name"`
TuplestoreName *string `json:"Tuplestore Name"`
PartialMode *string `json:"Partial Mode"`
Operation *string `json:"Operation"`
SubplanName *string `json:"Subplan Name"`
CustomPlanProvider *string `json:"Custom Plan Provider"`
Command *string `json:"Command"`
InnerUnique *bool `json:"Inner Unique"`
OrderBy *string `json:"Order By"`
Filter *string `json:"Filter"`
HeapFetches *string `json:"HeapFetches"`
WorkersPlanned *int `json:"Workers Planned"`
WorkersLaunched *int `json:"Workers Launched"`
SingleCopy *bool `json:"Single Copy"`
FunctionCall []string `json:"Function Call"`
TableFunctionCall []string `json:"Table Function Call"`
TIDCond *string `json:"TID Cond"`
JoinFilter *string `json:"Join Filter"`
MergeCond *string `json:"Merge Cond"`
HashCond *string `json:"Hash Cond"`
PresortedKey []string `json:"Presorted Key"`
Plans []*Plan `json:"Plans"`
}
func (db *DB) Analyze(query string) ([]*Explain, error) {
tx, err := db.Beginx()
if err != nil {
return nil, fmt.Errorf("could not begin transaction: %v", err)
}
defer tx.Rollback()
type qp struct {
Plan string `db:"QUERY PLAN"`
}
var plan qp
if err := tx.Get(&plan, fmt.Sprintf("EXPLAIN (ANALYZE, COSTS, BUFFERS, VERBOSE, FORMAT JSON) %s", query)); err != nil {
return nil, fmt.Errorf("could not analyze query: %v", err)
}
if err := tx.Rollback(); err != nil {
return nil, fmt.Errorf("could not roll back transaction: %v", err)
}
var exp []*Explain
// 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
}
|