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 }