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  }