github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/ingester/series.go (about) 1 package ingester 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/prometheus/client_golang/prometheus" 8 "github.com/prometheus/common/model" 9 "github.com/prometheus/prometheus/pkg/labels" 10 "github.com/prometheus/prometheus/pkg/value" 11 12 "github.com/cortexproject/cortex/pkg/chunk/encoding" 13 "github.com/cortexproject/cortex/pkg/prom1/storage/metric" 14 ) 15 16 const ( 17 sampleOutOfOrder = "sample-out-of-order" 18 newValueForTimestamp = "new-value-for-timestamp" 19 sampleOutOfBounds = "sample-out-of-bounds" 20 duplicateSample = "duplicate-sample" 21 duplicateTimestamp = "duplicate-timestamp" 22 ) 23 24 type memorySeries struct { 25 metric labels.Labels 26 27 // Sorted by start time, overlapping chunk ranges are forbidden. 28 chunkDescs []*desc 29 30 // Whether the current head chunk has already been finished. If true, 31 // the current head chunk must not be modified anymore. 32 headChunkClosed bool 33 34 // The timestamp & value of the last sample in this series. Needed to 35 // ensure timestamp monotonicity during ingestion. 36 lastSampleValueSet bool 37 lastTime model.Time 38 lastSampleValue model.SampleValue 39 40 // Prometheus metrics. 41 createdChunks prometheus.Counter 42 } 43 44 // newMemorySeries returns a pointer to a newly allocated memorySeries for the 45 // given metric. 46 func newMemorySeries(m labels.Labels, createdChunks prometheus.Counter) *memorySeries { 47 return &memorySeries{ 48 metric: m, 49 lastTime: model.Earliest, 50 createdChunks: createdChunks, 51 } 52 } 53 54 // add adds a sample pair to the series, possibly creating a new chunk. 55 // The caller must have locked the fingerprint of the series. 56 func (s *memorySeries) add(v model.SamplePair) error { 57 // If sender has repeated the same timestamp, check more closely and perhaps return error. 58 if v.Timestamp == s.lastTime { 59 // If we don't know what the last sample value is, silently discard. 60 // This will mask some errors but better than complaining when we don't really know. 61 if !s.lastSampleValueSet { 62 return makeNoReportError(duplicateTimestamp) 63 } 64 // If both timestamp and sample value are the same as for the last append, 65 // ignore as they are a common occurrence when using client-side timestamps 66 // (e.g. Pushgateway or federation). 67 if v.Value.Equal(s.lastSampleValue) { 68 return makeNoReportError(duplicateSample) 69 } 70 return makeMetricValidationError(newValueForTimestamp, s.metric, 71 fmt.Errorf("sample with repeated timestamp but different value; last value: %v, incoming value: %v", s.lastSampleValue, v.Value)) 72 } 73 if v.Timestamp < s.lastTime { 74 return makeMetricValidationError(sampleOutOfOrder, s.metric, 75 fmt.Errorf("sample timestamp out of order; last timestamp: %v, incoming timestamp: %v", s.lastTime, v.Timestamp)) 76 } 77 78 if len(s.chunkDescs) == 0 || s.headChunkClosed { 79 newHead := newDesc(encoding.New(), v.Timestamp, v.Timestamp) 80 s.chunkDescs = append(s.chunkDescs, newHead) 81 s.headChunkClosed = false 82 s.createdChunks.Inc() 83 } 84 85 newChunk, err := s.head().add(v) 86 if err != nil { 87 return err 88 } 89 90 // If we get a single chunk result, then just replace the head chunk with it 91 // (no need to update first/last time). Otherwise, we'll need to update first 92 // and last time. 93 if newChunk != nil { 94 first, last, err := firstAndLastTimes(newChunk) 95 if err != nil { 96 return err 97 } 98 s.chunkDescs = append(s.chunkDescs, newDesc(newChunk, first, last)) 99 s.createdChunks.Inc() 100 } 101 102 s.lastTime = v.Timestamp 103 s.lastSampleValue = v.Value 104 s.lastSampleValueSet = true 105 106 return nil 107 } 108 109 func firstAndLastTimes(c encoding.Chunk) (model.Time, model.Time, error) { 110 var ( 111 first model.Time 112 last model.Time 113 firstSet bool 114 iter = c.NewIterator(nil) 115 ) 116 for iter.Scan() { 117 sample := iter.Value() 118 if !firstSet { 119 first = sample.Timestamp 120 firstSet = true 121 } 122 last = sample.Timestamp 123 } 124 return first, last, iter.Err() 125 } 126 127 // closeHead marks the head chunk closed. The caller must have locked 128 // the fingerprint of the memorySeries. This method will panic if this 129 // series has no chunk descriptors. 130 func (s *memorySeries) closeHead(reason flushReason) { 131 s.chunkDescs[0].flushReason = reason 132 s.headChunkClosed = true 133 } 134 135 // firstTime returns the earliest known time for the series. The caller must have 136 // locked the fingerprint of the memorySeries. This method will panic if this 137 // series has no chunk descriptors. 138 func (s *memorySeries) firstTime() model.Time { 139 return s.chunkDescs[0].FirstTime 140 } 141 142 // Returns time of oldest chunk in the series, that isn't flushed. If there are 143 // no chunks, or all chunks are flushed, returns 0. 144 // The caller must have locked the fingerprint of the memorySeries. 145 func (s *memorySeries) firstUnflushedChunkTime() model.Time { 146 for _, c := range s.chunkDescs { 147 if !c.flushed { 148 return c.FirstTime 149 } 150 } 151 152 return 0 153 } 154 155 // head returns a pointer to the head chunk descriptor. The caller must have 156 // locked the fingerprint of the memorySeries. This method will panic if this 157 // series has no chunk descriptors. 158 func (s *memorySeries) head() *desc { 159 return s.chunkDescs[len(s.chunkDescs)-1] 160 } 161 162 func (s *memorySeries) samplesForRange(from, through model.Time) ([]model.SamplePair, error) { 163 // Find first chunk with start time after "from". 164 fromIdx := sort.Search(len(s.chunkDescs), func(i int) bool { 165 return s.chunkDescs[i].FirstTime.After(from) 166 }) 167 // Find first chunk with start time after "through". 168 throughIdx := sort.Search(len(s.chunkDescs), func(i int) bool { 169 return s.chunkDescs[i].FirstTime.After(through) 170 }) 171 if fromIdx == len(s.chunkDescs) { 172 // Even the last chunk starts before "from". Find out if the 173 // series ends before "from" and we don't need to do anything. 174 lt := s.chunkDescs[len(s.chunkDescs)-1].LastTime 175 if lt.Before(from) { 176 return nil, nil 177 } 178 } 179 if fromIdx > 0 { 180 fromIdx-- 181 } 182 if throughIdx == len(s.chunkDescs) { 183 throughIdx-- 184 } 185 var values []model.SamplePair 186 in := metric.Interval{ 187 OldestInclusive: from, 188 NewestInclusive: through, 189 } 190 var reuseIter encoding.Iterator 191 for idx := fromIdx; idx <= throughIdx; idx++ { 192 cd := s.chunkDescs[idx] 193 reuseIter = cd.C.NewIterator(reuseIter) 194 chValues, err := encoding.RangeValues(reuseIter, in) 195 if err != nil { 196 return nil, err 197 } 198 values = append(values, chValues...) 199 } 200 return values, nil 201 } 202 203 func (s *memorySeries) setChunks(descs []*desc) error { 204 if len(s.chunkDescs) != 0 { 205 return fmt.Errorf("series already has chunks") 206 } 207 208 s.chunkDescs = descs 209 if len(descs) > 0 { 210 s.lastTime = descs[len(descs)-1].LastTime 211 } 212 return nil 213 } 214 215 func (s *memorySeries) isStale() bool { 216 return s.lastSampleValueSet && value.IsStaleNaN(float64(s.lastSampleValue)) 217 } 218 219 type desc struct { 220 C encoding.Chunk // nil if chunk is evicted. 221 FirstTime model.Time // Timestamp of first sample. Populated at creation. Immutable. 222 LastTime model.Time // Timestamp of last sample. Populated at creation & on append. 223 LastUpdate model.Time // This server's local time on last change 224 flushReason flushReason // If chunk is closed, holds the reason why. 225 flushed bool // set to true when flush succeeds 226 } 227 228 func newDesc(c encoding.Chunk, firstTime model.Time, lastTime model.Time) *desc { 229 return &desc{ 230 C: c, 231 FirstTime: firstTime, 232 LastTime: lastTime, 233 LastUpdate: model.Now(), 234 } 235 } 236 237 // Add adds a sample pair to the underlying chunk. For safe concurrent access, 238 // The chunk must be pinned, and the caller must have locked the fingerprint of 239 // the series. 240 func (d *desc) add(s model.SamplePair) (encoding.Chunk, error) { 241 cs, err := d.C.Add(s) 242 if err != nil { 243 return nil, err 244 } 245 246 if cs == nil { 247 d.LastTime = s.Timestamp // sample was added to this chunk 248 d.LastUpdate = model.Now() 249 } 250 251 return cs, nil 252 } 253 254 func (d *desc) slice(start, end model.Time) *desc { 255 return &desc{ 256 C: d.C.Slice(start, end), 257 FirstTime: start, 258 LastTime: end, 259 } 260 }