-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathmysql_throttle_metric.go
More file actions
162 lines (139 loc) · 5.52 KB
/
Copy pathmysql_throttle_metric.go
File metadata and controls
162 lines (139 loc) · 5.52 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
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
/*
Copyright 2017 GitHub Inc.
See https://github.com/github/freno/blob/master/LICENSE
*/
package mysql
import (
"errors"
"fmt"
"strings"
"time"
"github.com/go-sql-driver/mysql"
"github.com/outbrain/golib/sqlutils"
"github.com/patrickmn/go-cache"
metrics "github.com/rcrowley/go-metrics"
)
var mysqlMetricCache = cache.New(cache.NoExpiration, 10*time.Millisecond)
func getMySQLMetricCacheKey(probe *Probe) string {
return fmt.Sprintf("%s:%s", probe.Key, probe.MetricQuery)
}
func cacheMySQLThrottleMetric(probe *Probe, mySQLThrottleMetric *MySQLThrottleMetric) *MySQLThrottleMetric {
if mySQLThrottleMetric.Err != nil {
return mySQLThrottleMetric
}
if probe.CacheMillis > 0 {
mysqlMetricCache.Set(getMySQLMetricCacheKey(probe), mySQLThrottleMetric, time.Duration(probe.CacheMillis)*time.Millisecond)
}
return mySQLThrottleMetric
}
func getCachedMySQLThrottleMetric(probe *Probe) *MySQLThrottleMetric {
if probe.CacheMillis == 0 {
return nil
}
if metric, found := mysqlMetricCache.Get(getMySQLMetricCacheKey(probe)); found {
mySQLThrottleMetric, _ := metric.(*MySQLThrottleMetric)
return mySQLThrottleMetric
}
return nil
}
type MySQLThrottleMetric struct {
ClusterName string
Key InstanceKey
Value float64
Err error
}
func NewMySQLThrottleMetric() *MySQLThrottleMetric {
return &MySQLThrottleMetric{Value: 0}
}
func (metric *MySQLThrottleMetric) GetClusterInstanceKey() ClusterInstanceKey {
return GetClusterInstanceKey(metric.ClusterName, &metric.Key)
}
func (metric *MySQLThrottleMetric) HashCode() string {
return metric.GetClusterInstanceKey().HashCode()
}
func (metric *MySQLThrottleMetric) Get() (float64, error) {
return metric.Value, metric.Err
}
// ReadThrottleMetric returns replication lag for a given connection config; either by explicit query
// or via SHOW REPLICA STATUS / SHOW SLAVE STATUS
func ReadThrottleMetric(probe *Probe, clusterName string) (mySQLThrottleMetric *MySQLThrottleMetric) {
if mySQLThrottleMetric := getCachedMySQLThrottleMetric(probe); mySQLThrottleMetric != nil {
return mySQLThrottleMetric
// On cached results we avoid taking latency metrics
}
started := time.Now()
mySQLThrottleMetric = NewMySQLThrottleMetric()
mySQLThrottleMetric.ClusterName = clusterName
mySQLThrottleMetric.Key = probe.Key
defer func(metric *MySQLThrottleMetric, started time.Time) {
go func() {
metrics.GetOrRegisterTimer("probes.latency", nil).Update(time.Since(started))
metrics.GetOrRegisterCounter("probes.total", nil).Inc(1)
if metric.Err != nil {
metrics.GetOrRegisterCounter("probes.error", nil).Inc(1)
}
}()
}(mySQLThrottleMetric, started)
dbUri := probe.GetDBUri("information_schema")
db, fromCache, err := sqlutils.GetDB(dbUri)
if err != nil {
mySQLThrottleMetric.Err = err
return mySQLThrottleMetric
}
if !fromCache {
db.SetMaxOpenConns(maxPoolConnections)
db.SetMaxIdleConns(maxIdleConnections)
}
if strings.HasPrefix(strings.ToLower(probe.MetricQuery), "select") {
mySQLThrottleMetric.Err = db.QueryRow(probe.MetricQuery).Scan(&mySQLThrottleMetric.Value)
return cacheMySQLThrottleMetric(probe, mySQLThrottleMetric)
}
if strings.HasPrefix(strings.ToLower(probe.MetricQuery), "show global") {
var variableName string // just a placeholder
mySQLThrottleMetric.Err = db.QueryRow(probe.MetricQuery).Scan(&variableName, &mySQLThrottleMetric.Value)
return cacheMySQLThrottleMetric(probe, mySQLThrottleMetric)
}
if probe.MetricQuery != "" {
mySQLThrottleMetric.Err = fmt.Errorf("Unsupported metrics query type: %s", probe.MetricQuery)
return mySQLThrottleMetric
}
// No metric query? By default we look at replication lag.
// Try SHOW REPLICA STATUS first (MySQL 8.0.22+, required in 8.4+), fall back to
// SHOW SLAVE STATUS for older MySQL versions that don't recognise the new syntax.
mySQLThrottleMetric.Err = sqlutils.QueryRowsMap(db, `show replica status`, func(m sqlutils.RowMap) error {
replicaIORunning := m.GetString("Replica_IO_Running")
replicaSQLRunning := m.GetString("Replica_SQL_Running")
secondsBehindSource := m.GetNullInt64("Seconds_Behind_Source")
if !secondsBehindSource.Valid {
return fmt.Errorf("replication not running; Replica_IO_Running=%+v, Replica_SQL_Running=%+v", replicaIORunning, replicaSQLRunning)
}
mySQLThrottleMetric.Value = float64(secondsBehindSource.Int64)
return nil
})
// MySQL error 1064 means syntax error — the server doesn't understand SHOW REPLICA STATUS
// (MySQL < 8.0.22). Fall back to the legacy SHOW SLAVE STATUS command.
if mySQLThrottleMetric.Err != nil {
var mysqlErr *mysql.MySQLError
if errors.As(mySQLThrottleMetric.Err, &mysqlErr) && mysqlErr.Number == 1064 {
originalErr := mySQLThrottleMetric.Err
fallbackErr := sqlutils.QueryRowsMap(db, `show slave status`, func(m sqlutils.RowMap) error {
slaveIORunning := m.GetString("Slave_IO_Running")
slaveSQLRunning := m.GetString("Slave_SQL_Running")
secondsBehindMaster := m.GetNullInt64("Seconds_Behind_Master")
if !secondsBehindMaster.Valid {
return fmt.Errorf("replication not running; Slave_IO_Running=%+v, Slave_SQL_Running=%+v", slaveIORunning, slaveSQLRunning)
}
mySQLThrottleMetric.Value = float64(secondsBehindMaster.Int64)
return nil
})
if fallbackErr == nil {
mySQLThrottleMetric.Err = nil
} else {
// Both commands failed; surface the original error as it's more informative.
mySQLThrottleMetric.Err = originalErr
}
}
// Non-syntax errors (permissions, connectivity, replication issues) are kept as-is.
}
return cacheMySQLThrottleMetric(probe, mySQLThrottleMetric)
}