#!/bin/bash ################################################################################ # Script Name: mysql-exporter.sh # Version: 1.0 # Description: Prometheus textfile exporter for MySQL — server status, # connections, QPS, slow queries, buffer pool, replication, # thread counts, and table lock waits via mysql CLI # # Author: Phil Connor # Contact: contact@mylinux.work # Website: https://mylinux.work # License: MIT # # Prerequisites: # - mysql CLI client # - Credentials via ~/.my.cnf or --user/--password flags # - Network access to MySQL server # - netcat (nc) for HTTP mode # # Usage: # # Output to stdout # ./mysql-exporter.sh # # # HTTP server mode # ./mysql-exporter.sh --http -p 9207 # # # Textfile collector mode # ./mysql-exporter.sh --textfile # # Metrics Exported: # - mysql_overview_up - Exporter status (1=up, 0=down) # - mysql_overview_version_info{version} - Server version # - mysql_overview_uptime_seconds - Server uptime # - mysql_overview_active_connections - Current active connections # - mysql_overview_max_connections - Maximum allowed connections # - mysql_overview_aborted_connections_total - Aborted connections # - mysql_overview_aborted_clients_total - Aborted clients # - mysql_overview_queries_total - Total queries executed # - mysql_overview_slow_queries_total - Total slow queries # - mysql_overview_tmp_disk_tables_total - Temp tables on disk # - mysql_overview_threads_running - Running threads # - mysql_overview_threads_cached - Cached threads # - mysql_overview_threads_connected - Connected threads # - mysql_overview_buffer_pool_size_bytes - Buffer pool total size # - mysql_overview_buffer_pool_used_bytes - Buffer pool used size # - mysql_overview_buffer_pool_hit_rate - Buffer pool hit rate # - mysql_overview_table_lock_waits_total - Table lock waits # - mysql_overview_slave_running - Replication running status # - mysql_overview_seconds_behind_master - Replication lag # - mysql_overview_relay_log_space_bytes - Relay log space # - mysql_overview_binlog_size_bytes - Binary log size # - mysql_overview_exporter_duration_seconds - Script execution time # - mysql_overview_exporter_last_run_timestamp - Last successful run # # Configuration: # Default HTTP port: 9207 # Textfile directory: /var/lib/node_exporter # ################################################################################ # ============================================================================ # CONFIGURATION VARIABLES # ============================================================================ TEXTFILE_DIR="/var/lib/node_exporter" OUTPUT_FILE="" HTTP_MODE=false HTTP_PORT=9207 MYSQL_USER="" MYSQL_PASSWORD="" MYSQL_HOST="127.0.0.1" MYSQL_PORT="3306" # ============================================================================ # HELPER FUNCTIONS # ============================================================================ show_usage() { cat <&2; exit 1 ;; esac done } # Build mysql command with optional credentials build_mysql_cmd() { local cmd="mysql -h $MYSQL_HOST -P $MYSQL_PORT" [ -n "$MYSQL_USER" ] && cmd="$cmd -u $MYSQL_USER" [ -n "$MYSQL_PASSWORD" ] && cmd="$cmd -p$MYSQL_PASSWORD" cmd="$cmd --batch --skip-column-names" echo "$cmd" } # Check if mysql client is available and server is reachable check_mysql() { if ! command -v mysql >/dev/null 2>&1; then echo "ERROR: mysql client not found" >&2 return 1 fi local mysql_cmd mysql_cmd=$(build_mysql_cmd) if ! $mysql_cmd -e "SELECT 1" >/dev/null 2>&1; then echo "ERROR: Cannot connect to MySQL server" >&2 return 1 fi return 0 } # Execute a MySQL query and return the result mysql_query() { local query="$1" local mysql_cmd mysql_cmd=$(build_mysql_cmd) $mysql_cmd -e "$query" 2>/dev/null } # Get a specific global status variable get_status_var() { local var_name="$1" mysql_query "SHOW GLOBAL STATUS LIKE '$var_name'" | awk '{print $2}' } # Get a specific global variable get_variable() { local var_name="$1" mysql_query "SHOW GLOBAL VARIABLES LIKE '$var_name'" | awk '{print $2}' } # ============================================================================ # METRIC GENERATION # ============================================================================ generate_metrics() { local script_start script_start=$(date +%s) if ! check_mysql; then cat </dev/null) uptime=$(get_status_var "Uptime") max_conn=$(get_variable "max_connections") active_conn=$(get_status_var "Threads_connected") local queries slow_queries tmp_disk_tables queries=$(get_status_var "Queries") slow_queries=$(get_status_var "Slow_queries") tmp_disk_tables=$(get_status_var "Created_tmp_disk_tables") local threads_running threads_cached threads_connected threads_running=$(get_status_var "Threads_running") threads_cached=$(get_status_var "Threads_cached") threads_connected=$(get_status_var "Threads_connected") local bp_size bp_pages_total bp_pages_free bp_read_requests bp_reads bp_size=$(get_variable "innodb_buffer_pool_size") bp_pages_total=$(get_status_var "Innodb_buffer_pool_pages_total") bp_pages_free=$(get_status_var "Innodb_buffer_pool_pages_free") bp_read_requests=$(get_status_var "Innodb_buffer_pool_read_requests") bp_reads=$(get_status_var "Innodb_buffer_pool_reads") local table_lock_waits aborted_conn aborted_clients table_lock_waits=$(get_status_var "Table_locks_waited") aborted_conn=$(get_status_var "Aborted_connects") aborted_clients=$(get_status_var "Aborted_clients") # Calculate buffer pool metrics local bp_used_pages bp_used_bytes bp_hit_rate bp_pages_total=${bp_pages_total:-0} bp_pages_free=${bp_pages_free:-0} bp_used_pages=$((bp_pages_total - bp_pages_free)) # InnoDB page size is 16384 bytes by default bp_used_bytes=$((bp_used_pages * 16384)) bp_read_requests=${bp_read_requests:-0} bp_reads=${bp_reads:-0} if [ "$bp_read_requests" -gt 0 ] 2>/dev/null; then bp_hit_rate=$(awk "BEGIN {printf \"%.6f\", 1 - ($bp_reads / $bp_read_requests)}" 2>/dev/null || echo "0") else bp_hit_rate="1.000000" fi # Replication status local slave_running seconds_behind relay_log_space local repl_output repl_output=$(mysql_query "SHOW SLAVE STATUS\G" 2>/dev/null) if [ -n "$repl_output" ]; then slave_running=$(echo "$repl_output" | grep "Slave_SQL_Running:" | awk '{print $2}') [ "$slave_running" = "Yes" ] && slave_running=1 || slave_running=0 seconds_behind=$(echo "$repl_output" | grep "Seconds_Behind_Master:" | awk '{print $2}') [ "$seconds_behind" = "NULL" ] && seconds_behind=-1 relay_log_space=$(echo "$repl_output" | grep "Relay_Log_Space:" | awk '{print $2}') else slave_running=0 seconds_behind=-1 relay_log_space=0 fi # Binary log size local binlog_size binlog_size=$(mysql_query "SHOW BINARY LOGS" 2>/dev/null | awk '{sum += $2} END {print sum+0}') # Output all metrics cat <&2 if ! command -v nc >/dev/null 2>&1; then echo "ERROR: netcat (nc) required for HTTP mode" >&2 exit 1 fi while true; do { read -r request if [[ "$request" =~ ^GET\ /metrics ]]; then echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\n\r" generate_metrics else echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r" cat < MySQL Exporter

MySQL Prometheus Exporter

Metrics

EOF fi } | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null done } # ============================================================================ # MAIN EXECUTION # ============================================================================ main() { parse_args "$@" if [ "$HTTP_MODE" = true ]; then run_http_server elif [ -n "$OUTPUT_FILE" ]; then local output_dir output_dir="$(dirname "$OUTPUT_FILE")" mkdir -p "$output_dir" local temp_file temp_file=$(mktemp "${output_dir}/.mysql_metrics.XXXXXX") if ! generate_metrics > "$temp_file" 2>/dev/null; then rm -f "$temp_file" echo "ERROR: Failed to generate metrics" >&2 exit 1 fi local file_lines file_lines=$(wc -l < "$temp_file" 2>/dev/null || echo 0) if [ "$file_lines" -lt 10 ]; then rm -f "$temp_file" echo "ERROR: Metrics file too small ($file_lines lines), keeping previous" >&2 exit 1 fi chmod 644 "$temp_file" mv -f "$temp_file" "$OUTPUT_FILE" echo "Metrics written to $OUTPUT_FILE ($file_lines lines)" >&2 else generate_metrics fi } main "$@"