// add adds a sample pair to the series. It returns the number of newly // completed chunks (which are now eligible for persistence). // // The caller must have locked the fingerprint of the series. func (s *memorySeries) add(v model.SamplePair) (int, error) { if len(s.chunkDescs) == 0 || s.headChunkClosed { newHead := chunk.NewDesc(chunk.New(), v.Timestamp) s.chunkDescs = append(s.chunkDescs, newHead) s.headChunkClosed = false } else if s.headChunkUsedByIterator && s.head().RefCount() > 1 { // We only need to clone the head chunk if the current head // chunk was used in an iterator at all and if the refCount is // still greater than the 1 we always have because the head // chunk is not yet persisted. The latter is just an // approximation. We will still clone unnecessarily if an older // iterator using a previous version of the head chunk is still // around and keep the head chunk pinned. We needed to track // pins by version of the head chunk, which is probably not // worth the effort. chunk.Ops.WithLabelValues(chunk.Clone).Inc() // No locking needed here because a non-persisted head chunk can // not get evicted concurrently. s.head().C = s.head().C.Clone() s.headChunkUsedByIterator = false } chunks, err := s.head().Add(v) if err != nil { return 0, err } s.head().C = chunks[0] for _, c := range chunks[1:] { s.chunkDescs = append(s.chunkDescs, chunk.NewDesc(c, c.FirstTime())) } // Populate lastTime of now-closed chunks. for _, cd := range s.chunkDescs[len(s.chunkDescs)-len(chunks) : len(s.chunkDescs)-1] { cd.MaybePopulateLastTime() } s.lastTime = v.Timestamp s.lastSampleValue = v.Value s.lastSampleValueSet = true return len(chunks) - 1, nil }
// scan works like bufio.Scanner.Scan. func (hs *headsScanner) scan() bool { if hs.seriesCurrent == hs.seriesTotal || hs.err != nil { return false } var ( seriesFlags byte fpAsInt uint64 metric codable.Metric persistWatermark int64 modTimeNano int64 modTime time.Time chunkDescsOffset int64 savedFirstTime int64 numChunkDescs int64 firstTime int64 lastTime int64 encoding byte ch chunk.Chunk lastTimeHead model.Time ) if seriesFlags, hs.err = hs.r.ReadByte(); hs.err != nil { return false } headChunkPersisted := seriesFlags&flagHeadChunkPersisted != 0 if fpAsInt, hs.err = codable.DecodeUint64(hs.r); hs.err != nil { return false } hs.fp = model.Fingerprint(fpAsInt) if hs.err = metric.UnmarshalFromReader(hs.r); hs.err != nil { return false } if hs.version != headsFormatLegacyVersion { // persistWatermark only present in v2. persistWatermark, hs.err = binary.ReadVarint(hs.r) if hs.err != nil { return false } modTimeNano, hs.err = binary.ReadVarint(hs.r) if hs.err != nil { return false } if modTimeNano != -1 { modTime = time.Unix(0, modTimeNano) } } if chunkDescsOffset, hs.err = binary.ReadVarint(hs.r); hs.err != nil { return false } if savedFirstTime, hs.err = binary.ReadVarint(hs.r); hs.err != nil { return false } if numChunkDescs, hs.err = binary.ReadVarint(hs.r); hs.err != nil { return false } chunkDescs := make([]*chunk.Desc, numChunkDescs) if hs.version == headsFormatLegacyVersion { if headChunkPersisted { persistWatermark = numChunkDescs } else { persistWatermark = numChunkDescs - 1 } } headChunkClosed := true // Initial assumption. for i := int64(0); i < numChunkDescs; i++ { if i < persistWatermark { if firstTime, hs.err = binary.ReadVarint(hs.r); hs.err != nil { return false } if lastTime, hs.err = binary.ReadVarint(hs.r); hs.err != nil { return false } chunkDescs[i] = &chunk.Desc{ ChunkFirstTime: model.Time(firstTime), ChunkLastTime: model.Time(lastTime), } chunk.NumMemDescs.Inc() } else { // Non-persisted chunk. // If there are non-persisted chunks at all, we consider // the head chunk not to be closed yet. headChunkClosed = false if encoding, hs.err = hs.r.ReadByte(); hs.err != nil { return false } if ch, hs.err = chunk.NewForEncoding(chunk.Encoding(encoding)); hs.err != nil { return false } if hs.err = ch.Unmarshal(hs.r); hs.err != nil { return false } cd := chunk.NewDesc(ch, ch.FirstTime()) if i < numChunkDescs-1 { // This is NOT the head chunk. So it's a chunk // to be persisted, and we need to populate lastTime. hs.chunksToPersistTotal++ cd.MaybePopulateLastTime() } chunkDescs[i] = cd } } if lastTimeHead, hs.err = chunkDescs[len(chunkDescs)-1].LastTime(); hs.err != nil { return false } hs.series = &memorySeries{ metric: model.Metric(metric), chunkDescs: chunkDescs, persistWatermark: int(persistWatermark), modTime: modTime, chunkDescsOffset: int(chunkDescsOffset), savedFirstTime: model.Time(savedFirstTime), lastTime: lastTimeHead, headChunkClosed: headChunkClosed, } hs.seriesCurrent++ return true }