Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions build/configuration.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"log_level": "INFO",
"common_discovery": {
"collection_frequency": {
"seconds": 10800
}
"collection_frequency": "10800s"
}
}
13 changes: 9 additions & 4 deletions internal/daemon/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ import (
"github.com/GoogleCloudPlatform/workloadagent/internal/mysqldiscovery"
"github.com/GoogleCloudPlatform/workloadagent/internal/mysqlmetrics"
"github.com/GoogleCloudPlatform/workloadagent/internal/usagemetrics"
cpb "github.com/GoogleCloudPlatform/workloadagent/protos/configuration"
configpb "github.com/GoogleCloudPlatform/workloadagent/protos/configuration"
)

// Service implements the interfaces for MySQL workload agent service.
type Service struct {
Config *cpb.Configuration
CloudProps *cpb.CloudProperties
Config *configpb.Configuration
CloudProps *configpb.CloudProperties
CommonCh chan commondiscovery.Result
processes commondiscovery.Result
mySQLProcesses []commondiscovery.ProcessWrapper
Expand Down Expand Up @@ -129,8 +129,13 @@ func runMetricCollection(ctx context.Context, a any) {
log.CtxLogger(ctx).Debugw("MySQL metric collection args", "args", args)
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
m, err := mysqlmetrics.New(ctx, args.s.Config)
if err != nil {
log.CtxLogger(ctx).Errorf("failed to create MySQL metrics: %v", err)
return
}
for {
mysqlmetrics.CollectMetricsOnce(ctx)
m.CollectMetricsOnce(ctx)
select {
case <-ctx.Done():
log.CtxLogger(ctx).Info("MySQL metric collection cancellation requested")
Expand Down
127 changes: 124 additions & 3 deletions internal/mysqlmetrics/mysqlmetrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,133 @@ package mysqlmetrics

import (
"context"
"fmt"
"strconv"
"strings"

"github.com/GoogleCloudPlatform/sapagent/shared/commandlineexecutor"
"github.com/GoogleCloudPlatform/sapagent/shared/gce"
"github.com/GoogleCloudPlatform/sapagent/shared/log"
"github.com/GoogleCloudPlatform/workloadagent/internal/secret"
"github.com/GoogleCloudPlatform/workloadagent/internal/usagemetrics"
configpb "github.com/GoogleCloudPlatform/workloadagent/protos/configuration"
)

type gceInterface interface {
GetSecret(ctx context.Context, projectID, secretName string) (string, error)
}

// MySQLMetrics contains variables and methods to collect metrics for MySQL databases running on the current host.
type MySQLMetrics struct {
Execute commandlineexecutor.Execute
Config *configpb.Configuration
GCEService gceInterface
password secret.String
}

// initPassword initializes the password for the MySQL database.
// If the password is set in the configuration, it is used directly (not recommended).
// Otherwise, if the secret configuration is set, the secret is fetched from GCE.
// Without either, the password is not set and requests to the MySQL database will fail.
func (m *MySQLMetrics) initPassword(ctx context.Context) error {
pw := ""
if m.Config.GetMysqlConfiguration().GetPassword() != "" {
m.password = secret.String(m.Config.GetMysqlConfiguration().GetPassword())
return nil
}
secretCfg := m.Config.GetMysqlConfiguration().GetSecret()
if secretCfg.GetSecretName() != "" && secretCfg.GetProjectId() != "" {
var err error
pw, err = m.GCEService.GetSecret(ctx, secretCfg.GetProjectId(), secretCfg.GetSecretName())
if err != nil {
return fmt.Errorf("failed to get secret: %v", err)
}
}
m.password = secret.String(pw)
return nil
}

// New creates a new MySQLMetrics object with default values.
func New(ctx context.Context, config *configpb.Configuration) (*MySQLMetrics, error) {
gceService, err := gce.NewGCEClient(ctx)
if err != nil {
usagemetrics.Error(usagemetrics.GCEServiceCreationFailure)
return nil, fmt.Errorf("initializing GCE services: %w", err)
}
m := &MySQLMetrics{
Execute: commandlineexecutor.ExecuteCommand,
Config: config,
GCEService: gceService,
}
err = m.initPassword(ctx)
if err != nil {
return nil, fmt.Errorf("initializing password: %w", err)
}
return m, nil
}

func (m *MySQLMetrics) bufferPoolSize(ctx context.Context) (int, error) {
user := m.Config.GetMysqlConfiguration().GetUser()
pw := fmt.Sprintf("-p='%s'", m.password)
cmd := commandlineexecutor.Params{
Executable: "sudo",
Args: []string{"mysql", "-u", user, pw, "-e", "SELECT @@innodb_buffer_pool_size"},
}
log.CtxLogger(ctx).Debugw("MySQL metric collection command", "command", cmd)
res := m.Execute(ctx, cmd)
log.CtxLogger(ctx).Debugw("MySQL metric collection result", "result", res)
lines := strings.Split(res.StdOut, "\n")
if len(lines) != 3 {
return 0, fmt.Errorf("found wrong number of lines in buffer pool size: %d", len(lines))
}
fields := strings.Fields(lines[1])
if len(fields) != 1 {
return 0, fmt.Errorf("found wrong number of fields in buffer pool size: %d", len(fields))
}
size, err := strconv.Atoi(fields[0])
if err != nil {
return 0, fmt.Errorf("failed to convert buffer pool size to integer: %v", err)
}
return size, nil
}

func (m *MySQLMetrics) totalRAM(ctx context.Context) (int, error) {
cmd := commandlineexecutor.Params{
Executable: "grep",
Args: []string{"MemTotal", "/proc/meminfo"},
}
log.CtxLogger(ctx).Debugw("getTotalRAM command", "command", cmd)
res := m.Execute(ctx, cmd)
log.CtxLogger(ctx).Debugw("getTotalRAM result", "result", res)
lines := strings.Split(res.StdOut, "\n")
if len(lines) != 2 {
return 0, fmt.Errorf("found wrong number of lines in total RAM: %d", len(lines))
}
fields := strings.Fields(lines[0])
if len(fields) != 3 {
return 0, fmt.Errorf("found wrong number of fields in total RAM: %d", len(fields))
}
ram, err := strconv.Atoi(fields[1])
if err != nil {
return 0, fmt.Errorf("failed to convert total RAM to integer: %v", err)
}
units := fields[2]
if strings.ToUpper(units) == "KB" {
ram = ram * 1024
}
return ram, nil
}

// CollectMetricsOnce collects metrics for MySQL databases running on the host.
func CollectMetricsOnce(ctx context.Context) {
log.CtxLogger(ctx).Info("MySQL metric collection not yet implemented.")
return
func (m *MySQLMetrics) CollectMetricsOnce(ctx context.Context) {
bufferPoolSize, err := m.bufferPoolSize(ctx)
if err != nil {
log.CtxLogger(ctx).Warnf("Failed to get buffer pool size: %v", err)
}
totalRAM, err := m.totalRAM(ctx)
if err != nil {
log.CtxLogger(ctx).Warnf("Failed to get total RAM: %v", err)
}
// TODO: send these metrics to Data Warehouse.
log.CtxLogger(ctx).Debugw("Buffer pool size: %s, total RAM: %s", bufferPoolSize, totalRAM)
}
Loading