Skip to content
Open
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
2 changes: 2 additions & 0 deletions cmd/workload-discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/aws/amazon-cloudwatch-agent/internal/detector"
"github.com/aws/amazon-cloudwatch-agent/internal/detector/filter"
"github.com/aws/amazon-cloudwatch-agent/internal/detector/java"
"github.com/aws/amazon-cloudwatch-agent/internal/detector/mysql"
"github.com/aws/amazon-cloudwatch-agent/internal/detector/nvidia"
"github.com/aws/amazon-cloudwatch-agent/internal/detector/util"
"github.com/aws/amazon-cloudwatch-agent/tool/paths"
Expand Down Expand Up @@ -47,6 +48,7 @@ func NewDiscoverer(cfg Config, logger *slog.Logger) *Discoverer {
logger: logger,
processDetectors: []detector.ProcessDetector{
java.NewDetector(logger, filters.Process.Name),
mysql.NewDetector(logger),
},
deviceDetectors: []detector.DeviceDetector{
nvidia.NewDetector(logger),
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ replace (
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsekshyperpodreceiver => github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/awsekshyperpodreceiver v0.0.0-20260617204523-70530ca095f9
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsxrayreceiver => github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/awsxrayreceiver v0.0.0-20260617204523-70530ca095f9
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jmxreceiver => github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/jmxreceiver v0.0.0-20260617204523-70530ca095f9
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mysqlreceiver => github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/mysqlreceiver v0.0.0-20260623153739-5a887b09871a
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/postgresqlreceiver => github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/postgresqlreceiver v0.0.0-20260617204523-70530ca095f9
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver => github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.0.0-20260617204523-70530ca095f9
)
Expand Down Expand Up @@ -195,6 +196,7 @@ require (
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/journaldreceiver v0.124.1
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/kafkareceiver v0.124.1
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/kubeletstatsreceiver v0.124.1
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mysqlreceiver v0.124.1
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/postgresqlreceiver v0.124.1
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.124.1
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver v0.124.1
Expand Down Expand Up @@ -619,7 +621,7 @@ require (
go.opentelemetry.io/collector/component/componentstatus v0.124.0 // indirect
go.opentelemetry.io/collector/config/configcompression v1.30.0
go.opentelemetry.io/collector/config/configgrpc v0.124.0 // indirect
go.opentelemetry.io/collector/config/confignet v1.30.0 // indirect
go.opentelemetry.io/collector/config/confignet v1.30.0
go.opentelemetry.io/collector/config/configretry v1.30.0 // indirect
go.opentelemetry.io/collector/confmap/provider/httpprovider v1.30.0 // indirect
go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.30.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/awsxrayr
github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/awsxrayreceiver v0.0.0-20260617204523-70530ca095f9/go.mod h1:gNDpUc9eYHKy9YqGYK8toPIo3QZKdYQFLnaVQDA32A0=
github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/jmxreceiver v0.0.0-20260617204523-70530ca095f9 h1:R4Ems+YMAXLtsEZwRXw8/cf1NkrzHQsc25WPdYbUaTM=
github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/jmxreceiver v0.0.0-20260617204523-70530ca095f9/go.mod h1:wQV77sHJJdUMIvbJna8LjlNb0omEdiVSrwQnoVAlTMM=
github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/mysqlreceiver v0.0.0-20260623153739-5a887b09871a h1:ScKUmN6HDigSB0rceZR3CmcO/CVDTxOtrk8XQSTC+Q4=
github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/mysqlreceiver v0.0.0-20260623153739-5a887b09871a/go.mod h1:tnnmAnxuLxWKBG7NjOI3po8R+EAX/9cG1y7TWiQTMvA=
github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/postgresqlreceiver v0.0.0-20260617204523-70530ca095f9 h1:ZLP3mZHMXzUjiXxXFVI9Z5LtU25qT13Qajz84mna/LY=
github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/postgresqlreceiver v0.0.0-20260617204523-70530ca095f9/go.mod h1:gSxi2fY+T8mNzlaU7qOEnaXTcKfhIpijDSyeCu5GOYo=
github.com/amazon-contributing/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.0.0-20260617204523-70530ca095f9 h1:p0HB9BwhUdkaI0ALIb3hYxi7oCTDsGUuGadeM3R36AM=
Expand Down
1 change: 1 addition & 0 deletions internal/detector/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
CategoryKafkaBroker Category = "KAFKA/BROKER"
CategoryKafkaClient Category = "KAFKA/CLIENT"
CategoryNvidiaGPU Category = "NVIDIA_GPU"
CategoryMySQL Category = "MYSQL"
)

// Status represents whether the resource requires more actions before telemetry is available.
Expand Down
24 changes: 24 additions & 0 deletions internal/detector/mysql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# MySQL Process Detector

Detects MySQL database server processes running on the system.

## Overview

The MySQL detector identifies MySQL server instances by checking for the `mysqld` executable name. It is used by the `workload-discovery` command to automatically discover MySQL workloads.

## Detection Method

The detector examines the executable path of each process and checks if the base name is `mysqld`.

## Status Results
- `READY`: MySQL process detected with a port (explicit via `--port`/`-P` flag or `MYSQL_TCP_PORT`, otherwise defaults to 3306).

## Sample Metadata Result
```json
{
"categories": ["MYSQL"],
"name": "mysql",
"status": "READY",
"telemetryPort": 3306
}
```
109 changes: 109 additions & 0 deletions internal/detector/mysql/extract/port.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT

package extract

import (
"context"
"strconv"
"strings"

"github.com/aws/amazon-cloudwatch-agent/internal/detector"
"github.com/aws/amazon-cloudwatch-agent/internal/detector/util"
)

const (
portFlagLong = "--port"
portFlagShort = "-P"
portEnvVar = "MYSQL_TCP_PORT"
defaultMySQLPort = 3306
)

type portExtractor struct {
subExtractors []detector.PortExtractor
}

// NewPortExtractor creates a port extractor that attempts to find the MySQL port
// from command line arguments (--port or -P flag) or environment variables (MYSQL_TCP_PORT).
// Falls back to the default MySQL port 3306.
func NewPortExtractor() detector.PortExtractor {
return &portExtractor{
subExtractors: []detector.PortExtractor{
&cmdlinePortExtractor{},
&envPortExtractor{},
},
}
}

func (e *portExtractor) Extract(ctx context.Context, process detector.Process) (int, error) {
for _, sub := range e.subExtractors {
port, err := sub.Extract(ctx, process)
if err == nil {
return port, nil
}
}
return defaultMySQLPort, nil
}

// cmdlinePortExtractor extracts port from --port or -P flag
type cmdlinePortExtractor struct{}

func (e *cmdlinePortExtractor) Extract(ctx context.Context, process detector.Process) (int, error) {
args, err := process.CmdlineSliceWithContext(ctx)
if err != nil {
return 0, err
}

for i, arg := range args {
// --port=3307 or --port 3307
if arg == portFlagLong && i+1 < len(args) {
port, err := strconv.Atoi(args[i+1])
if err == nil && util.IsValidPort(port) {
return port, nil
}
}
if strings.HasPrefix(arg, portFlagLong+"=") {
port, err := strconv.Atoi(arg[len(portFlagLong)+1:])
if err == nil && util.IsValidPort(port) {
return port, nil
}
}
// -P 3307 or -P3307
if arg == portFlagShort && i+1 < len(args) {
port, err := strconv.Atoi(args[i+1])
if err == nil && util.IsValidPort(port) {
return port, nil
}
}
if strings.HasPrefix(arg, portFlagShort) && len(arg) > len(portFlagShort) {
port, err := strconv.Atoi(arg[len(portFlagShort):])
if err == nil && util.IsValidPort(port) {
return port, nil
}
}
}

return 0, detector.ErrExtractPort
}

// envPortExtractor extracts port from MYSQL_TCP_PORT environment variable
type envPortExtractor struct{}

func (e *envPortExtractor) Extract(ctx context.Context, process detector.Process) (int, error) {
env, err := process.EnvironWithContext(ctx)
if err != nil {
return 0, err
}

for _, entry := range env {
parts := strings.SplitN(entry, "=", 2)
if len(parts) == 2 && parts[0] == portEnvVar {
port, err := strconv.Atoi(strings.TrimSpace(parts[1]))
if err == nil && util.IsValidPort(port) {
return port, nil
}
}
}

return 0, detector.ErrExtractPort
}
89 changes: 89 additions & 0 deletions internal/detector/mysql/extract/port_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT

package extract

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/aws/amazon-cloudwatch-agent/internal/detector/detectortest"
)

func TestPortExtractor(t *testing.T) {
ctx := context.Background()
extractor := NewPortExtractor()

tests := map[string]struct {
setup func(*detectortest.MockProcess)
wantPort int
}{
"Success/PortFromLongFlag": {
setup: func(mp *detectortest.MockProcess) {
mp.On("CmdlineSliceWithContext", ctx).Return([]string{"mysqld", "--port", "3307"}, nil)
},
wantPort: 3307,
},
"Success/PortFromLongFlagEquals": {
setup: func(mp *detectortest.MockProcess) {
mp.On("CmdlineSliceWithContext", ctx).Return([]string{"mysqld", "--port=3308"}, nil)
},
wantPort: 3308,
},
"Success/PortFromShortFlag": {
setup: func(mp *detectortest.MockProcess) {
mp.On("CmdlineSliceWithContext", ctx).Return([]string{"mysqld", "-P", "3309"}, nil)
},
wantPort: 3309,
},
"Success/PortFromShortFlagNoSpace": {
setup: func(mp *detectortest.MockProcess) {
mp.On("CmdlineSliceWithContext", ctx).Return([]string{"mysqld", "-P3310"}, nil)
},
wantPort: 3310,
},
"Success/PortFromEnv": {
setup: func(mp *detectortest.MockProcess) {
mp.On("CmdlineSliceWithContext", ctx).Return([]string{"mysqld"}, nil)
mp.On("EnvironWithContext", ctx).Return([]string{"PATH=/usr/bin", "MYSQL_TCP_PORT=3311"}, nil)
},
wantPort: 3311,
},
"Success/CmdlineTakesPrecedence": {
setup: func(mp *detectortest.MockProcess) {
mp.On("CmdlineSliceWithContext", ctx).Return([]string{"mysqld", "--port", "3307"}, nil)
},
wantPort: 3307,
},
"Success/DefaultPort": {
setup: func(mp *detectortest.MockProcess) {
mp.On("CmdlineSliceWithContext", ctx).Return([]string{"mysqld"}, nil)
mp.On("EnvironWithContext", ctx).Return([]string{"PATH=/usr/bin"}, nil)
},
wantPort: 3306,
},
"Success/DefaultOnAllSourcesFail": {
setup: func(mp *detectortest.MockProcess) {
mp.On("CmdlineSliceWithContext", ctx).Return(nil, assert.AnError)
mp.On("EnvironWithContext", ctx).Return(nil, assert.AnError)
},
wantPort: 3306,
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
mp := new(detectortest.MockProcess)
tt.setup(mp)

port, err := extractor.Extract(ctx, mp)

require.NoError(t, err)
assert.Equal(t, tt.wantPort, port)
mp.AssertExpectations(t)
})
}
}
61 changes: 61 additions & 0 deletions internal/detector/mysql/mysql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT

package mysql

import (
"context"
"log/slog"

"github.com/aws/amazon-cloudwatch-agent/internal/detector"
"github.com/aws/amazon-cloudwatch-agent/internal/detector/mysql/extract"
"github.com/aws/amazon-cloudwatch-agent/internal/detector/util"
)

const (
exeName = "mysqld"
)

type mysqlDetector struct {
logger *slog.Logger
portExtractor detector.PortExtractor
}

var _ detector.ProcessDetector = (*mysqlDetector)(nil)

// NewDetector creates a new process detector that identifies MySQL processes.
func NewDetector(logger *slog.Logger) detector.ProcessDetector {
return &mysqlDetector{
logger: logger,
portExtractor: extract.NewPortExtractor(),
}
}

// Detect identifies MySQL server processes and returns metadata.
func (d *mysqlDetector) Detect(ctx context.Context, process detector.Process) (*detector.Metadata, error) {
exe, err := process.ExeWithContext(ctx)
if err != nil {
return nil, err
}

base := util.BaseExe(exe)
if base != exeName {
return nil, detector.ErrIncompatibleDetector
}

d.logger.Debug("MySQL process detected", "pid", process.PID())

md := &detector.Metadata{
Name: "mysql",
Categories: []detector.Category{detector.CategoryMySQL},
}

port, err := d.portExtractor.Extract(ctx, process)
if err != nil {
return nil, err
}
md.Status = detector.StatusReady
md.TelemetryPort = port

return md, nil
}
Loading
Loading