Skip to content

Commit 92b8c2c

Browse files
authored
feat(inputs.mysql): Add wsrep provider options fields (#18117)
1 parent 67ee5c0 commit 92b8c2c

File tree

4 files changed

+96
-1
lines changed

4 files changed

+96
-1
lines changed

plugins/inputs/mysql/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ measurement name.
255255
* wsrep_evs_repl_latency_stdev(float, seconds)
256256
* wsrep_evs_repl_latency_sample_size(float, number)
257257
* Global variables - all numeric and boolean values of `SHOW GLOBAL VARIABLES`
258+
* wsrep_provider_options - a complex field containing multiple values is split
259+
into separate fields
260+
* gcache_size(int, bytes)
258261
* Slave status - metrics from `SHOW SLAVE STATUS` the metrics are gathered when
259262
the single-source replication is on. If the multi-source replication is set,
260263
then everything works differently, this metric does not work with multi-source

plugins/inputs/mysql/mysql.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,8 @@ func (m *Mysql) gatherGlobalVariables(db *sql.DB, servtag string, acc telegraf.A
597597
acc.AddError(errString)
598598
}
599599
} else {
600-
fields[key] = value
600+
// v2.ConvertGlobalVariables can parse "complex" multi-value fields, e.g. wsrep_provider_options
601+
parseKeyValues(fields, key, value)
601602
}
602603

603604
// Send 20 fields at a time

plugins/inputs/mysql/v2/convert.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,66 @@ func parseWsrepLatency(value sql.RawBytes) (interface{}, error) {
7777
return result, nil
7878
}
7979

80+
// ParseWsrepProviderOptions parses the given sql.RawBytes value into a map
81+
// containing all wsrep Provider options settings.
82+
func parseWsrepProviderOptions(value sql.RawBytes) (interface{}, error) {
83+
parts := strings.Split(strings.TrimSpace(string(value)), ";")
84+
result := make(map[string]interface{})
85+
for _, setting := range parts {
86+
key, data, found := strings.Cut(setting, "=")
87+
88+
// empty string at the end of the field, skip
89+
if len(setting) != 0 && !found {
90+
return nil, fmt.Errorf("invalid key-value pair for %q", setting)
91+
}
92+
93+
key = strings.TrimSpace(key)
94+
data = strings.TrimSpace(data)
95+
if len(data) == 0 {
96+
continue // empty value, no point in continuing
97+
}
98+
99+
// Only process gcache.size for now
100+
if key != "gcache.size" {
101+
continue
102+
}
103+
key = strings.Replace(key, ".", "_", -1)
104+
105+
// Extract the size suffix for values like 128M
106+
suffix := data[len(data)-1:]
107+
value := data[:len(data)-1]
108+
109+
// Determine the scaling factor from the suffix
110+
var factor uint64
111+
switch strings.ToUpper(suffix) {
112+
case "K":
113+
factor = 1024
114+
case "M":
115+
factor = 1024 * 1024
116+
case "G":
117+
factor = 1024 * 1024 * 1024
118+
case "T":
119+
factor = 1024 * 1024 * 1024 * 1024
120+
case "P":
121+
factor = 1024 * 1024 * 1024 * 1024 * 1024
122+
case "E":
123+
factor = 1024 * 1024 * 1024 * 1024 * 1024 * 1024
124+
default:
125+
// We got no known unit so parse the whole data as value
126+
value = data
127+
factor = 1
128+
}
129+
130+
// Compute the actual value
131+
v, err := strconv.ParseUint(value, 10, 64)
132+
if err != nil {
133+
return nil, fmt.Errorf("failed to parse value %q: %w", data, err)
134+
}
135+
result[key] = v * factor
136+
}
137+
return result, nil
138+
}
139+
80140
// ParseGTIDMode parses the given sql.RawBytes value into an int64
81141
// representing the GTID mode. It returns an error if the value is unrecognized.
82142
func ParseGTIDMode(value sql.RawBytes) (interface{}, error) {
@@ -172,6 +232,9 @@ var globalVariableConversions = map[string]conversionFunc{
172232
// https://dev.mysql.com/doc/refman/5.7/en/replication-options-gtids.html
173233
// https://dev.mysql.com/doc/refman/8.0/en/replication-options-gtids.html
174234
"gtid_mode": ParseGTIDMode,
235+
236+
// https://galeracluster.com/documentation/html_docs_proto-12/documentation/mysql-wsrep-options.html#wsrep-provider-options
237+
"wsrep_provider_options": parseWsrepProviderOptions,
175238
}
176239

177240
// ConvertGlobalStatus converts the given key and sql.RawBytes value into an appropriate type based on globalStatusConversions.

plugins/inputs/mysql/v2/convert_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,34 @@ func TestConvertGlobalVariables(t *testing.T) {
8989
expected: nil,
9090
expectedErr: nil,
9191
},
92+
{
93+
name: "multiple values in one metric converted to a map",
94+
key: "wsrep_provider_options",
95+
value: []byte(
96+
"allocator.disk_pages_encryption = no; allocator.encryption_cache_page_size = 32K; allocator.encryption_cache_size = 16777216; " +
97+
"base_dir = /var/lib/mysql; base_host = 192.168.1.1; base_port = 4567; cert.log_conflicts = no; cert.optimistic_pa = NO; debug = no; " +
98+
"evs.auto_evict = 0; evs.causal_keepalive_period = PT1S; evs.debug_log_mask = 0x1; evs.delay_margin = PT1S; " +
99+
"evs.delayed_keep_period = PT30S; evs.inactive_check_period = PT0.5S; evs.inactive_timeout = PT15S; evs.info_log_mask = 0; " +
100+
"evs.install_timeout = PT7.5S; evs.join_retrans_period = PT1S; evs.keepalive_period = PT1S; evs.max_install_timeouts = 3; " +
101+
"evs.send_window = 10; evs.stats_report_period = PT1M; evs.suspect_timeout = PT5S; evs.use_aggregate = true; evs.user_send_window = 4; " +
102+
"evs.version = 1; evs.view_forget_timeout = P1D; gcache.dir = /var/lib/mysql; gcache.encryption = no; " +
103+
"gcache.encryption_cache_page_size = 32K; gcache.encryption_cache_size = 16777216; gcache.freeze_purge_at_seqno = -1; " +
104+
"gcache.keep_pages_count = 0; gcache.keep_pages_size = 0; gcache.mem_size = 0; gcache.name = galera.cache; gcache.page_size = 128M; " +
105+
"gcache.recover = yes; gcache.size = 128M; gcomm.thread_prio = ; gcs.fc_auto_evict_threshold = 0.75; gcs.fc_auto_evict_window = 0; " +
106+
"gcs.fc_debug = 0; gcs.fc_factor = 1.0; gcs.fc_limit = 100; gcs.fc_master_slave = no; gcs.fc_single_primary = no; " +
107+
"gcs.max_packet_size = 64500; gcs.max_throttle = 0.25; gcs.recv_q_hard_limit = 9223372036854775807; gcs.recv_q_soft_limit = 0.25; " +
108+
"gcs.sync_donor = no; gmcast.listen_addr = tcp://0.0.0.0:4567; gmcast.mcast_addr = ; gmcast.mcast_ttl = 1; gmcast.peer_timeout = PT3S; " +
109+
"gmcast.segment = 0; gmcast.time_wait = PT5S; gmcast.version = 0; ist.recv_addr = 192.168.1.1; pc.announce_timeout = PT3S; " +
110+
"pc.checksum = false; pc.ignore_quorum = false; pc.ignore_sb = false; pc.linger = PT20S; pc.npvo = false; pc.recovery = true; " +
111+
"pc.version = 0; pc.wait_prim = true; pc.wait_prim_timeout = PT30S; pc.wait_restored_prim_timeout = PT0S; pc.weight = 1; " +
112+
"protonet.backend = asio; protonet.version = 0; repl.causal_read_timeout = PT30S; repl.commit_order = 3; repl.key_format = FLAT8; " +
113+
"repl.max_ws_size = 2147483647; repl.proto_max = 11; socket.checksum = 2; socket.recv_buf_size = auto; socket.send_buf_size = auto; ",
114+
),
115+
expected: map[string]interface{}{
116+
"gcache_size": uint64(134217728),
117+
},
118+
expectedErr: nil,
119+
},
92120
}
93121
for _, tt := range tests {
94122
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)