// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Scrape `mysql.user`. package collector import ( "context" "database/sql" "fmt" "log/slog" "strings" "github.com/alecthomas/kingpin/v2" "github.com/prometheus/client_golang/prometheus" ) // mysqlSubsystem used for metric names. const mysqlSubsystem = "mysql" const mysqlUserQuery = ` SELECT user, host, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, File_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv, Execute_priv, Repl_slave_priv, Repl_client_priv, Create_view_priv, Show_view_priv, Create_routine_priv, Alter_routine_priv, Create_user_priv, Event_priv, Trigger_priv, Create_tablespace_priv, max_questions, max_updates, max_connections, max_user_connections FROM mysql.user ` // Tunable flags. var ( userPrivilegesFlag = kingpin.Flag( "collect.mysql.user.privileges", "Enable collecting user privileges from mysql.user", ).Default("false").Bool() ) var ( labelNames = []string{"mysql_user", "hostmask"} ) // Metric descriptors. var ( userMaxQuestionsDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, mysqlSubsystem, "max_questions"), "The number of max_questions by user.", labelNames, nil) userMaxUpdatesDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, mysqlSubsystem, "max_updates"), "The number of max_updates by user.", labelNames, nil) userMaxConnectionsDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, mysqlSubsystem, "max_connections"), "The number of max_connections by user.", labelNames, nil) userMaxUserConnectionsDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, mysqlSubsystem, "max_user_connections"), "The number of max_user_connections by user.", labelNames, nil) ) // ScrapeUser collects from `information_schema.processlist`. type ScrapeUser struct{} // Name of the Scraper. Should be unique. func (ScrapeUser) Name() string { return mysqlSubsystem + ".user" } // Help describes the role of the Scraper. func (ScrapeUser) Help() string { return "Collect data from mysql.user" } // Version of MySQL from which scraper is available. func (ScrapeUser) Version() float64 { return 5.1 } // Scrape collects data from database connection and sends it over channel as prometheus metric. func (ScrapeUser) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger *slog.Logger) error { db := instance.getDB() var ( userRows *sql.Rows err error ) userQuery := fmt.Sprint(mysqlUserQuery) userRows, err = db.QueryContext(ctx, userQuery) if err != nil { return err } defer userRows.Close() var ( user string host string Select_priv string Insert_priv string Update_priv string Delete_priv string Create_priv string Drop_priv string Reload_priv string Shutdown_priv string Process_priv string File_priv string Grant_priv string References_priv string Index_priv string Alter_priv string Show_db_priv string Super_priv string Create_tmp_table_priv string Lock_tables_priv string Execute_priv string Repl_slave_priv string Repl_client_priv string Create_view_priv string Show_view_priv string Create_routine_priv string Alter_routine_priv string Create_user_priv string Event_priv string Trigger_priv string Create_tablespace_priv string max_questions uint32 max_updates uint32 max_connections uint32 max_user_connections uint32 ) for userRows.Next() { err = userRows.Scan( &user, &host, &Select_priv, &Insert_priv, &Update_priv, &Delete_priv, &Create_priv, &Drop_priv, &Reload_priv, &Shutdown_priv, &Process_priv, &File_priv, &Grant_priv, &References_priv, &Index_priv, &Alter_priv, &Show_db_priv, &Super_priv, &Create_tmp_table_priv, &Lock_tables_priv, &Execute_priv, &Repl_slave_priv, &Repl_client_priv, &Create_view_priv, &Show_view_priv, &Create_routine_priv, &Alter_routine_priv, &Create_user_priv, &Event_priv, &Trigger_priv, &Create_tablespace_priv, &max_questions, &max_updates, &max_connections, &max_user_connections, ) if err != nil { return err } if *userPrivilegesFlag { userCols, err := userRows.Columns() if err != nil { return err } scanArgs := make([]interface{}, len(userCols)) for i := range scanArgs { scanArgs[i] = &sql.RawBytes{} } if err := userRows.Scan(scanArgs...); err != nil { return err } for i, col := range userCols { if value, ok := parsePrivilege(*scanArgs[i].(*sql.RawBytes)); ok { // Silently skip unparsable values. ch <- prometheus.MustNewConstMetric( prometheus.NewDesc( prometheus.BuildFQName(namespace, mysqlSubsystem, strings.ToLower(col)), col+" by user.", labelNames, nil, ), prometheus.GaugeValue, value, user, host, ) } } } ch <- prometheus.MustNewConstMetric(userMaxQuestionsDesc, prometheus.GaugeValue, float64(max_questions), user, host) ch <- prometheus.MustNewConstMetric(userMaxUpdatesDesc, prometheus.GaugeValue, float64(max_updates), user, host) ch <- prometheus.MustNewConstMetric(userMaxConnectionsDesc, prometheus.GaugeValue, float64(max_connections), user, host) ch <- prometheus.MustNewConstMetric(userMaxUserConnectionsDesc, prometheus.GaugeValue, float64(max_user_connections), user, host) } return nil }