github.com/grafana/pyroscope@v1.18.0/pkg/segmentwriter/memdb/head.go (about) 1 package memdb 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "math" 8 "sync" 9 10 "github.com/google/uuid" 11 "github.com/prometheus/client_golang/prometheus" 12 "github.com/prometheus/common/model" 13 "go.uber.org/atomic" 14 15 profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" 16 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 17 phlaremodel "github.com/grafana/pyroscope/pkg/model" 18 "github.com/grafana/pyroscope/pkg/phlaredb/labels" 19 schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" 20 "github.com/grafana/pyroscope/pkg/phlaredb/symdb" 21 ) 22 23 type FlushedHead struct { 24 Index []byte 25 Profiles []byte 26 Symbols []byte 27 Unsymbolized bool 28 Meta struct { 29 ProfileTypeNames []string 30 MinTimeNanos int64 31 MaxTimeNanos int64 32 NumSamples uint64 33 NumProfiles uint64 34 NumSeries uint64 35 } 36 } 37 38 type Head struct { 39 symbols *symdb.PartitionWriter 40 metaLock sync.RWMutex 41 minTimeNanos int64 42 maxTimeNanos int64 43 totalSamples *atomic.Uint64 44 profiles *profilesIndex 45 metrics *HeadMetrics 46 } 47 48 func NewHead(metrics *HeadMetrics) *Head { 49 h := &Head{ 50 metrics: metrics, 51 symbols: symdb.NewPartitionWriter(0, &symdb.Config{ 52 Version: symdb.FormatV3, 53 }), 54 totalSamples: atomic.NewUint64(0), 55 minTimeNanos: math.MaxInt64, 56 maxTimeNanos: 0, 57 profiles: newProfileIndex(metrics), 58 } 59 60 return h 61 } 62 63 func (h *Head) Ingest(p *profilev1.Profile, id uuid.UUID, externalLabels []*typesv1.LabelPair, annotations []*typesv1.ProfileAnnotation) { 64 if len(p.Sample) == 0 { 65 return 66 } 67 68 // Delta not supported. 69 externalLabels = phlaremodel.Labels(externalLabels).Delete(phlaremodel.LabelNameDelta) 70 // Label order is enforced to ensure that __profile_type__ and __service_name__ always 71 // come first in the label set. This is important for spatial locality: profiles are 72 // stored in the label series order. 73 externalLabels = phlaremodel.Labels(externalLabels).Delete(phlaremodel.LabelNameOrder) 74 75 lbls, seriesFingerprints := labels.CreateProfileLabels(true, p, externalLabels...) 76 metricName := phlaremodel.Labels(externalLabels).Get(model.MetricNameLabel) 77 78 var profileIngested bool 79 memProfiles := h.symbols.WriteProfileSymbols(p) 80 for idxType := range memProfiles { 81 profile := &memProfiles[idxType] 82 profile.ID = id 83 profile.SeriesFingerprint = seriesFingerprints[idxType] 84 profile.Samples = profile.Samples.Compact(false) 85 86 profile.TotalValue = profile.Samples.Sum() 87 88 profile.Annotations.Keys = make([]string, 0, len(annotations)) 89 profile.Annotations.Values = make([]string, 0, len(annotations)) 90 for _, annotation := range annotations { 91 profile.Annotations.Keys = append(profile.Annotations.Keys, annotation.Key) 92 profile.Annotations.Values = append(profile.Annotations.Values, annotation.Value) 93 } 94 95 if profile.Samples.Len() == 0 { 96 continue 97 } 98 99 h.profiles.Add(profile, lbls[idxType], metricName) 100 101 profileIngested = true 102 h.totalSamples.Add(uint64(profile.Samples.Len())) 103 h.metrics.sampleValuesIngested.WithLabelValues(metricName).Add(float64(profile.Samples.Len())) 104 h.metrics.sampleValuesReceived.WithLabelValues(metricName).Add(float64(len(p.Sample))) 105 } 106 107 if !profileIngested { 108 return 109 } 110 111 h.metaLock.Lock() 112 if p.TimeNanos < h.minTimeNanos { 113 h.minTimeNanos = p.TimeNanos 114 } 115 if p.TimeNanos > h.maxTimeNanos { 116 h.maxTimeNanos = p.TimeNanos 117 } 118 h.metaLock.Unlock() 119 } 120 121 func (h *Head) Flush(ctx context.Context) (res *FlushedHead, err error) { 122 t := prometheus.NewTimer(h.metrics.flushedBlockDurationSeconds) 123 defer t.ObserveDuration() 124 125 if res, err = h.flush(ctx); err != nil { 126 h.metrics.flushedBlocks.WithLabelValues("failed").Inc() 127 return nil, err 128 } 129 130 blockSize := len(res.Index) + len(res.Profiles) + len(res.Symbols) 131 h.metrics.flushedBlocks.WithLabelValues("success").Inc() 132 133 unsymbolized := "false" 134 if res.Unsymbolized { 135 unsymbolized = "true" 136 } 137 138 h.metrics.flushedBlocksUnsymbolized.WithLabelValues(unsymbolized).Inc() 139 h.metrics.flushedBlockSamples.Observe(float64(res.Meta.NumSamples)) 140 h.metrics.flusehdBlockProfiles.Observe(float64(res.Meta.NumProfiles)) 141 h.metrics.flushedBlockSeries.Observe(float64(res.Meta.NumSeries)) 142 h.metrics.flushedBlockSizeBytes.Observe(float64(blockSize)) 143 h.metrics.flushedFileSizeBytes.WithLabelValues("tsdb").Observe(float64(len(res.Index))) 144 h.metrics.flushedFileSizeBytes.WithLabelValues("profiles.parquet").Observe(float64(len(res.Profiles))) 145 h.metrics.flushedFileSizeBytes.WithLabelValues("symbols.symdb").Observe(float64(len(res.Symbols))) 146 return res, nil 147 } 148 149 func (h *Head) flush(ctx context.Context) (*FlushedHead, error) { 150 var ( 151 err error 152 profiles []schemav1.InMemoryProfile 153 ) 154 res := new(FlushedHead) 155 res.Meta.MinTimeNanos = h.minTimeNanos 156 res.Meta.MaxTimeNanos = h.maxTimeNanos 157 res.Meta.NumSamples = h.totalSamples.Load() 158 res.Meta.NumSeries = uint64(h.profiles.totalSeries.Load()) 159 160 if res.Meta.NumSamples == 0 { 161 return res, nil 162 } 163 164 res.Unsymbolized = HasUnsymbolizedProfiles(h.symbols.Symbols()) 165 166 symbolsBuffer := bytes.NewBuffer(nil) 167 if err := symdb.WritePartition(h.symbols, symbolsBuffer); err != nil { 168 return nil, err 169 } 170 res.Symbols = symbolsBuffer.Bytes() 171 172 if res.Meta.ProfileTypeNames, err = h.profiles.profileTypeNames(); err != nil { 173 return nil, fmt.Errorf("failed to get profile type names: %w", err) 174 } 175 176 if res.Index, profiles, err = h.profiles.Flush(ctx); err != nil { 177 return nil, fmt.Errorf("failed to flush profiles: %w", err) 178 } 179 res.Meta.NumProfiles = uint64(len(profiles)) 180 181 if res.Profiles, err = WriteProfiles(h.metrics, profiles); err != nil { 182 return nil, fmt.Errorf("failed to write profiles parquet: %w", err) 183 } 184 return res, nil 185 } 186 187 // TODO: move into the symbolizer package when available 188 func HasUnsymbolizedProfiles(symbols *symdb.Symbols) bool { 189 locations := symbols.Locations 190 mappings := symbols.Mappings 191 for _, loc := range locations { 192 if !mappings[loc.MappingId].HasFunctions { 193 return true 194 } 195 } 196 return false 197 }