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 }