github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/head.go (about)

     1  package phlaredb
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"sync"
    10  	"time"
    11  
    12  	"connectrpc.com/connect"
    13  	"github.com/go-kit/log"
    14  	"github.com/go-kit/log/level"
    15  	"github.com/gogo/status"
    16  	"github.com/google/uuid"
    17  	"github.com/oklog/ulid/v2"
    18  	"github.com/pkg/errors"
    19  	"github.com/prometheus/common/model"
    20  	"github.com/prometheus/prometheus/model/labels"
    21  	"github.com/prometheus/prometheus/promql/parser"
    22  	"github.com/prometheus/prometheus/tsdb/fileutil"
    23  	"github.com/samber/lo"
    24  	"go.uber.org/atomic"
    25  	"google.golang.org/grpc/codes"
    26  
    27  	profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
    28  	ingestv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1"
    29  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    30  	"github.com/grafana/pyroscope/pkg/iter"
    31  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    32  	"github.com/grafana/pyroscope/pkg/phlaredb/block"
    33  	phlarelabels "github.com/grafana/pyroscope/pkg/phlaredb/labels"
    34  	schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1"
    35  	"github.com/grafana/pyroscope/pkg/phlaredb/symdb"
    36  	phlarecontext "github.com/grafana/pyroscope/pkg/pyroscope/context"
    37  )
    38  
    39  var defaultParquetConfig = &ParquetConfig{
    40  	MaxBufferRowCount: 100_000,
    41  	MaxRowGroupBytes:  10 * 128 * 1024 * 1024,
    42  	MaxBlockBytes:     10 * 10 * 128 * 1024 * 1024,
    43  }
    44  
    45  type Table interface {
    46  	Name() string
    47  	Size() uint64       // Size estimates the uncompressed byte size of the table in memory and on disk.
    48  	MemorySize() uint64 // MemorySize estimates the uncompressed byte size of the table in memory.
    49  	Init(path string, cfg *ParquetConfig, metrics *headMetrics) error
    50  	Flush(context.Context) (numRows uint64, numRowGroups uint64, err error)
    51  	Close() error
    52  }
    53  
    54  type Head struct {
    55  	logger  log.Logger
    56  	metrics *headMetrics
    57  	stopCh  chan struct{}
    58  	wg      sync.WaitGroup
    59  
    60  	headPath  string // path while block is actively appended to
    61  	localPath string // path once block has been cut
    62  
    63  	inFlightProfiles sync.WaitGroup // ongoing ingestion requests.
    64  	metaLock         sync.RWMutex
    65  	meta             *block.Meta
    66  
    67  	config        Config
    68  	parquetConfig *ParquetConfig
    69  	symdb         *symdb.SymDB
    70  	profiles      *profileStore
    71  	totalSamples  *atomic.Uint64
    72  	tables        []Table
    73  	delta         *deltaProfiles
    74  
    75  	limiter   TenantLimiter
    76  	updatedAt *atomic.Time
    77  }
    78  
    79  const (
    80  	pathHead          = "head"
    81  	PathLocal         = "local"
    82  	defaultFolderMode = 0o755
    83  )
    84  
    85  func NewHead(phlarectx context.Context, cfg Config, limiter TenantLimiter) (*Head, error) {
    86  	// todo if tenantLimiter is nil ....
    87  	parquetConfig := *defaultParquetConfig
    88  	h := &Head{
    89  		logger:  phlarecontext.Logger(phlarectx),
    90  		metrics: contextHeadMetrics(phlarectx),
    91  
    92  		stopCh: make(chan struct{}),
    93  
    94  		meta:         block.NewMeta(),
    95  		totalSamples: atomic.NewUint64(0),
    96  
    97  		config:        cfg,
    98  		parquetConfig: &parquetConfig,
    99  		limiter:       limiter,
   100  		updatedAt:     atomic.NewTime(time.Now()),
   101  	}
   102  	h.headPath = filepath.Join(cfg.DataPath, pathHead, h.meta.ULID.String())
   103  	h.localPath = filepath.Join(cfg.DataPath, PathLocal, h.meta.ULID.String())
   104  
   105  	if cfg.Parquet != nil {
   106  		h.parquetConfig = cfg.Parquet
   107  	}
   108  
   109  	h.parquetConfig.MaxRowGroupBytes = cfg.RowGroupTargetSize
   110  
   111  	// ensure folder is writable
   112  	err := os.MkdirAll(h.headPath, defaultFolderMode)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	// create profile store
   118  	h.profiles = newProfileStore(phlarectx)
   119  	h.delta = newDeltaProfiles()
   120  	h.tables = []Table{
   121  		h.profiles,
   122  	}
   123  	for _, t := range h.tables {
   124  		if err := t.Init(h.headPath, h.parquetConfig, h.metrics); err != nil {
   125  			return nil, err
   126  		}
   127  	}
   128  
   129  	symdbConfig := symdb.DefaultConfig()
   130  	if cfg.SymDBFormat == symdb.FormatV3 {
   131  		symdbConfig.Version = symdb.FormatV3
   132  		symdbConfig.Dir = h.headPath
   133  	} else {
   134  		symdbConfig.Version = symdb.FormatV2
   135  		symdbConfig.Dir = filepath.Join(h.headPath, symdb.DefaultDirName)
   136  		symdbConfig.Parquet = symdb.ParquetConfig{
   137  			MaxBufferRowCount: h.parquetConfig.MaxBufferRowCount,
   138  		}
   139  	}
   140  
   141  	h.symdb = symdb.NewSymDB(symdbConfig)
   142  
   143  	h.wg.Add(1)
   144  	go h.loop()
   145  
   146  	return h, nil
   147  }
   148  
   149  func (h *Head) MemorySize() uint64 {
   150  	// TODO: TSDB index
   151  	return h.profiles.MemorySize() + h.symdb.MemorySize()
   152  }
   153  
   154  func (h *Head) Size() uint64 {
   155  	// TODO: TSDB index
   156  	return h.profiles.Size() + h.symdb.MemorySize()
   157  }
   158  
   159  func (h *Head) loop() {
   160  	symdbMetricsUpdateTicker := time.NewTicker(5 * time.Second)
   161  	var memStats symdb.MemoryStats
   162  	defer func() {
   163  		symdbMetricsUpdateTicker.Stop()
   164  		h.wg.Done()
   165  	}()
   166  
   167  	for {
   168  		select {
   169  		case <-symdbMetricsUpdateTicker.C:
   170  			h.updateSymbolsMemUsage(&memStats)
   171  		case <-h.stopCh:
   172  			return
   173  		}
   174  	}
   175  }
   176  
   177  const StaleGracePeriod = 5 * time.Minute
   178  
   179  // isStale returns true if the head is stale and should be flushed.
   180  // The head is stale if it is older than the max time of the block provided as maxT.
   181  // And if the head has not been updated for 5 minutes.
   182  func (h *Head) isStale(maxT int64, now time.Time) bool {
   183  	// If we're still receiving data we'll wait 5 minutes before flushing.
   184  	if now.Sub(h.updatedAt.Load()) <= StaleGracePeriod {
   185  		return false
   186  	}
   187  	// Blocks that have pass their maxT range by 5min are stale
   188  	return now.After(time.Unix(0, maxT))
   189  }
   190  
   191  func (h *Head) Ingest(ctx context.Context, p *profilev1.Profile, id uuid.UUID, annotations []*typesv1.ProfileAnnotation, externalLabels ...*typesv1.LabelPair) error {
   192  	if len(p.Sample) == 0 {
   193  		level.Debug(h.logger).Log("msg", "profile has no samples", "labels", externalLabels)
   194  		return nil
   195  	}
   196  
   197  	delta := phlaremodel.Labels(externalLabels).Get(phlaremodel.LabelNameDelta) != "false"
   198  	externalLabels = phlaremodel.Labels(externalLabels).Delete(phlaremodel.LabelNameDelta)
   199  
   200  	otel := phlaremodel.Labels(externalLabels).Get(phlaremodel.LabelNameOTEL) == "true"
   201  	externalLabels = phlaremodel.Labels(externalLabels).Delete(phlaremodel.LabelNameOTEL)
   202  
   203  	enforceLabelOrder := phlaremodel.Labels(externalLabels).Get(phlaremodel.LabelNameOrder) == phlaremodel.LabelOrderEnforced
   204  	externalLabels = phlaremodel.Labels(externalLabels).Delete(phlaremodel.LabelNameOrder)
   205  
   206  	metricName := phlaremodel.Labels(externalLabels).Get(model.MetricNameLabel)
   207  	symbolsPartitionLabel := h.config.SymbolsPartitionLabel
   208  	if otel && symbolsPartitionLabel == "" {
   209  		symbolsPartitionLabel = phlaremodel.LabelNameServiceName
   210  	}
   211  	partition := phlaremodel.SymbolsPartitionForProfile(externalLabels, symbolsPartitionLabel, p)
   212  
   213  	lbls, seriesFingerprints := phlarelabels.CreateProfileLabels(enforceLabelOrder, p, externalLabels...)
   214  	for i, fp := range seriesFingerprints {
   215  		if err := h.limiter.AllowProfile(fp, lbls[i], p.TimeNanos); err != nil {
   216  			return err
   217  		}
   218  	}
   219  
   220  	var profileIngested bool
   221  	for idxType, profile := range h.symdb.WriteProfileSymbols(partition, p) {
   222  		profile.ID = id
   223  		profile.SeriesFingerprint = seriesFingerprints[idxType]
   224  		if delta && isDeltaSupported(lbls[idxType]) {
   225  			profile.Samples = h.delta.computeDelta(profile)
   226  		} else {
   227  			profile.Samples = profile.Samples.Compact(false)
   228  		}
   229  
   230  		profile.TotalValue = profile.Samples.Sum()
   231  
   232  		if profile.Samples.Len() == 0 {
   233  			level.Debug(h.logger).Log("msg", "profile is empty after delta computation", "metricName", metricName)
   234  			continue
   235  		}
   236  
   237  		profile.Annotations.Keys = make([]string, 0, len(annotations))
   238  		profile.Annotations.Values = make([]string, 0, len(annotations))
   239  		for i := range annotations {
   240  			profile.Annotations.Keys = append(profile.Annotations.Keys, annotations[i].Key)
   241  			profile.Annotations.Values = append(profile.Annotations.Values, annotations[i].Value)
   242  		}
   243  
   244  		if err := h.profiles.ingest(ctx, []schemav1.InMemoryProfile{profile}, lbls[idxType], metricName); err != nil {
   245  			return err
   246  		}
   247  
   248  		profileIngested = true
   249  		h.totalSamples.Add(uint64(profile.Samples.Len()))
   250  		h.metrics.sampleValuesIngested.WithLabelValues(metricName).Add(float64(profile.Samples.Len()))
   251  		h.metrics.sampleValuesReceived.WithLabelValues(metricName).Add(float64(len(p.Sample)))
   252  	}
   253  
   254  	if !profileIngested {
   255  		return nil
   256  	}
   257  
   258  	h.metaLock.Lock()
   259  	v := model.TimeFromUnixNano(p.TimeNanos)
   260  	if v < h.meta.MinTime {
   261  		h.meta.MinTime = v
   262  	}
   263  	if v > h.meta.MaxTime {
   264  		h.meta.MaxTime = v
   265  	}
   266  	h.metaLock.Unlock()
   267  
   268  	h.updatedAt.Store(time.Now())
   269  
   270  	return nil
   271  }
   272  
   273  // LabelValues returns the possible label values for a given label name.
   274  func (h *Head) LabelValues(ctx context.Context, req *connect.Request[typesv1.LabelValuesRequest]) (*connect.Response[typesv1.LabelValuesResponse], error) {
   275  	selectors, err := parseSelectors(req.Msg.Matchers)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  
   280  	// shortcut to index when matcher match all
   281  	if selectors.matchesAll() {
   282  		values, err := h.profiles.index.ix.LabelValues(req.Msg.Name, nil)
   283  		if err != nil {
   284  			return nil, err
   285  		}
   286  		return connect.NewResponse(&typesv1.LabelValuesResponse{
   287  			Names: values,
   288  		}), nil
   289  	}
   290  
   291  	// aggregate all label values from series matching, when matchers are given.
   292  
   293  	values := make(map[string]struct{})
   294  	if err := h.forMatchingSelectors(selectors, func(lbs phlaremodel.Labels, fp model.Fingerprint) error {
   295  		if v := lbs.Get(req.Msg.Name); v != "" {
   296  			values[v] = struct{}{}
   297  		}
   298  		return nil
   299  	}); err != nil {
   300  		return nil, err
   301  	}
   302  
   303  	return connect.NewResponse(&typesv1.LabelValuesResponse{
   304  		Names: lo.Keys(values),
   305  	}), nil
   306  }
   307  
   308  // LabelNames returns the possible label values for a given label name.
   309  func (h *Head) LabelNames(ctx context.Context, req *connect.Request[typesv1.LabelNamesRequest]) (*connect.Response[typesv1.LabelNamesResponse], error) {
   310  	selectors, err := parseSelectors(req.Msg.Matchers)
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	// shortcut to index when matcher match all
   316  	if selectors.matchesAll() {
   317  		values, err := h.profiles.index.ix.LabelNames(nil)
   318  		if err != nil {
   319  			return nil, err
   320  		}
   321  		sort.Strings(values)
   322  		return connect.NewResponse(&typesv1.LabelNamesResponse{
   323  			Names: values,
   324  		}), nil
   325  	}
   326  
   327  	// aggregate all label values from series matching, when matchers are given.
   328  	values := make(map[string]struct{})
   329  	if err := h.forMatchingSelectors(selectors, func(lbs phlaremodel.Labels, fp model.Fingerprint) error {
   330  		for _, lbl := range lbs {
   331  			values[lbl.Name] = struct{}{}
   332  		}
   333  		return nil
   334  	}); err != nil {
   335  		return nil, err
   336  	}
   337  
   338  	return connect.NewResponse(&typesv1.LabelNamesResponse{
   339  		Names: lo.Keys(values),
   340  	}), nil
   341  }
   342  
   343  func (h *Head) MustProfileTypeNames() []string {
   344  	ptypes, err := h.profiles.index.ix.LabelValues(phlaremodel.LabelNameProfileType, nil)
   345  	if err != nil {
   346  		panic(err)
   347  	}
   348  	sort.Strings(ptypes)
   349  	return ptypes
   350  }
   351  
   352  // ProfileTypes returns the possible profile types.
   353  func (h *Head) ProfileTypes(ctx context.Context, req *connect.Request[ingestv1.ProfileTypesRequest]) (*connect.Response[ingestv1.ProfileTypesResponse], error) {
   354  	values, err := h.profiles.index.ix.LabelValues(phlaremodel.LabelNameProfileType, nil)
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  	sort.Strings(values)
   359  
   360  	profileTypes := make([]*typesv1.ProfileType, len(values))
   361  	for i, v := range values {
   362  		tp, err := phlaremodel.ParseProfileTypeSelector(v)
   363  		if err != nil {
   364  			return nil, err
   365  		}
   366  		profileTypes[i] = tp
   367  	}
   368  
   369  	return connect.NewResponse(&ingestv1.ProfileTypesResponse{
   370  		ProfileTypes: profileTypes,
   371  	}), nil
   372  }
   373  
   374  func (h *Head) BlockID() string {
   375  	return h.meta.ULID.String()
   376  }
   377  
   378  func (h *Head) Bounds() (mint, maxt model.Time) {
   379  	h.metaLock.RLock()
   380  	defer h.metaLock.RUnlock()
   381  	return h.meta.MinTime, h.meta.MaxTime
   382  }
   383  
   384  // Returns underlying queries, the queriers should be roughly ordered in TS increasing order
   385  func (h *Head) Queriers() Queriers {
   386  	h.profiles.rowsLock.RLock()
   387  	defer h.profiles.rowsLock.RUnlock()
   388  
   389  	queriers := make([]Querier, 0, len(h.profiles.rowGroups)+1)
   390  	for idx := range h.profiles.rowGroups {
   391  		queriers = append(queriers, &headOnDiskQuerier{
   392  			head:        h,
   393  			rowGroupIdx: idx,
   394  		})
   395  	}
   396  	queriers = append(queriers, &headInMemoryQuerier{h})
   397  	return queriers
   398  }
   399  
   400  func (h *Head) Sort(in []Profile) []Profile {
   401  	return in
   402  }
   403  
   404  type ProfileSelectorIterator struct {
   405  	batch   chan []Profile
   406  	current iter.Iterator[Profile]
   407  	once    sync.Once
   408  }
   409  
   410  func NewProfileSelectorIterator() *ProfileSelectorIterator {
   411  	return &ProfileSelectorIterator{
   412  		batch: make(chan []Profile, 1),
   413  	}
   414  }
   415  
   416  func (it *ProfileSelectorIterator) Push(batch []Profile) {
   417  	if len(batch) == 0 {
   418  		return
   419  	}
   420  	it.batch <- batch
   421  }
   422  
   423  func (it *ProfileSelectorIterator) Next() bool {
   424  	if it.current == nil {
   425  		batch, ok := <-it.batch
   426  		if !ok {
   427  			return false
   428  		}
   429  		it.current = iter.NewSliceIterator(batch)
   430  	}
   431  	if !it.current.Next() {
   432  		it.current = nil
   433  		return it.Next()
   434  	}
   435  	return true
   436  }
   437  
   438  func (it *ProfileSelectorIterator) At() Profile {
   439  	if it.current == nil {
   440  		return ProfileWithLabels{}
   441  	}
   442  	return it.current.At()
   443  }
   444  
   445  func (it *ProfileSelectorIterator) Close() error {
   446  	it.once.Do(func() {
   447  		close(it.batch)
   448  	})
   449  	return nil
   450  }
   451  
   452  func (it *ProfileSelectorIterator) Err() error {
   453  	return nil
   454  }
   455  
   456  // selectors are composed of any amount of selectors which are ORed
   457  type selectors [][]*labels.Matcher
   458  
   459  func parseSelectors(selectorStrings []string) (selectors, error) {
   460  	sels := make([][]*labels.Matcher, 0, len(selectorStrings))
   461  	for _, m := range selectorStrings {
   462  		s, err := parser.ParseMetricSelector(m)
   463  		if err != nil {
   464  			return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to parse label selector: %v", err))
   465  		}
   466  		sels = append(sels, s)
   467  	}
   468  
   469  	return sels, nil
   470  }
   471  
   472  func (sels selectors) matchesAll() bool {
   473  	if len(sels) == 0 {
   474  		return true
   475  	}
   476  
   477  	for _, sel := range sels {
   478  		if len(sel) == 0 {
   479  			return true
   480  		}
   481  	}
   482  
   483  	return false
   484  }
   485  
   486  func (h *Head) forMatchingSelectors(sels selectors, fn func(lbs phlaremodel.Labels, fp model.Fingerprint) error) error {
   487  	if sels.matchesAll() {
   488  		return h.profiles.index.forMatchingLabels(nil, fn)
   489  	}
   490  
   491  	for _, sel := range sels {
   492  		if err := h.profiles.index.forMatchingLabels(sel, fn); err != nil {
   493  			return err
   494  		}
   495  	}
   496  
   497  	return nil
   498  }
   499  
   500  func (h *Head) Series(ctx context.Context, req *connect.Request[ingestv1.SeriesRequest]) (*connect.Response[ingestv1.SeriesResponse], error) {
   501  	selectors, err := parseSelectors(req.Msg.Matchers)
   502  	if err != nil {
   503  		return nil, err
   504  	}
   505  
   506  	// build up map of label names
   507  	labelNameMap := make(map[string]struct{}, len(req.Msg.LabelNames))
   508  	for _, labelName := range req.Msg.LabelNames {
   509  		labelNameMap[labelName] = struct{}{}
   510  	}
   511  
   512  	response := &ingestv1.SeriesResponse{}
   513  	uniqu := map[model.Fingerprint]struct{}{}
   514  	if err := h.forMatchingSelectors(selectors, func(lbs phlaremodel.Labels, fp model.Fingerprint) error {
   515  		if len(req.Msg.LabelNames) > 0 {
   516  			lbs = lbs.WithLabels(req.Msg.LabelNames...)
   517  			fp = model.Fingerprint(lbs.Hash())
   518  		}
   519  
   520  		if _, ok := uniqu[fp]; ok {
   521  			return nil
   522  		}
   523  		uniqu[fp] = struct{}{}
   524  		response.LabelsSet = append(response.LabelsSet, &typesv1.Labels{Labels: lbs})
   525  		return nil
   526  	}); err != nil {
   527  		return nil, err
   528  	}
   529  
   530  	sort.Slice(response.LabelsSet, func(i, j int) bool {
   531  		return phlaremodel.CompareLabelPairs(response.LabelsSet[i].Labels, response.LabelsSet[j].Labels) < 0
   532  	})
   533  
   534  	return connect.NewResponse(response), nil
   535  }
   536  
   537  // Flush closes the head and writes data to disk. No ingestion requests should
   538  // be made concurrently with the call, or after it returns.
   539  // The call is thread-safe for reads.
   540  func (h *Head) Flush(ctx context.Context) error {
   541  	close(h.stopCh)
   542  	h.wg.Wait()
   543  	start := time.Now()
   544  	defer func() {
   545  		h.metrics.flushedBlockDurationSeconds.Observe(time.Since(start).Seconds())
   546  	}()
   547  	if err := h.flush(ctx); err != nil {
   548  		h.metrics.flushedBlocks.WithLabelValues("failed").Inc()
   549  		return err
   550  	}
   551  	h.metrics.flushedBlocks.WithLabelValues("success").Inc()
   552  	return nil
   553  }
   554  
   555  func (h *Head) flush(ctx context.Context) error {
   556  	// Ensure all the in-flight ingestion requests have finished.
   557  	// It must be guaranteed that no new inserts will happen
   558  	// after the call start.
   559  	h.inFlightProfiles.Wait()
   560  	if h.profiles.index.totalProfiles.Load() == 0 {
   561  		level.Info(h.logger).Log("msg", "head empty - no block written")
   562  		return os.RemoveAll(h.headPath)
   563  	}
   564  
   565  	var blockSize uint64
   566  	files := make([]block.File, 0, 8)
   567  
   568  	// profiles
   569  	for _, t := range h.tables {
   570  		numRows, numRowGroups, err := t.Flush(ctx)
   571  		if err != nil {
   572  			return errors.Wrapf(err, "flushing table %s", t.Name())
   573  		}
   574  		h.metrics.rowsWritten.WithLabelValues(t.Name()).Add(float64(numRows))
   575  		f := block.File{
   576  			RelPath: t.Name() + block.ParquetSuffix,
   577  			Parquet: &block.ParquetFile{
   578  				NumRowGroups: numRowGroups,
   579  				NumRows:      numRows,
   580  			},
   581  		}
   582  		if err = t.Close(); err != nil {
   583  			return errors.Wrapf(err, "closing table %s", t.Name())
   584  		}
   585  		if stat, err := os.Stat(filepath.Join(h.headPath, f.RelPath)); err == nil {
   586  			f.SizeBytes = uint64(stat.Size())
   587  			blockSize += f.SizeBytes
   588  			h.metrics.flushedFileSizeBytes.WithLabelValues(t.Name()).Observe(float64(f.SizeBytes))
   589  		}
   590  		files = append(files, f)
   591  	}
   592  
   593  	// symdb
   594  	if err := h.symdb.Flush(); err != nil {
   595  		return errors.Wrap(err, "flushing symdb")
   596  	}
   597  	for _, file := range h.symdb.Files() {
   598  		// Files' path is relative to the symdb dir.
   599  		if h.symdb.FormatVersion() == symdb.FormatV2 {
   600  			file.RelPath = filepath.Join(symdb.DefaultDirName, file.RelPath)
   601  		}
   602  		files = append(files, file)
   603  		blockSize += file.SizeBytes
   604  		h.metrics.flushedFileSizeBytes.WithLabelValues(file.RelPath).Observe(float64(file.SizeBytes))
   605  	}
   606  
   607  	// tsdb
   608  	h.meta.Stats.NumSeries = uint64(h.profiles.index.totalSeries.Load())
   609  	f := block.File{
   610  		RelPath: block.IndexFilename,
   611  		TSDB: &block.TSDBFile{
   612  			NumSeries: h.meta.Stats.NumSeries,
   613  		},
   614  	}
   615  	h.metrics.flushedBlockSeries.Observe(float64(h.meta.Stats.NumSeries))
   616  	if stat, err := os.Stat(filepath.Join(h.headPath, block.IndexFilename)); err == nil {
   617  		f.SizeBytes = uint64(stat.Size())
   618  		blockSize += f.SizeBytes
   619  		h.metrics.flushedFileSizeBytes.WithLabelValues("tsdb").Observe(float64(f.SizeBytes))
   620  	}
   621  	files = append(files, f)
   622  
   623  	h.metrics.flushedBlockSizeBytes.Observe(float64(blockSize))
   624  	sort.Slice(files, func(i, j int) bool {
   625  		return files[i].RelPath < files[j].RelPath
   626  	})
   627  	h.meta.Files = files
   628  	h.meta.Stats.NumProfiles = uint64(h.profiles.index.totalProfiles.Load())
   629  	h.meta.Stats.NumSamples = h.totalSamples.Load()
   630  	h.meta.Compaction.Sources = []ulid.ULID{h.meta.ULID}
   631  	h.meta.Compaction.Level = 1
   632  	h.metrics.flushedBlockSamples.Observe(float64(h.meta.Stats.NumSamples))
   633  	h.metrics.flusehdBlockProfiles.Observe(float64(h.meta.Stats.NumProfiles))
   634  
   635  	sort.Slice(files, func(i, j int) bool { return files[i].RelPath < files[j].RelPath })
   636  	if _, err := h.meta.WriteToFile(h.logger, h.headPath); err != nil {
   637  		return err
   638  	}
   639  	h.metrics.blockDurationSeconds.Observe(h.meta.MaxTime.Sub(h.meta.MinTime).Seconds())
   640  	return nil
   641  }
   642  
   643  // Move moves the head directory to local blocks. The call is not thread-safe:
   644  // no concurrent reads and writes are allowed.
   645  //
   646  // After the call, head in-memory representation is not valid and should not
   647  // be accessed for querying.
   648  func (h *Head) Move() error {
   649  	// Remove intermediate row groups before the move as they are still
   650  	// referencing files on the disk.
   651  	if err := h.profiles.DeleteRowGroups(); err != nil {
   652  		return err
   653  	}
   654  
   655  	// move block to the local directory
   656  	if err := os.MkdirAll(filepath.Dir(h.localPath), defaultFolderMode); err != nil {
   657  		return err
   658  	}
   659  	if err := fileutil.Rename(h.headPath, h.localPath); err != nil {
   660  		return err
   661  	}
   662  
   663  	level.Info(h.logger).Log("msg", "head successfully written to block", "block_path", h.localPath)
   664  	return nil
   665  }
   666  
   667  func (h *Head) updateSymbolsMemUsage(memStats *symdb.MemoryStats) {
   668  	h.symdb.WriteMemoryStats(memStats)
   669  	m := h.metrics.sizeBytes
   670  	m.WithLabelValues("stacktraces").Set(float64(memStats.StacktracesSize))
   671  	m.WithLabelValues("locations").Set(float64(memStats.LocationsSize))
   672  	m.WithLabelValues("functions").Set(float64(memStats.FunctionsSize))
   673  	m.WithLabelValues("mappings").Set(float64(memStats.MappingsSize))
   674  	m.WithLabelValues("strings").Set(float64(memStats.StringsSize))
   675  }
   676  
   677  func (h *Head) GetMetaStats() block.MetaStats {
   678  	h.metaLock.RLock()
   679  	defer h.metaLock.RUnlock()
   680  	return h.meta.GetStats()
   681  }
   682  
   683  func (h *Head) Meta() *block.Meta {
   684  	return h.meta
   685  }
   686  
   687  func (h *Head) LocalPathFor(relPath string) string {
   688  	return filepath.Join(h.localPath, relPath)
   689  }