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 }