package mysql import ( "context" "database/sql" "strconv" "time" _ "github.com/go-sql-driver/mysql" "bugs.watch/agent/internal/aggregate" ) // Collector queries MySQL global status each sample interval. // All queries run under a 3-second timeout; any failure is silently skipped. type Collector struct { db *sql.DB prevSlowQueries uint64 hasPrev bool } func New(dsn string) (*Collector, error) { db, err := sql.Open("mysql", dsn) if err != nil { return nil, err } db.SetMaxOpenConns(2) db.SetMaxIdleConns(1) db.SetConnMaxLifetime(5 * time.Minute) return &Collector{db: db}, nil } func (c *Collector) Name() string { return "mysql" } func (c *Collector) Close() { c.db.Close() } func (c *Collector) Collect(window *aggregate.Window) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() rows, err := c.db.QueryContext(ctx, "SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected','Threads_running','Slow_queries')", ) if err != nil { return } defer rows.Close() vars := make(map[string]uint64, 3) for rows.Next() { var name, raw string if err := rows.Scan(&name, &raw); err != nil { continue } if v, err := strconv.ParseUint(raw, 10, 64); err == nil { vars[name] = v } } if err := rows.Err(); err != nil { return } var maxConn uint64 if err := c.db.QueryRowContext(ctx, "SELECT @@max_connections").Scan(&maxConn); err != nil || maxConn == 0 { maxConn = 1 } connPct := float64(vars["Threads_connected"]) / float64(maxConn) * 100 window.AddDBConnPct(connPct) window.AddDBThreadsRunning(float64(vars["Threads_running"])) slow := vars["Slow_queries"] if c.hasPrev { var delta uint64 if slow >= c.prevSlowQueries { delta = slow - c.prevSlowQueries } window.AddDBSlowQueriesDelta(delta) } c.prevSlowQueries = slow c.hasPrev = true var sizeBytes uint64 err = c.db.QueryRowContext(ctx, "SELECT COALESCE(SUM(data_length + index_length), 0)"+ " FROM information_schema.tables"+ " WHERE table_schema NOT IN ('information_schema','performance_schema','mysql','sys')", ).Scan(&sizeBytes) if err == nil { window.SetDBSizeBytes(sizeBytes) } }