master
go 178 lines 4.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package mysql
4
5 import (
6 "context"
7 "database/sql"
8 "errors"
9 "fmt"
10 "strconv"
11 "time"
12
13 "github.com/blang/semver/v4"
14
15 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/sqlquery"
16 )
17
18 func (c *Collector) ensureVersionAndCapabilities(ctx context.Context) error {
19 if c.version != nil {
20 return nil
21 }
22 if err := c.collectVersion(ctx); err != nil {
23 return fmt.Errorf("error on collecting version: %v", err)
24 }
25 // https://mariadb.com/kb/en/user-statistics/
26 c.doUserStatistics = c.isPercona || c.isMariaDB && c.version.GTE(semver.Version{Major: 10, Minor: 1, Patch: 1})
27 return nil
28 }
29
30 func (c *Collector) collect(ctx context.Context) error {
31 if !c.dbReady() {
32 if err := c.openConnection(ctx); err != nil {
33 return err
34 }
35 }
36 if err := c.ensureVersionAndCapabilities(ctx); err != nil {
37 return err
38 }
39
40 c.disableSessionQueryLog(ctx)
41
42 state := &collectRunState{}
43
44 if err := c.collectGlobalStatus(ctx, state); err != nil {
45 return fmt.Errorf("error on collecting global status: %v", err)
46 }
47
48 if err := c.collectEngineInnoDBStatus(ctx, state); err != nil {
49 return fmt.Errorf("error on collecting engine innodb status: %v", err)
50 }
51
52 now := time.Now()
53 if now.Sub(c.recheckGlobalVarsTime) > c.recheckGlobalVarsEvery {
54 if err := c.collectGlobalVariables(ctx); err != nil {
55 return fmt.Errorf("error on collecting global variables: %v", err)
56 }
57 c.recheckGlobalVarsTime = now
58 }
59 c.mx.set("innodb_log_file_size", c.varInnoDBLogFileSize)
60 c.mx.set("innodb_log_files_in_group", c.varInnoDBLogFilesInGroup)
61
62 logGroupCapacity := c.varInnoDBLogFileSize * c.varInnoDBLogFilesInGroup
63 c.mx.set("innodb_log_group_capacity", logGroupCapacity)
64
65 // https://mariadb.com/docs/server/server-usage/storage-engines/innodb/innodb-redo-log#determining-the-redo-log-occupancy
66 if logGroupCapacity > 0 {
67 c.mx.set("innodb_log_occupancy", 100*1000*state.innodbCheckpointAge/logGroupCapacity)
68 } else {
69 c.mx.set("innodb_log_occupancy", 0)
70 }
71 c.mx.set("max_connections", c.varMaxConns)
72 c.mx.set("table_open_cache", c.varTableOpenCache)
73
74 // TODO: perhaps make a decisions based on privileges? (SHOW GRANTS FOR CURRENT_USER();)
75 if c.doSlaveStatus {
76 if err := c.collectSlaveStatus(ctx); err != nil {
77 c.Warningf("error on collecting slave status: %v", err)
78 c.doSlaveStatus = errors.Is(err, context.DeadlineExceeded)
79 }
80 }
81
82 if c.doUserStatistics {
83 if err := c.collectUserStatistics(ctx); err != nil {
84 c.Warningf("error on collecting user statistics: %v", err)
85 c.doUserStatistics = errors.Is(err, context.DeadlineExceeded)
86 }
87 }
88
89 if err := c.collectProcessListStatistics(ctx); err != nil {
90 c.Errorf("error on collecting process list statistics: %v", err)
91 }
92
93 c.mx.set("thread_cache_misses", calcThreadCacheMisses(state.threadsCreated, state.connections))
94 return nil
95 }
96
97 func (c *Collector) check(ctx context.Context) error {
98 if !c.dbReady() {
99 if err := c.openConnection(ctx); err != nil {
100 return err
101 }
102 }
103
104 if err := c.ensureVersionAndCapabilities(ctx); err != nil {
105 return err
106 }
107
108 if err := c.probeGlobalStatus(ctx); err != nil {
109 return fmt.Errorf("error on collecting global status: %v", err)
110 }
111
112 if err := c.collectGlobalVariables(ctx); err != nil {
113 return fmt.Errorf("error on collecting global variables: %v", err)
114 }
115
116 return nil
117 }
118
119 func (c *Collector) openConnection(ctx context.Context) error {
120 if ctx == nil {
121 ctx = context.Background()
122 }
123
124 db, err := sql.Open("mysql", c.DSN)
125 if err != nil {
126 return fmt.Errorf("error on opening a connection with the mysql database [%s]: %v", c.safeDSN, err)
127 }
128
129 db.SetConnMaxLifetime(10 * time.Minute)
130
131 ctx, cancel := context.WithTimeout(ctx, c.Timeout.Duration())
132 defer cancel()
133
134 if err := db.PingContext(ctx); err != nil {
135 _ = db.Close()
136 return fmt.Errorf("error on pinging the mysql database [%s]: %v", c.safeDSN, err)
137 }
138
139 c.setDB(db)
140 return nil
141 }
142
143 func calcThreadCacheMisses(threads, cons int64) int64 {
144 if threads == 0 || cons == 0 {
145 return 0
146 }
147 return int64(float64(threads) / float64(cons) * 10000)
148 }
149
150 func (c *Collector) collectQuery(ctx context.Context, query string, assign func(column, value string, lineEnd bool)) (duration int64, err error) {
151 if ctx == nil {
152 ctx = context.Background()
153 }
154
155 ctx, cancel := context.WithTimeout(ctx, c.Timeout.Duration())
156 defer cancel()
157
158 db, err := c.currentDB()
159 if err != nil {
160 return 0, err
161 }
162
163 queryDuration, err := sqlquery.QueryRows(ctx, db, query, assign)
164 if err != nil {
165 return 0, err
166 }
167 return queryDuration.Milliseconds(), nil
168 }
169
170 func parseInt(s string) int64 {
171 v, _ := strconv.ParseInt(s, 10, 64)
172 return v
173 }
174
175 func parseFloat(s string) float64 {
176 v, _ := strconv.ParseFloat(s, 64)
177 return v
178 }