agones.dev/agones@v1.54.0/pkg/gameserversets/metrics_test.go (about)

     1  // Copyright 2025 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gameserversets
    16  
    17  import (
    18  	"bufio"
    19  	"context"
    20  	"net/http"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    26  	mt "agones.dev/agones/pkg/metrics"
    27  	agtesting "agones.dev/agones/pkg/testing"
    28  	"agones.dev/agones/pkg/util/httpserver"
    29  	utilruntime "agones.dev/agones/pkg/util/runtime"
    30  	"agones.dev/agones/test/e2e/framework"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  	"go.opencensus.io/stats/view"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/runtime"
    36  	k8stesting "k8s.io/client-go/testing"
    37  )
    38  
    39  func resetMetrics() {
    40  	unRegisterViews()
    41  	registerViews()
    42  }
    43  
    44  func TestGSSMetrics(t *testing.T) {
    45  	resetMetrics()
    46  
    47  	utilruntime.FeatureTestMutex.Lock()
    48  	defer utilruntime.FeatureTestMutex.Unlock()
    49  
    50  	conf := mt.Config{
    51  		PrometheusMetrics: true,
    52  	}
    53  	server := &httpserver.Server{
    54  		Port:   "3001",
    55  		Logger: framework.TestLogger(t),
    56  	}
    57  
    58  	health, closer := mt.SetupMetrics(conf, server)
    59  	defer t.Cleanup(closer)
    60  
    61  	assert.NotNil(t, health, "Health check handler should not be nil")
    62  	server.Handle("/", health)
    63  
    64  	gsSet := defaultFixture()
    65  	c, m := newFakeController()
    66  	expected := 10
    67  	count := 0
    68  
    69  	m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
    70  		ca := action.(k8stesting.CreateAction)
    71  		gs := ca.GetObject().(*agonesv1.GameServer)
    72  
    73  		assert.True(t, metav1.IsControlledBy(gs, gsSet))
    74  		count++
    75  
    76  		return true, gs, nil
    77  	})
    78  
    79  	ctx, cancel := agtesting.StartInformers(m)
    80  	defer cancel()
    81  
    82  	err := c.addMoreGameServers(ctx, gsSet, expected)
    83  	assert.Nil(t, err)
    84  	assert.Equal(t, expected, count)
    85  	agtesting.AssertEventContains(t, m.FakeRecorder.Events, "SuccessfulCreate")
    86  
    87  	ctxHTTP, cancelHTTP := context.WithCancel(context.Background())
    88  	defer cancelHTTP()
    89  
    90  	// Start the HTTP server
    91  	go func() {
    92  		_ = server.Run(ctxHTTP, 0)
    93  	}()
    94  	time.Sleep(300 * time.Millisecond)
    95  
    96  	resp, err := http.Get("http://localhost:3001/metrics")
    97  	require.NoError(t, err, "Failed to GET metrics endpoint")
    98  	defer func() {
    99  		assert.NoError(t, resp.Body.Close())
   100  	}()
   101  
   102  	assert.Equal(t, http.StatusOK, resp.StatusCode, "Expected status code 200")
   103  
   104  	metricsSet := collectMetricNames(resp)
   105  	expectedMetrics := getMetricNames()
   106  
   107  	for _, metric := range expectedMetrics {
   108  		assert.Contains(t, metricsSet, metric, "Missing expected metric: %s", metric)
   109  	}
   110  }
   111  
   112  // getMetricNames returns all metric view names.
   113  func getMetricNames() []string {
   114  	var metricNames []string
   115  	for _, v := range stateViews {
   116  		metricName := "agones_" + v.Name
   117  
   118  		// Check if the aggregation type is Distribution
   119  		if v.Aggregation.Type == view.AggTypeDistribution {
   120  			// If it's a distribution, we append _bucket, _sum, and _count
   121  			metricNames = append(metricNames,
   122  				metricName+"_bucket",
   123  				metricName+"_sum",
   124  				metricName+"_count",
   125  			)
   126  		} else {
   127  			metricNames = append(metricNames, metricName)
   128  
   129  		}
   130  	}
   131  	return metricNames
   132  }
   133  
   134  func collectMetricNames(resp *http.Response) map[string]bool {
   135  	metrics := make(map[string]bool)
   136  	scanner := bufio.NewScanner(resp.Body)
   137  	for scanner.Scan() {
   138  		line := scanner.Text()
   139  		if strings.HasPrefix(line, "#") || line == "" {
   140  			continue
   141  		}
   142  		fields := strings.Fields(line)
   143  		if len(fields) > 0 {
   144  			// Extract only the metric name, excluding labels
   145  			metricName := fields[0]
   146  			if idx := strings.Index(metricName, "{"); idx != -1 {
   147  				metricName = metricName[:idx]
   148  			}
   149  			metrics[metricName] = true
   150  		}
   151  	}
   152  	return metrics
   153  }