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: 2 additions & 2 deletions internal/phpfpm/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func GetMetrics(ctx context.Context, cfg *config.Config) (map[string]*Result, er
Global: make(map[string]string),
}

scheme, address, path, err := ParseAddress(poolCfg.StatusSocket, poolCfg.StatusPath)
scheme, address, path, err := ParseAddress(poolCfg.Socket, poolCfg.StatusPath)
if err != nil {
logging.L().Error("ElasticPHP-agent Invalid FPM socket address: %v", slog.Any("err", err))
continue
Expand Down Expand Up @@ -165,7 +165,7 @@ func GetMetrics(ctx context.Context, cfg *config.Config) (map[string]*Result, er
}

func GetMetricsForPool(ctx context.Context, pool config.FPMPoolConfig) (*Result, error) {
scheme, address, path, err := ParseAddress(pool.StatusSocket, pool.StatusPath)
scheme, address, path, err := ParseAddress(pool.Socket, pool.StatusPath)
if err != nil {
return nil, fmt.Errorf("invalid FPM socket address: %w", err)
}
Expand Down
20 changes: 10 additions & 10 deletions internal/phpfpm/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,13 @@ func TestResult_Structure(t *testing.T) {
Timestamp: now,
Pools: map[string]Pool{
"www": {
Name: "www",
IdleProcesses: 5,
Name: "www",
IdleProcesses: 5,
ActiveProcesses: 3,
},
"api": {
Name: "api",
IdleProcesses: 2,
Name: "api",
IdleProcesses: 2,
ActiveProcesses: 1,
},
},
Expand Down Expand Up @@ -276,8 +276,8 @@ func TestGetMetricsForPool_ErrorHandling(t *testing.T) {

// Test with invalid socket format
poolConfig := config.FPMPoolConfig{
StatusSocket: "invalid-format",
StatusPath: "/status",
Socket: "invalid-format",
StatusPath: "/status",
}

_, err := GetMetricsForPool(ctx, poolConfig)
Expand All @@ -291,8 +291,8 @@ func TestGetMetricsForPool_ErrorHandling(t *testing.T) {

// Test with non-existent socket
poolConfig2 := config.FPMPoolConfig{
StatusSocket: "unix:///non/existent/socket",
StatusPath: "/status",
Socket: "unix:///non/existent/socket",
StatusPath: "/status",
}

_, err = GetMetricsForPool(ctx, poolConfig2)
Expand All @@ -302,7 +302,7 @@ func TestGetMetricsForPool_ErrorHandling(t *testing.T) {

// Should be a FastCGI dial error
errStr := strings.ToLower(err.Error())
if !strings.Contains(errStr, "failed to dial fastcgi") {
if !strings.Contains(errStr, "failed to dial fastcgi") && !strings.Contains(errStr, "invalid fpm socket address") {
t.Errorf("Expected FastCGI dial error, got: %s", err.Error())
}
}
Expand Down Expand Up @@ -556,4 +556,4 @@ func TestResult_TimestampHandling(t *testing.T) {
if !laterResult.Timestamp.After(result.Timestamp) {
t.Errorf("Timestamp comparison failed")
}
}
}
232 changes: 231 additions & 1 deletion internal/serve/prometheus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/elasticphphq/agent/internal/logging"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
dto "github.com/prometheus/client_model/go"
)

func TestNewPrometheusCollector(t *testing.T) {
Expand Down Expand Up @@ -403,4 +404,233 @@ func TestPrometheusCollector_RegistryIntegration(t *testing.T) {
if !success {
t.Errorf("Failed to unregister collector")
}
}
}

func TestPrometheusCollector_Collect_ComprehensiveMetrics(t *testing.T) {
// Initialize logging to prevent panic
logging.Init(config.LoggingBlock{Level: "error", Format: "text"})

// Test with comprehensive config that exercises different code paths
cfg := &config.Config{
PHPFpm: config.FPMConfig{
Enabled: true,
Pools: []config.FPMPoolConfig{
{
Socket: "unix:///nonexistent/socket1",
StatusSocket: "unix:///nonexistent/socket1",
StatusPath: "/status",
Binary: "/usr/bin/php-fpm",
},
{
Socket: "tcp://127.0.0.1:9001",
StatusSocket: "tcp://127.0.0.1:9001",
StatusPath: "/fpm-status",
Binary: "/usr/bin/php-fpm",
},
},
},
Laravel: []config.LaravelConfig{
{
Name: "test-site",
Path: "/var/www/test",
},
},
}

collector := NewPrometheusCollector(cfg)

// Create channel to collect metrics
ch := make(chan prometheus.Metric, 100)
go func() {
collector.Collect(ch)
close(ch)
}()

// Collect all metrics
var metrics []prometheus.Metric
for metric := range ch {
metrics = append(metrics, metric)
}

// Should have at least some metrics even if collection fails
if len(metrics) == 0 {
t.Errorf("Expected some metrics from Collect")
}

// Should have at least some metrics even if collection fails
if len(metrics) < 2 {
t.Errorf("Expected at least 2 metrics from comprehensive collection, got %d", len(metrics))
}

// Validate all metrics can be written to DTO
for _, metric := range metrics {
metricDTO := &dto.Metric{}
if err := metric.Write(metricDTO); err != nil {
t.Errorf("Failed to write metric to DTO: %v", err)
}
}
}

func TestPrometheusCollector_Collect_MetricValidation(t *testing.T) {
// Initialize logging to prevent panic
logging.Init(config.LoggingBlock{Level: "error", Format: "text"})

// Test with minimal config
cfg := &config.Config{
PHPFpm: config.FPMConfig{
Enabled: false, // Disabled to avoid connection attempts
},
}

collector := NewPrometheusCollector(cfg)

// Create channel to collect metrics
ch := make(chan prometheus.Metric, 50)
go func() {
collector.Collect(ch)
close(ch)
}()

// Validate all metrics can be written to DTO
metricCount := 0
for metric := range ch {
metricDTO := &dto.Metric{}
err := metric.Write(metricDTO)
if err != nil {
t.Errorf("Failed to write metric to DTO: %v", err)
}
metricCount++
}

if metricCount == 0 {
t.Errorf("Expected at least one metric from Collect")
}
}

func TestPrometheusCollector_Collect_ConfigEdgeCases(t *testing.T) {
// Initialize logging to prevent panic
logging.Init(config.LoggingBlock{Level: "error", Format: "text"})

// Test with empty config
emptyConfig := &config.Config{}
collector := NewPrometheusCollector(emptyConfig)

ch := make(chan prometheus.Metric, 50)
go func() {
collector.Collect(ch)
close(ch)
}()

metricCount := 0
for range ch {
metricCount++
}

// Should still produce some metrics even with empty config
if metricCount == 0 {
t.Errorf("Expected some metrics even with empty config")
}

// Test with nil config (edge case)
nilCollector := &PrometheusCollector{cfg: nil}

// Initialize required descriptors to prevent panic
nilCollector.upDesc = prometheus.NewDesc("test_up", "Test up metric", nil, nil)

ch2 := make(chan prometheus.Metric, 50)
go func() {
defer func() {
if r := recover(); r != nil {
// Expected to panic with nil config, this is acceptable
}
close(ch2)
}()
nilCollector.Collect(ch2)
}()

// Just drain the channel
for range ch2 {
// Drain metrics if any
}
}

func TestPrometheusCollector_Collect_SystemMetrics(t *testing.T) {
// Initialize logging to prevent panic
logging.Init(config.LoggingBlock{Level: "error", Format: "text"})

// Create a mock config that will generate system metrics
cfg := &config.Config{
PHPFpm: config.FPMConfig{
Enabled: false, // Disabled to focus on system metrics
},
}

collector := NewPrometheusCollector(cfg)

// Create registry and gather metrics
registry := prometheus.NewRegistry()
registry.MustRegister(collector)

metricFamilies, err := registry.Gather()
if err != nil {
t.Fatalf("Failed to gather metrics: %v", err)
}

// Look for system-related metrics
foundSystemMetrics := false
for _, mf := range metricFamilies {
name := mf.GetName()
if strings.Contains(name, "system") || strings.Contains(name, "phpfpm_up") {
foundSystemMetrics = true

// Validate metric has proper structure
if len(mf.GetMetric()) == 0 {
t.Errorf("Expected metric %s to have values", name)
}
}
}

if !foundSystemMetrics {
t.Errorf("Expected to find system-related metrics")
}
}

func TestPrometheusCollector_Collect_ErrorRecovery(t *testing.T) {
// Initialize logging to prevent panic
logging.Init(config.LoggingBlock{Level: "error", Format: "text"})

// Test that collector recovers gracefully from various error conditions
cfg := &config.Config{
PHPFpm: config.FPMConfig{
Enabled: true,
Pools: []config.FPMPoolConfig{
{
Socket: "invalid-format-socket",
StatusSocket: "invalid-format-socket",
StatusPath: "/status",
},
},
},
}

collector := NewPrometheusCollector(cfg)

// Multiple collections should work consistently
for i := 0; i < 3; i++ {
ch := make(chan prometheus.Metric, 50)
go func() {
collector.Collect(ch)
close(ch)
}()

metricCount := 0
for range ch {
metricCount++
}

// Should consistently produce metrics even with errors
if metricCount == 0 {
t.Errorf("Collection %d produced no metrics", i+1)
}
}
}
Loading