github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/telemetry/telemeter_test.go (about)

     1  package telemetry
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"testing/synctest"
     7  	"time"
     8  
     9  	"github.com/grafana/synthetic-monitoring-agent/internal/testhelper"
    10  	sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
    11  	prom "github.com/prometheus/client_golang/prometheus"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestTelemeterAddExecution(t *testing.T) {
    16  	synctest.Test(t, func(t *testing.T) {
    17  		verifyTelemeter := func(t *testing.T, tele *Telemeter, nRegionPushers int) {
    18  			t.Helper()
    19  
    20  			tele.pushersMu.RLock()
    21  			defer tele.pushersMu.RUnlock()
    22  
    23  			require.Equal(t, len(tele.pushers), nRegionPushers)
    24  		}
    25  
    26  		verifyRegionPusher := func(t *testing.T, tele *Telemeter, regionID int32, ee ...Execution) {
    27  			t.Helper()
    28  			tele.pushersMu.RLock()
    29  			p, ok := tele.pushers[regionID]
    30  			tele.pushersMu.RUnlock()
    31  			require.True(t, ok)
    32  
    33  			// sum expected executions data, indexed by tenant -> checkClass -> calKey
    34  			regionTele := make(map[int64]map[sm.CheckClass]map[string]*sm.CheckClassTelemetry)
    35  			for _, e := range ee {
    36  				tenantTele, ok := regionTele[e.LocalTenantID]
    37  				if !ok {
    38  					tenantTele = make(map[sm.CheckClass]map[string]*sm.CheckClassTelemetry)
    39  					regionTele[e.LocalTenantID] = tenantTele
    40  				}
    41  
    42  				clTele, ok := tenantTele[e.CheckClass]
    43  				if !ok {
    44  					clTele = make(map[string]*sm.CheckClassTelemetry)
    45  					tenantTele[e.CheckClass] = clTele
    46  				}
    47  
    48  				calKey := serializeCALs(e.CostAttributionLabels)
    49  
    50  				calTele, ok := clTele[calKey]
    51  				if !ok {
    52  					calTele = &sm.CheckClassTelemetry{
    53  						CheckClass:            e.CheckClass,
    54  						CostAttributionLabels: deserializeCals(calKey),
    55  					}
    56  					clTele[calKey] = calTele
    57  				}
    58  
    59  				calTele.Executions++
    60  				calTele.Duration += float32(e.Duration.Seconds())
    61  			}
    62  
    63  			// verify - must lock before reading p.telemetry to avoid data race
    64  			// with the background goroutine that calls next()
    65  			p.telemetryMu.Lock()
    66  			defer p.telemetryMu.Unlock()
    67  
    68  			for tenantID, expTTele := range regionTele {
    69  				gotTTele, ok := p.telemetry[tenantID]
    70  				require.True(t, ok, "telemetry not found for tenant %d", tenantID)
    71  
    72  				for checkClass, expCLTele := range expTTele {
    73  					gotCLTele, ok := gotTTele[checkClass]
    74  					require.True(t, ok, "telemetry not found for check class %v", checkClass)
    75  
    76  					for calKey, expCalTele := range expCLTele {
    77  						gotCalTele, ok := gotCLTele[calKey]
    78  						require.True(t, ok, "telemetry not found for cal key %s", calKey)
    79  						require.Equal(t, expCalTele.Executions, gotCalTele.Executions, "executions mismatch for cal key %s", calKey)
    80  						require.Equal(t, expCalTele.Duration, gotCalTele.Duration, "duration mismatch for cal key %s", calKey)
    81  						require.Equal(t, expCalTele.CostAttributionLabels, gotCalTele.CostAttributionLabels, "cals mispath for cal key %s", calKey)
    82  					}
    83  				}
    84  			}
    85  		}
    86  
    87  		var (
    88  			timeSpan   = 1 * time.Hour
    89  			testClient = &testTelemetryClient{
    90  				rr: testPushResp{
    91  					tr: &sm.PushTelemetryResponse{
    92  						Status: &sm.Status{Code: sm.StatusCode_OK},
    93  					},
    94  				},
    95  			}
    96  			registerer = prom.NewPedanticRegistry()
    97  		)
    98  
    99  		// Create context within synctest bubble.
   100  		//
   101  		// Do not use t.Context() here because that's derived from the main context created outside the bubble.
   102  		ctx, cancel := context.WithCancel(context.Background())
   103  
   104  		defer func() {
   105  			// Cancel context and wait for all goroutines to exit before test returns.
   106  			// This prevents race conditions between goroutine logging and test cleanup.
   107  			cancel()
   108  			synctest.Wait()
   109  		}()
   110  
   111  		tele := NewTelemeter(ctx, instance, timeSpan, testClient, testhelper.Logger(t), registerer)
   112  
   113  		{ // should init with no region pushers
   114  			verifyTelemeter(t, tele, 0)
   115  		}
   116  
   117  		{ // should create a new region pusher
   118  			execution := getTestDataset(0).executions[0]
   119  			tele.AddExecution(execution)
   120  			verifyTelemeter(t, tele, 1)
   121  			verifyRegionPusher(t, tele, execution.RegionID, execution)
   122  		}
   123  
   124  		{ // should add telemetry to current region pusher
   125  			executions := getTestDataset(0).executions
   126  			tele.AddExecution(executions[1])
   127  			tele.AddExecution(executions[2])
   128  			verifyTelemeter(t, tele, 1)
   129  			verifyRegionPusher(t, tele, executions[0].RegionID, executions[:2]...)
   130  		}
   131  
   132  		{ // should add another region pusher
   133  			executions := getTestDataset(0).executions
   134  			executions[2].RegionID = 1
   135  			tele.AddExecution(executions[2])
   136  			executions[3].RegionID = 1
   137  			tele.AddExecution(executions[3])
   138  			verifyTelemeter(t, tele, 2)
   139  			verifyRegionPusher(t, tele, executions[3].RegionID, executions[2:4]...)
   140  		}
   141  
   142  		{ // initial region pusher data should be intact
   143  			executions := getTestDataset(0).executions
   144  			verifyRegionPusher(t, tele, executions[0].RegionID, executions[:2]...)
   145  		}
   146  	})
   147  }