-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcluster.go
More file actions
104 lines (95 loc) · 2.99 KB
/
cluster.go
File metadata and controls
104 lines (95 loc) · 2.99 KB
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
package qshape
import "sort"
type (
Query struct {
Raw string `json:"raw"`
QueryID int64 `json:"queryid,omitempty"`
Calls int64 `json:"calls,omitempty"`
TotalExecTimeMs float64 `json:"total_exec_time_ms,omitempty"`
MeanExecTimeMs float64 `json:"mean_exec_time_ms,omitempty"`
StddevExecTimeMs float64 `json:"stddev_exec_time_ms,omitempty"`
Rows int64 `json:"rows,omitempty"`
}
Cluster struct {
Fingerprint string `json:"fingerprint"`
Canonical string `json:"canonical"`
Members []Query `json:"members"`
TotalCalls int64 `json:"total_calls"`
TotalExecTimeMs float64 `json:"total_exec_time_ms,omitempty"`
MeanExecTimeMs float64 `json:"mean_exec_time_ms,omitempty"`
Rows int64 `json:"rows,omitempty"`
Params []ParamAttribution `json:"params,omitempty"`
}
ParamAttribution struct {
Position int `json:"position"`
Schema string `json:"schema,omitempty"`
Table string `json:"table,omitempty"`
Column string `json:"column,omitempty"`
Confidence string `json:"confidence"`
Note string `json:"note,omitempty"`
}
)
// Group clusters queries by canonical fingerprint. Queries that fail to
// parse become singleton clusters with empty Fingerprint. Output is
// sorted by descending TotalExecTimeMs (when any timing is present),
// otherwise by descending TotalCalls, with Fingerprint as the tiebreaker.
func Group(queries []Query) ([]Cluster, error) {
groups := make(map[string]*Cluster)
var unparseable []Cluster
for _, q := range queries {
fp, err := Fingerprint(q.Raw)
if err != nil {
unparseable = append(unparseable, Cluster{
Fingerprint: "",
Canonical: q.Raw,
Members: []Query{q},
TotalCalls: q.Calls,
TotalExecTimeMs: q.TotalExecTimeMs,
Rows: q.Rows,
MeanExecTimeMs: q.MeanExecTimeMs,
})
continue
}
c, ok := groups[fp]
if !ok {
canonical, derr := Normalize(q.Raw)
if derr != nil {
canonical = q.Raw
}
c = &Cluster{
Fingerprint: fp,
Canonical: canonical,
}
groups[fp] = c
}
c.Members = append(c.Members, q)
c.TotalCalls += q.Calls
c.TotalExecTimeMs += q.TotalExecTimeMs
c.Rows += q.Rows
}
out := make([]Cluster, 0, len(groups)+len(unparseable))
for _, c := range groups {
if c.TotalCalls > 0 {
c.MeanExecTimeMs = c.TotalExecTimeMs / float64(c.TotalCalls)
}
out = append(out, *c)
}
out = append(out, unparseable...)
hasTiming := false
for _, c := range out {
if c.TotalExecTimeMs > 0 {
hasTiming = true
break
}
}
sort.Slice(out, func(i, j int) bool {
if hasTiming && out[i].TotalExecTimeMs != out[j].TotalExecTimeMs {
return out[i].TotalExecTimeMs > out[j].TotalExecTimeMs
}
if out[i].TotalCalls != out[j].TotalCalls {
return out[i].TotalCalls > out[j].TotalCalls
}
return out[i].Fingerprint < out[j].Fingerprint
})
return out, nil
}