| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package chartengine |
| 4 | |
| 5 | import ( |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/netdata/netdata/go/plugins/plugin/framework/chartengine/internal/program" |
| 9 | "github.com/stretchr/testify/assert" |
| 10 | "github.com/stretchr/testify/require" |
| 11 | ) |
| 12 | |
| 13 | func TestPlannerLifecycleScenarios(t *testing.T) { |
| 14 | tests := map[string]struct { |
| 15 | run func(t *testing.T) |
| 16 | }{ |
| 17 | "enforce lifecycle caps dimension cap evicts lru": { |
| 18 | run: runTestEnforceLifecycleCapsDimensionCapEvictsLRU, |
| 19 | }, |
| 20 | "collect expiry removals dimension and chart expiry": { |
| 21 | run: runTestCollectExpiryRemovalsDimensionAndChartExpiry, |
| 22 | }, |
| 23 | } |
| 24 | |
| 25 | for name, tc := range tests { |
| 26 | t.Run(name, tc.run) |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | func runTestEnforceLifecycleCapsDimensionCapEvictsLRU(t *testing.T) { |
| 31 | tests := map[string]struct { |
| 32 | currentSeq uint64 |
| 33 | }{ |
| 34 | "dimension cap evicts least-recently-seen inactive dimension first": { |
| 35 | currentSeq: 10, |
| 36 | }, |
| 37 | } |
| 38 | |
| 39 | for name, tc := range tests { |
| 40 | t.Run(name, func(t *testing.T) { |
| 41 | meta := program.ChartMeta{Title: "Requests", Context: "requests", Units: "requests/s"} |
| 42 | lifecycle := program.LifecyclePolicy{ |
| 43 | Dimensions: program.DimensionLifecyclePolicy{MaxDims: 2}, |
| 44 | } |
| 45 | |
| 46 | state := newMaterializedState() |
| 47 | matChart, created := state.ensureChart("chart_a", "tpl.requests", meta, lifecycle) |
| 48 | require.True(t, created) |
| 49 | |
| 50 | oldA, created := matChart.ensureDimension("old_a", dimensionState{ |
| 51 | algorithm: program.AlgorithmAbsolute, |
| 52 | multiplier: 1, |
| 53 | divisor: 1, |
| 54 | }) |
| 55 | require.True(t, created) |
| 56 | oldA.lastSeenSuccessSeq = 1 |
| 57 | |
| 58 | oldB, created := matChart.ensureDimension("old_b", dimensionState{ |
| 59 | algorithm: program.AlgorithmAbsolute, |
| 60 | multiplier: 1, |
| 61 | divisor: 1, |
| 62 | }) |
| 63 | require.True(t, created) |
| 64 | oldB.lastSeenSuccessSeq = 2 |
| 65 | |
| 66 | chartsByID := map[string]*chartState{ |
| 67 | "chart_a": { |
| 68 | templateID: "tpl.requests", |
| 69 | chartID: "chart_a", |
| 70 | meta: meta, |
| 71 | lifecycle: lifecycle, |
| 72 | currentBuildSeq: tc.currentSeq, |
| 73 | observedCount: 1, |
| 74 | entries: map[string]*dimBuildEntry{ |
| 75 | "new_c": { |
| 76 | seenSeq: tc.currentSeq, |
| 77 | dimensionState: dimensionState{ |
| 78 | static: false, |
| 79 | order: 0, |
| 80 | algorithm: program.AlgorithmAbsolute, |
| 81 | multiplier: 1, |
| 82 | divisor: 1, |
| 83 | }, |
| 84 | }, |
| 85 | }, |
| 86 | }, |
| 87 | } |
| 88 | |
| 89 | removeDims, removeCharts := enforceLifecycleCaps(tc.currentSeq, chartsByID, &state) |
| 90 | |
| 91 | assert.Empty(t, removeCharts) |
| 92 | require.Len(t, removeDims, 1) |
| 93 | assert.Equal(t, "chart_a", removeDims[0].ChartID) |
| 94 | assert.Equal(t, "old_a", removeDims[0].Name) |
| 95 | |
| 96 | assert.NotContains(t, matChart.dimensions, "old_a") |
| 97 | assert.Contains(t, matChart.dimensions, "old_b") |
| 98 | assert.Contains(t, chartsByID["chart_a"].entries, "new_c") |
| 99 | }) |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | func runTestCollectExpiryRemovalsDimensionAndChartExpiry(t *testing.T) { |
| 104 | tests := map[string]struct { |
| 105 | currentSeq uint64 |
| 106 | }{ |
| 107 | "stale chart and stale dimension are removed in one pass": { |
| 108 | currentSeq: 5, |
| 109 | }, |
| 110 | } |
| 111 | |
| 112 | for name, tc := range tests { |
| 113 | t.Run(name, func(t *testing.T) { |
| 114 | state := newMaterializedState() |
| 115 | |
| 116 | expiredMeta := program.ChartMeta{Title: "Expired", Context: "expired"} |
| 117 | expiredChart, created := state.ensureChart("chart_expired", "tpl.expired", expiredMeta, program.LifecyclePolicy{ |
| 118 | ExpireAfterCycles: 2, |
| 119 | }) |
| 120 | require.True(t, created) |
| 121 | expiredChart.lastSeenSuccessSeq = 3 |
| 122 | |
| 123 | liveMeta := program.ChartMeta{Title: "Live", Context: "live"} |
| 124 | liveChart, created := state.ensureChart("chart_live", "tpl.live", liveMeta, program.LifecyclePolicy{ |
| 125 | Dimensions: program.DimensionLifecyclePolicy{ExpireAfterCycles: 2}, |
| 126 | }) |
| 127 | require.True(t, created) |
| 128 | liveChart.lastSeenSuccessSeq = tc.currentSeq |
| 129 | |
| 130 | staleDim, created := liveChart.ensureDimension("stale_dim", dimensionState{ |
| 131 | algorithm: program.AlgorithmAbsolute, |
| 132 | multiplier: 1, |
| 133 | divisor: 1, |
| 134 | }) |
| 135 | require.True(t, created) |
| 136 | staleDim.lastSeenSuccessSeq = 2 |
| 137 | |
| 138 | freshDim, created := liveChart.ensureDimension("fresh_dim", dimensionState{ |
| 139 | algorithm: program.AlgorithmAbsolute, |
| 140 | multiplier: 1, |
| 141 | divisor: 1, |
| 142 | }) |
| 143 | require.True(t, created) |
| 144 | freshDim.lastSeenSuccessSeq = 4 |
| 145 | |
| 146 | removeDims, removeCharts := collectExpiryRemovals(tc.currentSeq, &state) |
| 147 | |
| 148 | require.Len(t, removeDims, 1) |
| 149 | assert.Equal(t, "chart_live", removeDims[0].ChartID) |
| 150 | assert.Equal(t, "stale_dim", removeDims[0].Name) |
| 151 | |
| 152 | require.Len(t, removeCharts, 1) |
| 153 | assert.Equal(t, "chart_expired", removeCharts[0].ChartID) |
| 154 | |
| 155 | assert.NotContains(t, state.charts, "chart_expired") |
| 156 | assert.NotContains(t, liveChart.dimensions, "stale_dim") |
| 157 | assert.Contains(t, liveChart.dimensions, "fresh_dim") |
| 158 | }) |
| 159 | } |
| 160 | } |