diff --git a/plugins/mysql/mysql.go b/plugins/mysql/mysql.go new file mode 100644 index 000000000..44d28b715 --- /dev/null +++ b/plugins/mysql/mysql.go @@ -0,0 +1,134 @@ +package mysql + +import ( + "database/sql" + "strconv" + "strings" + + _ "github.com/go-sql-driver/mysql" + "github.com/influxdb/tivan/plugins" +) + +type Server struct { + Address string +} + +type Mysql struct { + Disabled bool + Servers []*Server +} + +var localhost = &Server{} + +func (m *Mysql) Gather(acc plugins.Accumulator) error { + if m.Disabled { + return nil + } + + if len(m.Servers) == 0 { + // if we can't get stats in this case, thats fine, don't report + // an error. + m.gatherServer(localhost, acc) + return nil + } + + for _, serv := range m.Servers { + err := m.gatherServer(serv, acc) + if err != nil { + return err + } + } + + return nil +} + +type mapping struct { + onServer string + inExport string +} + +var mappings = []*mapping{ + { + onServer: "Bytes_", + inExport: "mysql_bytes_", + }, + { + onServer: "Com_", + inExport: "mysql_commands_", + }, + { + onServer: "Handler_", + inExport: "mysql_handler_", + }, + { + onServer: "Innodb_", + inExport: "mysql_innodb_", + }, + { + onServer: "Threads_", + inExport: "mysql_threads_", + }, +} + +func (m *Mysql) gatherServer(serv *Server, acc plugins.Accumulator) error { + db, err := sql.Open("mysql", serv.Address) + if err != nil { + return err + } + + defer db.Close() + + rows, err := db.Query(`SHOW /*!50002 GLOBAL */ STATUS`) + if err != nil { + return nil + } + + for rows.Next() { + var name string + var val interface{} + + err = rows.Scan(&name, &val) + if err != nil { + return err + } + + var found bool + + for _, mapped := range mappings { + if strings.HasPrefix(name, mapped.onServer) { + i, _ := strconv.Atoi(string(val.([]byte))) + acc.Add(mapped.inExport+name[len(mapped.onServer):], i, nil) + found = true + } + } + + if found { + continue + } + + switch name { + case "Queries": + i, err := strconv.ParseInt(string(val.([]byte)), 10, 64) + if err != nil { + return err + } + + acc.Add("mysql_queries", i, nil) + case "Slow_queries": + i, err := strconv.ParseInt(string(val.([]byte)), 10, 64) + if err != nil { + return err + } + + acc.Add("mysql_slow_queries", i, nil) + } + } + + return nil +} + +func init() { + plugins.Add("mysql", func() plugins.Plugin { + return &Mysql{} + }) +} diff --git a/plugins/mysql/mysql_test.go b/plugins/mysql/mysql_test.go new file mode 100644 index 000000000..63c25b543 --- /dev/null +++ b/plugins/mysql/mysql_test.go @@ -0,0 +1,68 @@ +package mysql + +import ( + "strings" + "testing" + + "github.com/influxdb/tivan/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMysqlGeneratesMetrics(t *testing.T) { + m := &Mysql{ + Servers: []*Server{ + { + Address: "", + }, + }, + } + + var acc testutil.Accumulator + + err := m.Gather(&acc) + require.NoError(t, err) + + prefixes := []struct { + prefix string + count int + }{ + {"mysql_commands", 141}, + {"mysql_handler", 18}, + {"mysql_bytes", 2}, + {"mysql_innodb", 51}, + {"mysql_threads", 4}, + } + + intMetrics := []string{ + "mysql_queries", + "mysql_slow_queries", + } + + for _, prefix := range prefixes { + var count int + + for _, p := range acc.Points { + if strings.HasPrefix(p.Name, prefix.prefix) { + count++ + } + } + + assert.Equal(t, prefix.count, count) + } + + for _, metric := range intMetrics { + assert.True(t, acc.HasIntValue(metric)) + } +} + +func TestMysqlDefaultsToLocal(t *testing.T) { + m := &Mysql{} + + var acc testutil.Accumulator + + err := m.Gather(&acc) + require.NoError(t, err) + + assert.True(t, len(acc.Points) > 0) +}