MySQL / MariaDB¶
Monitors MySQL and MariaDB instances — self-hosted, AWS RDS, Aurora,
Cloud SQL, Azure Database, Supabase. Collects health, connections,
throughput, replication, buffer pool, locks, IO and storage from
SHOW GLOBAL STATUS, SHOW GLOBAL VARIABLES, SHOW REPLICA
STATUS and information_schema.
License: Free
Prerequisites¶
- MySQL 5.7+ or MariaDB 10.3+
- Network access from the agent host to the database (default port
3306) - A monitoring user with the right grants — see GRANTs below
Configuration¶
# probes.d/20-mysql.yaml — each file under probes.d/ is a YAML array of probes
- name: production-mysql
type: mysql
params:
host: db.example.com
port: 3306
username: senhub_monitor
password: ${secret:production-mysql.password} # OS secret store; inline plaintext is auto-sealed on install
interval: 60
timeout: 10
tls: true
per_database: false
Parameters¶
| Parameter | Must set | Default | Description |
|---|---|---|---|
host |
In practice | 127.0.0.1 |
Server hostname or address |
port |
In practice | 3306 |
Server port |
username |
In practice | - | Monitoring user |
password |
In practice | - | Monitoring user's password. A secret: reference it with ${secret:…}, ${env:…} or ${file:…} rather than writing it in the file |
database |
No | - | Database the connection opens on; optional |
tls |
No | - | TLS settings, or simply true |
tls.enabled |
No | false |
Use TLS |
tls.skip_verify |
No | false |
Accept the server certificate without verifying it. Also accepted: insecure_skip_verify |
tls.ca_file |
No | - | CA certificate (PEM) the server is verified against. Also accepted: ca_cert |
timeout |
No | 10s |
Query timeout, seconds or a duration |
interval |
No | 60 |
Seconds between collections |
max_replication_lag_seconds |
No | 300s |
Lag past which a replica counts as unhealthy; 0 turns the lag term off |
per_database |
No | false |
Emit per-database metrics. Also accepted: expose_per_database |
per_table |
No | false |
Emit per-table metrics for the largest tables; needs per_database |
top_n_tables |
No | 20 |
How many tables per_table covers. Also accepted: expose_top_tables |
instance_name |
No | - | Stable identity override for this server |
per_database scales the series count with the number of databases;
per_table adds the largest tables on top of it. A replica whose two
threads are both running still counts as unhealthy once its lag passes
max_replication_lag_seconds; set it to 0 for a deliberately delayed
replica.
TLS¶
tls: true verifies the server's certificate against the system roots.
For a database behind an internal authority, or a lab with a self-signed
certificate, write a block instead:
tls:
ca_file: /etc/ssl/db-ca.pem # verify against this authority
skip_verify: false # true accepts any certificate — labs only
Naming a ca_file, or asking to skip verification, turns TLS on: there
is no reading of "configure how TLS behaves, then don't use it". A
ca_file the agent cannot read, or that holds no certificate, stops the
probe with that reason — it never falls back to the system roots, which
would verify against the wrong authority and look like it worked.
Renamed parameters¶
Two names changed when this probe replaced the paid one. Both still
work, and agent config check names the current spelling:
| Old | Now |
|---|---|
expose_per_database: true |
per_database: true |
expose_top_tables: 25 |
per_table: true + top_n_tables: 25 |
GRANTs¶
The monitoring user needs read access to engine status, replication
state, and information_schema. Use the agent's helper command to
print the exact GRANT block for your version:
The helper does not connect to the database. Copy the SQL it prints, review it, paste it into a DBA shell.
For MySQL 8.0:
CREATE USER 'senhub_monitor'@'%' IDENTIFIED BY 'STRONG-PASSWORD-HERE';
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'senhub_monitor'@'%';
FLUSH PRIVILEGES;
For MariaDB 10.5.8+ add SLAVE MONITOR ON *.*.
Collected Metrics¶
Every metric is tagged with metric_type so the PRTG Sensor
Builder splits them into family chips. The categories below match
those chips.
Every datapoint also carries the OTel resource attributes
db.system.name (mysql), server.address, and server.port.
Overview¶
The 6 metrics every dashboard wants at a glance.
| Metric | Unit | Description |
|---|---|---|
senhub.db.up |
bool | 1 = last ping reached the server |
mysql.uptime |
s | Engine uptime |
senhub.db.version.info |
– | Always 1; version carried as label |
senhub.db.connections.utilization |
ratio | (active+idle) / max_connections |
senhub.db.replication.role |
enum | 0=standalone, 1=primary, 2=replica |
senhub.db.replication.health |
bool | Composite: io+sql threads running AND lag below threshold |
Connections¶
| Metric | Unit | Description |
|---|---|---|
mysql.threads{kind=running} |
count | Sessions running a query |
senhub.db.connection.idle |
count | Connected but doing nothing |
senhub.db.connections.max |
count | Engine cap |
mysql.connection.errors{error=aborted_clients} |
counter | Client-side connection drops |
mysql.connection.errors{error=aborted_connects} |
counter | Auth failures + protocol errors |
mysql.connection.errors{error=max_connections} |
counter | Out-of-slots events |
Throughput¶
| Metric | Unit | Description |
|---|---|---|
mysql.query.count |
counter | Total statements (rate = QPS) |
senhub.db.mysql.transaction.count{state=committed} |
counter | Successful commits |
senhub.db.mysql.transaction.count{state=rolled_back} |
counter | Rollbacks (app errors / deadlocks) |
senhub.db.mysql.slow_queries.count |
counter | Queries above long_query_time |
mysql.commands{command=…} |
counter | Per-verb rate (select/insert/update/delete/replace) |
senhub.db.mysql.tmp_tables.disk.ratio |
ratio | Disk temp / total temp — >25% suggests tuning |
Replication¶
Emitted only when role is primary or replica. The probe auto-detects
role via SHOW REPLICA STATUS — no per-host configuration needed.
| Metric | Unit | Description |
|---|---|---|
senhub.db.replication.lag.seconds |
s | Replica Seconds_Behind_Source |
senhub.db.replication.io_running |
bool | 1 = IO thread Yes |
senhub.db.replication.sql_running |
bool | 1 = SQL thread Yes |
senhub.db.replication.replicas.connected |
count | Downstream replicas (primary side) |
Cache¶
| Metric | Unit | Description |
|---|---|---|
senhub.db.mysql.buffer_pool.hit_ratio |
ratio | InnoDB buffer pool hit ratio — <99% under-provisioned |
senhub.db.buffer.utilization |
ratio | Pages data / pages total |
mysql.buffer_pool.data_pages{status=dirty} |
count | Pressure on the checkpointer |
Locks¶
| Metric | Unit | Description |
|---|---|---|
senhub.db.mysql.lock.deadlocks |
counter | App-level deadlocks — should be near zero |
senhub.db.locks.waiting |
count | Sessions currently blocked |
senhub.db.locks.row_lock_time.avg |
s | Average InnoDB row lock wait (was ms in ≤0.1.91) |
IO¶
| Metric | Unit | Description |
|---|---|---|
senhub.db.mysql.io{io.direction=read} |
counter (bytes) | InnoDB engine-side reads |
senhub.db.mysql.io{io.direction=write} |
counter (bytes) | InnoDB engine-side writes |
Storage¶
| Metric | Unit | Description |
|---|---|---|
senhub.db.database.size |
bytes | Total user-schema size |
senhub.db.mysql.table.count |
count | User tables across non-system schemas |
Output formats¶
The probe emits the catalog above; each sink picks the metrics it
wants via the metric_type tag.
- PRTG / Sensor URLs tab — pick chips per family. A typical setup uses one "MySQL Overview" sensor (6 channels) and a "MySQL Replication" sensor (4 channels) per instance.
- Nagios —
/api/{key}/nagios/metrics/{probe-name}?tags=metric_type:overview - Prometheus — all metrics exposed under
senhub_db_mysql_*andmysql_*names at/api/{key}/prometheus/metrics. - OTLP — pushed as
senhub.db.mysql.*andmysql.*to any OTel receiver.
Auto-detected environments¶
The probe sets the environment tag from @@version_comment /
version():
self_hosted(default)rds,auroracloudsqlazure_flexiblesupabase
OS-level metrics that managed databases do not expose (e.g. data directory mtime) are dropped silently.
Cloud-managed instances¶
Tested against:
- AWS RDS for MySQL (8.0), Aurora MySQL — works as-is
- GCP Cloud SQL for MySQL — works as-is
- Azure Database for MySQL Flexible Server — works as-is
PlanetScale is out of scope for v1 (Vitess hides replication behind the control plane).
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
senhub.db.up = 0 |
Network / firewall | Verify connectivity from the agent to host:port |
senhub.db.replication.* all missing on a primary |
role=standalone detected because no replica connected |
Expected — replication family is replica-side |
| All metrics missing | GRANTs incomplete | Re-run the db-monitoring init helper |
| Empty per-table data | expose_top_tables: 0 |
Set expose_top_tables: 20 (or another N) and reload |
Metric reference¶
Every metric this probe can emit. Metric is the OpenTelemetry name the
OTLP, Prometheus and Zabbix outputs derive theirs from. Name is what a
Nagios check and the API metrics= filter match.
PRTG channel is the label PRTG shows, placeholders filled from the
series' tags.
| Metric | Name | PRTG channel | Unit | Description |
|---|---|---|---|---|
senhub.db.up |
senhub.db.up |
Database Up | # | 1 if the agent's most recent ping reached the server, 0 otherwise |
mysql.uptime |
mysql.uptime |
Uptime | s | Seconds since the engine started (SHOW GLOBAL STATUS LIKE 'Uptime') |
senhub.db.version.info |
senhub.db.version.info |
Version | # | Engine version banner — value=1, version string carried in attribute db.system.version |
mysql.threads |
mysql.threads.connected |
Threads Connected | # | Currently open client sessions (Threads_connected) |
mysql.threads |
mysql.threads.running |
Threads Running | # | Threads not sleeping — includes InnoDB background threads on MariaDB (Threads_running) |
senhub.db.connection.idle |
senhub.db.connection.idle |
Connections Idle | # | Idle client sessions — clamped max(0, Threads_connected − Threads_running) |
senhub.db.mysql.connection.max |
senhub.db.mysql.connection.max |
Connections Max | # | max_connections variable (instantaneous cap) |
senhub.db.connection.utilization |
senhub.db.connection.utilization |
Connections Used % | % | Threads_connected / max_connections, percent (0-100) |
mysql.connection.errors |
mysql.connection.errors.aborted_clients |
Aborted Clients | # | Connections aborted because the client died without closing properly (Aborted_clients) |
mysql.connection.errors |
mysql.connection.errors.aborted_connects |
Aborted Connects | # | Failed connection attempts — auth or protocol errors (Aborted_connects) |
mysql.connection.errors |
mysql.connection.errors.max_connections |
Connections Refused (Cap) | # | Connections rejected due to max_connections cap (Connection_errors_max_connections) |
mysql.query.count |
mysql.query.count |
Queries | # | Statements executed (Questions counter) |
mysql.query.slow.count |
mysql.query.slow.count |
Slow Queries | # | Queries exceeding long_query_time (Slow_queries counter) |
mysql.commands |
mysql.commands |
Commands ({command}) | # | Statements executed per verb (Com_select, Com_insert, Com_update, Com_delete, Com_replace) |
senhub.db.mysql.transaction.count |
senhub.db.mysql.transaction.count.committed |
Transactions Committed | # | Commits cumulatif (Com_commit). MySQL contrib n'expose pas les transactions — extension SenHub homogène avec mysql.commands. |
senhub.db.mysql.transaction.count |
senhub.db.mysql.transaction.count.rolled_back |
Transactions Rolled Back | # | Rollbacks cumulatif (Com_rollback). |
senhub.db.mysql.tmp_tables.disk_ratio |
senhub.db.mysql.tmp_tables.disk_ratio |
Tmp Tables to Disk % | % | Created_tmp_disk_tables / Created_tmp_tables — Created_tmp_tables already includes the disk-spilled subset on MySQL 8.0 and MariaDB; > 25% suggests increasing tmp_table_size. |
senhub.db.mysql.buffer_pool.hit_ratio |
senhub.db.mysql.buffer_pool.hit_ratio |
Buffer Hit Ratio | % | 1 − (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests). Derived ratio — contrib expose les compteurs bruts. |
senhub.db.mysql.buffer_pool.utilization |
senhub.db.mysql.buffer_pool.utilization |
Buffer Utilization | % | Innodb_buffer_pool_pages_data / Innodb_buffer_pool_pages_total |
mysql.buffer_pool.data_pages |
mysql.buffer_pool.data_pages.dirty |
Buffer Dirty Pages | # | InnoDB pages waiting to be flushed to disk (Innodb_buffer_pool_pages_dirty) |
senhub.db.mysql.lock.deadlocks |
senhub.db.mysql.lock.deadlocks |
Deadlocks | # | Cumulative deadlocks detected (Innodb_deadlocks) — silently absent on MariaDB (variable not exposed) |
senhub.db.mysql.lock.waiting |
senhub.db.mysql.lock.waiting |
Locks Waiting | # | Instantaneous count of transactions waiting on a row lock (Innodb_row_lock_current_waits) |
senhub.db.mysql.row_lock.time.avg |
senhub.db.mysql.row_lock.time.avg |
Row Lock Time Avg | ms | Average row lock wait time. Source Innodb_row_lock_time_avg in ms — converted to seconds for OTel (mapper ÷ 1000). |
senhub.db.mysql.io |
senhub.db.mysql.io.read |
IO Read | B | Bytes read by InnoDB (Innodb_data_read) |
senhub.db.mysql.io |
senhub.db.mysql.io.write |
IO Write | B | Bytes written by InnoDB (Innodb_data_written) |
senhub.db.mysql.data.size |
senhub.db.database.size |
Database Size | B | Total size of all user databases (information_schema.tables sum) |
senhub.db.mysql.table.count |
senhub.db.mysql.table.count |
Tables Count | # | Number of user tables across all databases (excludes information_schema, performance_schema, mysql, sys) |
senhub.db.replication.role |
senhub.db.replication.role |
Replication Role | # | Detected replication role of this instance |
senhub.db.replication.health |
senhub.db.replication.health |
Replication Health | # | Composite gauge: 1 if replication looks healthy (or instance is standalone), 0 if degraded |
senhub.db.replication.replicas.connected |
senhub.db.replication.replicas.connected |
Replicas Connected | # | On primary: number of replicas currently connected (SHOW REPLICAS / pg_stat_replication count) |
mysql.replica.time_behind_source |
mysql.replica.time_behind_source |
Replication Lag | s | On replica: seconds behind source (Seconds_Behind_Master / Seconds_Behind_Source) |
senhub.db.mysql.replica.io_thread.running |
senhub.db.mysql.replica.io_thread.running |
Replica IO Thread Running | # | On replica: 1 if Slave_IO_Running='Yes', 0 otherwise |
senhub.db.mysql.replica.sql_thread.running |
senhub.db.mysql.replica.sql_thread.running |
Replica SQL Thread Running | # | On replica: 1 if Slave_SQL_Running='Yes', 0 otherwise |
senhub.db.database.size |
senhub.db.database.size.per_database |
Database Size ({database}) | B | Size of an individual database (opt-in via per_database flag) |
senhub.db.mysql.table.size |
senhub.db.mysql.table.size |
Table Size ({database}.{table}) | B | Size of an individual table (opt-in via per_table flag) |