| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package chartengine |
| 4 | |
| 5 | import ( |
| 6 | "github.com/netdata/netdata/go/plugins/plugin/framework/chartengine/internal/program" |
| 7 | ) |
| 8 | |
| 9 | // materializedState tracks engine-owned chart lifecycle across successful cycles. |
| 10 | type materializedState struct { |
| 11 | charts map[string]*materializedChartState |
| 12 | } |
| 13 | |
| 14 | // materializedChartState tracks one materialized chart instance. |
| 15 | type materializedChartState struct { |
| 16 | templateID string |
| 17 | meta program.ChartMeta |
| 18 | lifecycle program.LifecyclePolicy |
| 19 | lastSeenSuccessSeq uint64 |
| 20 | dimensions map[string]*materializedDimensionState |
| 21 | orderedDims []string |
| 22 | orderedDimsDirty bool |
| 23 | scratchEntries map[string]*dimBuildEntry |
| 24 | } |
| 25 | |
| 26 | // materializedDimensionState tracks one materialized dimension in a chart. |
| 27 | type materializedDimensionState struct { |
| 28 | hidden bool |
| 29 | float bool |
| 30 | static bool |
| 31 | order int |
| 32 | algorithm program.Algorithm |
| 33 | multiplier int |
| 34 | divisor int |
| 35 | lastSeenSuccessSeq uint64 |
| 36 | } |
| 37 | |
| 38 | func newMaterializedState() materializedState { |
| 39 | return materializedState{ |
| 40 | charts: make(map[string]*materializedChartState), |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | func (s materializedState) clone() materializedState { |
| 45 | if len(s.charts) == 0 { |
| 46 | return newMaterializedState() |
| 47 | } |
| 48 | |
| 49 | out := materializedState{ |
| 50 | charts: make(map[string]*materializedChartState, len(s.charts)), |
| 51 | } |
| 52 | for chartID, chart := range s.charts { |
| 53 | if chart == nil { |
| 54 | continue |
| 55 | } |
| 56 | out.charts[chartID] = chart.clone() |
| 57 | } |
| 58 | return out |
| 59 | } |
| 60 | |
| 61 | func (c *materializedChartState) clone() *materializedChartState { |
| 62 | if c == nil { |
| 63 | return nil |
| 64 | } |
| 65 | |
| 66 | out := &materializedChartState{ |
| 67 | templateID: c.templateID, |
| 68 | meta: c.meta, |
| 69 | lifecycle: c.lifecycle, |
| 70 | lastSeenSuccessSeq: c.lastSeenSuccessSeq, |
| 71 | orderedDims: append([]string(nil), c.orderedDims...), |
| 72 | orderedDimsDirty: c.orderedDimsDirty, |
| 73 | } |
| 74 | if len(c.dimensions) > 0 { |
| 75 | out.dimensions = make(map[string]*materializedDimensionState, len(c.dimensions)) |
| 76 | for name, dim := range c.dimensions { |
| 77 | if dim == nil { |
| 78 | continue |
| 79 | } |
| 80 | cloned := *dim |
| 81 | out.dimensions[name] = &cloned |
| 82 | } |
| 83 | } else { |
| 84 | out.dimensions = make(map[string]*materializedDimensionState) |
| 85 | } |
| 86 | if len(c.scratchEntries) > 0 { |
| 87 | out.scratchEntries = make(map[string]*dimBuildEntry, len(c.scratchEntries)) |
| 88 | for name, entry := range c.scratchEntries { |
| 89 | if entry == nil { |
| 90 | continue |
| 91 | } |
| 92 | cloned := *entry |
| 93 | out.scratchEntries[name] = &cloned |
| 94 | } |
| 95 | } |
| 96 | return out |
| 97 | } |
| 98 | |
| 99 | func (s *materializedState) ensureChart( |
| 100 | chartID string, |
| 101 | templateID string, |
| 102 | meta program.ChartMeta, |
| 103 | lifecycle program.LifecyclePolicy, |
| 104 | ) (*materializedChartState, bool) { |
| 105 | chart, ok := s.charts[chartID] |
| 106 | if ok { |
| 107 | if chart.templateID != templateID { |
| 108 | chart.templateID = templateID |
| 109 | chart.dimensions = make(map[string]*materializedDimensionState) |
| 110 | chart.orderedDims = nil |
| 111 | chart.orderedDimsDirty = false |
| 112 | chart.scratchEntries = nil |
| 113 | } |
| 114 | chart.meta = meta |
| 115 | chart.lifecycle = lifecycle |
| 116 | return chart, false |
| 117 | } |
| 118 | chart = &materializedChartState{ |
| 119 | templateID: templateID, |
| 120 | meta: meta, |
| 121 | lifecycle: lifecycle, |
| 122 | dimensions: make(map[string]*materializedDimensionState), |
| 123 | } |
| 124 | s.charts[chartID] = chart |
| 125 | return chart, true |
| 126 | } |
| 127 | |
| 128 | func (c *materializedChartState) ensureDimension(name string, state dimensionState) (*materializedDimensionState, bool) { |
| 129 | dim, ok := c.dimensions[name] |
| 130 | if ok { |
| 131 | if dim.static != state.static || dim.order != state.order { |
| 132 | c.orderedDimsDirty = true |
| 133 | } |
| 134 | dim.hidden = state.hidden |
| 135 | dim.float = state.float |
| 136 | dim.static = state.static |
| 137 | dim.order = state.order |
| 138 | dim.algorithm = state.algorithm |
| 139 | dim.multiplier = state.multiplier |
| 140 | dim.divisor = state.divisor |
| 141 | return dim, false |
| 142 | } |
| 143 | dim = &materializedDimensionState{ |
| 144 | hidden: state.hidden, |
| 145 | float: state.float, |
| 146 | static: state.static, |
| 147 | order: state.order, |
| 148 | algorithm: state.algorithm, |
| 149 | multiplier: state.multiplier, |
| 150 | divisor: state.divisor, |
| 151 | } |
| 152 | c.dimensions[name] = dim |
| 153 | c.orderedDimsDirty = true |
| 154 | return dim, true |
| 155 | } |
| 156 | |
| 157 | func (c *materializedChartState) removeDimension(name string) { |
| 158 | if _, ok := c.dimensions[name]; !ok { |
| 159 | return |
| 160 | } |
| 161 | delete(c.dimensions, name) |
| 162 | c.orderedDimsDirty = true |
| 163 | } |
| 164 | |
| 165 | func (c *materializedChartState) orderedDimensionNames() []string { |
| 166 | if !c.orderedDimsDirty && len(c.orderedDims) == len(c.dimensions) { |
| 167 | return c.orderedDims |
| 168 | } |
| 169 | c.orderedDims = orderedMaterializedDimensionNames(c.dimensions) |
| 170 | c.orderedDimsDirty = false |
| 171 | return c.orderedDims |
| 172 | } |
| 173 | |
| 174 | func (c *materializedChartState) checkoutScratchEntries(dimCap int) map[string]*dimBuildEntry { |
| 175 | if c.scratchEntries != nil { |
| 176 | return c.scratchEntries |
| 177 | } |
| 178 | c.scratchEntries = make(map[string]*dimBuildEntry, dimCap) |
| 179 | return c.scratchEntries |
| 180 | } |
| 181 | |
| 182 | func (c *materializedChartState) storeScratchEntries(entries map[string]*dimBuildEntry) { |
| 183 | c.scratchEntries = entries |
| 184 | } |
| 185 | |
| 186 | func (c *materializedChartState) pruneScratchEntries(currentSeq uint64) { |
| 187 | if len(c.scratchEntries) == 0 { |
| 188 | return |
| 189 | } |
| 190 | for name, entry := range c.scratchEntries { |
| 191 | if entry == nil { |
| 192 | delete(c.scratchEntries, name) |
| 193 | continue |
| 194 | } |
| 195 | if entry.seenSeq == currentSeq { |
| 196 | continue |
| 197 | } |
| 198 | if _, keep := c.dimensions[name]; keep { |
| 199 | continue |
| 200 | } |
| 201 | delete(c.scratchEntries, name) |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | func shouldExpire(lastSeenSuccessSeq, currentSuccessSeq uint64, expireAfterCycles int) bool { |
| 206 | if expireAfterCycles <= 0 { |
| 207 | return false |
| 208 | } |
| 209 | if lastSeenSuccessSeq == 0 || currentSuccessSeq <= lastSeenSuccessSeq { |
| 210 | return false |
| 211 | } |
| 212 | missedCycles := currentSuccessSeq - lastSeenSuccessSeq |
| 213 | return missedCycles >= uint64(expireAfterCycles) |
| 214 | } |