github.com/grafana/pyroscope@v1.18.0/pkg/block/dataset.go (about)

     1  package block
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/grafana/dskit/multierror"
     8  	"github.com/parquet-go/parquet-go"
     9  	"golang.org/x/sync/errgroup"
    10  
    11  	metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1"
    12  	"github.com/grafana/pyroscope/pkg/objstore"
    13  	"github.com/grafana/pyroscope/pkg/objstore/providers/memory"
    14  	"github.com/grafana/pyroscope/pkg/phlaredb"
    15  	"github.com/grafana/pyroscope/pkg/phlaredb/symdb"
    16  	"github.com/grafana/pyroscope/pkg/util"
    17  	"github.com/grafana/pyroscope/pkg/util/bufferpool"
    18  	"github.com/grafana/pyroscope/pkg/util/refctr"
    19  )
    20  
    21  type DatasetFormat uint32
    22  
    23  const (
    24  	DatasetFormat0 DatasetFormat = iota
    25  	DatasetFormat1
    26  )
    27  
    28  type Section uint32
    29  
    30  const (
    31  	SectionProfiles Section = iota
    32  	SectionTSDB
    33  	SectionSymbols
    34  	SectionDatasetIndex
    35  )
    36  
    37  type sectionDesc struct {
    38  	// The section entry index in the table of contents.
    39  	index int
    40  	// The name is only used in log and error messages.
    41  	name string
    42  }
    43  
    44  var (
    45  	// Format: section => desc
    46  	sections = [...][]sectionDesc{
    47  		DatasetFormat0: {
    48  			SectionProfiles: sectionDesc{index: 0, name: "profiles"},
    49  			SectionTSDB:     sectionDesc{index: 1, name: "tsdb"},
    50  			SectionSymbols:  sectionDesc{index: 2, name: "symbols"},
    51  		},
    52  		DatasetFormat1: {
    53  			// The dataset index can be used instead of the tsdb section of the
    54  			// dataset in cases where SeriesIndex is not used. Therefore, it has
    55  			// an alias record: if a query accesses the tsdb index of the dataset,
    56  			// it will access the tenant-wide dataset index.
    57  			SectionDatasetIndex: sectionDesc{index: 0, name: "dataset_tsdb_index"},
    58  			SectionTSDB:         sectionDesc{index: 0, name: "dataset_tsdb_index"},
    59  		},
    60  	}
    61  )
    62  
    63  func (sc Section) open(ctx context.Context, s *Dataset) (err error) {
    64  	switch sc {
    65  	case SectionTSDB:
    66  		return openTSDB(ctx, s)
    67  	case SectionSymbols:
    68  		return openSymbols(ctx, s)
    69  	case SectionProfiles:
    70  		return openProfileTable(ctx, s)
    71  	case SectionDatasetIndex:
    72  		return openDatasetIndex(ctx, s)
    73  	default:
    74  		panic(fmt.Sprintf("bug: unknown section: %d", sc))
    75  	}
    76  }
    77  
    78  type Dataset struct {
    79  	tenant string
    80  	name   string
    81  
    82  	meta *metastorev1.Dataset
    83  	obj  *Object
    84  
    85  	refs refctr.Counter
    86  	buf  *bufferpool.Buffer
    87  	err  error
    88  
    89  	tsdb     *tsdbBuffer
    90  	symbols  *symdb.Reader
    91  	profiles *ParquetFile
    92  
    93  	memSize int
    94  }
    95  
    96  func NewDataset(meta *metastorev1.Dataset, obj *Object) *Dataset {
    97  	return &Dataset{
    98  		tenant:  obj.meta.StringTable[meta.Tenant],
    99  		name:    obj.meta.StringTable[meta.Name],
   100  		meta:    meta,
   101  		obj:     obj,
   102  		memSize: defaultTenantDatasetSizeLoadInMemory,
   103  	}
   104  }
   105  
   106  type DatasetOption func(*Dataset)
   107  
   108  func WithDatasetMaxSizeLoadInMemory(size int) DatasetOption {
   109  	return func(s *Dataset) {
   110  		s.memSize = size
   111  	}
   112  }
   113  
   114  // Open opens the dataset, initializing the sections specified.
   115  //
   116  // Open may be called multiple times concurrently, but the dataset
   117  // is only initialized once. While it is possible to open the dataset
   118  // repeatedly after close, the caller must pass the failure reason to
   119  // the CloseWithError call, preventing further use, if applicable.
   120  func (s *Dataset) Open(ctx context.Context, sections ...Section) error {
   121  	return s.refs.IncErr(func() error {
   122  		if err := s.open(ctx, sections...); err != nil {
   123  			return fmt.Errorf("%w (%s)", err, s.obj.meta.Id)
   124  		}
   125  		return nil
   126  	})
   127  }
   128  
   129  func (s *Dataset) open(ctx context.Context, sections ...Section) (err error) {
   130  	if s.err != nil {
   131  		// The dataset has already been closed with an error.
   132  		return s.err
   133  	}
   134  	if err = s.obj.Open(ctx); err != nil {
   135  		return fmt.Errorf("failed to open object: %w", err)
   136  	}
   137  	defer func() {
   138  		// Close the object here because the dataset won't be
   139  		// closed if it fails to open.
   140  		if err != nil {
   141  			_ = s.closeErr(err)
   142  		}
   143  	}()
   144  	if s.obj.buf == nil && s.meta.Size < uint64(s.memSize) {
   145  		s.buf = bufferpool.GetBuffer(int(s.meta.Size))
   146  		off, size := int64(s.offset()), int64(s.meta.Size)
   147  		if err = objstore.ReadRange(ctx, s.buf, s.obj.path, s.obj.storage, off, size); err != nil {
   148  			return fmt.Errorf("loading sections into memory: %w", err)
   149  		}
   150  	}
   151  	g, ctx := errgroup.WithContext(ctx)
   152  	for _, sc := range sections {
   153  		sc := sc
   154  		g.Go(util.RecoverPanic(func() error {
   155  			if openErr := sc.open(ctx, s); openErr != nil {
   156  				return fmt.Errorf("opening section %v: %w", s.section(sc).name, openErr)
   157  			}
   158  			return nil
   159  		}))
   160  	}
   161  	return g.Wait()
   162  }
   163  
   164  func (s *Dataset) Close() error { return s.CloseWithError(nil) }
   165  
   166  // CloseWithError closes the dataset and disposes all the resources
   167  // associated with it.
   168  //
   169  // Any further attempts to open the dataset will return the provided error.
   170  func (s *Dataset) CloseWithError(err error) (closeErr error) {
   171  	s.refs.Dec(func() {
   172  		closeErr = s.closeErr(err)
   173  	})
   174  	return closeErr
   175  }
   176  
   177  func (s *Dataset) closeErr(err error) error {
   178  	s.err = err
   179  	if s.buf != nil {
   180  		bufferpool.Put(s.buf)
   181  		s.buf = nil
   182  	}
   183  	var merr multierror.MultiError
   184  	if s.tsdb != nil {
   185  		merr.Add(s.tsdb.Close())
   186  	}
   187  	if s.symbols != nil {
   188  		merr.Add(s.symbols.Close())
   189  	}
   190  	if s.profiles != nil {
   191  		merr.Add(s.profiles.Close())
   192  	}
   193  	if s.obj != nil {
   194  		merr.Add(s.obj.CloseWithError(err))
   195  	}
   196  	return merr.Err()
   197  }
   198  
   199  func (s *Dataset) TenantID() string { return s.tenant }
   200  
   201  func (s *Dataset) Name() string { return s.name }
   202  
   203  func (s *Dataset) Metadata() *metastorev1.Dataset { return s.meta }
   204  
   205  func (s *Dataset) Profiles() *ParquetFile { return s.profiles }
   206  
   207  func (s *Dataset) ProfileRowReader() parquet.RowReader { return s.profiles.RowReader() }
   208  
   209  func (s *Dataset) Symbols() symdb.SymbolsReader { return s.symbols }
   210  
   211  func (s *Dataset) Index() phlaredb.IndexReader { return s.tsdb.index }
   212  
   213  // Offset of the dataset section within the object.
   214  func (s *Dataset) offset() uint64 { return s.meta.TableOfContents[0] }
   215  
   216  func (s *Dataset) section(sc Section) sectionDesc {
   217  	if int(s.meta.Format) >= len(sections) {
   218  		panic(fmt.Sprintf("bug: unknown dataset format: %d", s.meta.Format))
   219  	}
   220  	f := sections[s.meta.Format]
   221  	if int(sc) >= len(f) {
   222  		panic(fmt.Sprintf("bug: invalid section index: %d", int(sc)))
   223  	}
   224  	return f[sc]
   225  }
   226  
   227  func (s *Dataset) sectionOffset(sc Section) int64 {
   228  	return int64(s.meta.TableOfContents[s.section(sc).index])
   229  }
   230  
   231  func (s *Dataset) sectionSize(sc Section) int64 {
   232  	idx := s.section(sc).index
   233  	off := s.meta.TableOfContents[idx]
   234  	var next uint64
   235  	if idx == len(s.meta.TableOfContents)-1 {
   236  		next = s.offset() + s.meta.Size
   237  	} else {
   238  		next = s.meta.TableOfContents[idx+1]
   239  	}
   240  	return int64(next - off)
   241  }
   242  
   243  func (s *Dataset) inMemoryBuffer() []byte {
   244  	if s.obj.buf != nil {
   245  		// If the entire object is loaded into memory,
   246  		// return the dataset sub-slice.
   247  		lo := s.offset()
   248  		hi := lo + s.meta.Size
   249  		buf := s.obj.buf.B
   250  		return buf[lo:hi]
   251  	}
   252  	if s.buf != nil {
   253  		return s.buf.B
   254  	}
   255  	return nil
   256  }
   257  
   258  func (s *Dataset) inMemoryBucket(buf []byte) objstore.Bucket {
   259  	bucket := memory.NewInMemBucket()
   260  	bucket.Set(s.obj.path, buf)
   261  	return objstore.NewBucket(bucket)
   262  }