github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/symdb/format.go (about) 1 //nolint:unused 2 package symdb 3 4 import ( 5 "bytes" 6 "encoding/binary" 7 "fmt" 8 "hash/crc32" 9 "io" 10 "unsafe" 11 12 "github.com/parquet-go/parquet-go/encoding/delta" 13 14 "github.com/grafana/pyroscope/pkg/slices" 15 ) 16 17 // V1 and V2: 18 // 19 // The database is a collection of files. The only file that is guaranteed 20 // to be present is the index file: it indicates the version of the format, 21 // and the structure of the database contents. The file is supposed to be 22 // read into memory entirely and opened with an OpenIndex call. 23 24 // V3: 25 // 26 // The database is a single file. The file consists of the following sections: 27 // [Data ] 28 // [Index ] 29 // [Footer] 30 // 31 // The file is supposed to be open with Open call: it reads the footer, locates 32 // index section, and fetches it into memory. 33 // 34 // Data section is version specific. 35 // v3: Partitions. 36 // 37 // Index section is structured in the following way: 38 // 39 // [IndexHeader] Header defines the format version and denotes the content type. 40 // [TOC ] Table of contents. Its entries refer to the Data section. 41 // It is of a fixed size for a given version (number of entries). 42 // [Data ] Data is an arbitrary structured section. The exact structure is 43 // defined by the TOC and Header (version, flags, etc). 44 // v1: StacktraceChunkHeaders. 45 // v2: PartitionHeadersV2. 46 // v3: PartitionHeadersV3. 47 // [CRC32 ] Checksum. 48 // 49 // Footer section is version agnostic and is only needed to locate 50 // the index offset within the file. 51 52 // In all version big endian order is used unless otherwise noted. 53 54 const ( 55 DefaultFileName = "symbols.symdb" // Added in v3. 56 57 // Pre-v3 assets. Left for compatibility reasons. 58 59 DefaultDirName = "symbols" 60 IndexFileName = "index.symdb" 61 StacktracesFileName = "stacktraces.symdb" 62 ) 63 64 type FormatVersion uint32 65 66 const ( 67 // Within a database, the same format version 68 // must be used in all places. 69 _ FormatVersion = iota 70 71 FormatV1 72 FormatV2 73 FormatV3 74 75 unknownVersion 76 ) 77 78 const ( 79 // TOC entries are version-specific. 80 // The constants point to the entry index in the TOC. 81 tocEntryStacktraceChunkHeaders = 0 82 tocEntryPartitionHeaders = 0 83 84 // Total number of entries in the current version. 85 // TODO(kolesnikovae): TOC size is version specific, 86 // but at the moment, all versions have the same size: 1. 87 tocEntriesTotal = 1 88 ) 89 90 // https://en.wikipedia.org/wiki/List_of_file_signatures 91 var symdbMagic = [4]byte{'s', 'y', 'm', '1'} 92 93 var castagnoli = crc32.MakeTable(crc32.Castagnoli) 94 95 const ( 96 checksumSize = 4 97 indexChecksumOffset = -checksumSize 98 ) 99 100 var ( 101 ErrInvalidSize = &FormatError{fmt.Errorf("invalid size")} 102 ErrInvalidCRC = &FormatError{fmt.Errorf("invalid CRC")} 103 ErrInvalidMagic = &FormatError{fmt.Errorf("invalid magic number")} 104 ErrUnknownVersion = &FormatError{fmt.Errorf("unknown version")} 105 ) 106 107 type FormatError struct{ err error } 108 109 func (e *FormatError) Error() string { 110 return e.err.Error() 111 } 112 113 type IndexFile struct { 114 Header IndexHeader 115 TOC TOC 116 117 // Version-specific. 118 PartitionHeaders PartitionHeaders 119 120 CRC uint32 // Checksum of the index. 121 } 122 123 // NOTE(kolesnikovae): IndexHeader is rudimentary and is left for compatibility. 124 125 type IndexHeader struct { 126 Magic [4]byte 127 Version FormatVersion 128 _ [4]byte // Reserved for future use. 129 _ [4]byte // Reserved for future use. 130 } 131 132 const IndexHeaderSize = int(unsafe.Sizeof(IndexHeader{})) 133 134 func (h *IndexHeader) MarshalBinary() []byte { 135 b := make([]byte, IndexHeaderSize) 136 copy(b[0:4], h.Magic[:]) 137 binary.BigEndian.PutUint32(b[4:8], uint32(h.Version)) 138 return b 139 } 140 141 func (h *IndexHeader) UnmarshalBinary(b []byte) error { 142 if len(b) != IndexHeaderSize { 143 return ErrInvalidSize 144 } 145 if copy(h.Magic[:], b[0:4]); !bytes.Equal(h.Magic[:], symdbMagic[:]) { 146 return ErrInvalidMagic 147 } 148 h.Version = FormatVersion(binary.BigEndian.Uint32(b[4:8])) 149 if h.Version >= unknownVersion { 150 return ErrUnknownVersion 151 } 152 return nil 153 } 154 155 type Footer struct { 156 Magic [4]byte 157 Version FormatVersion 158 IndexOffset uint64 // Index header offset in the file. 159 _ [4]byte // Reserved for future use. 160 CRC uint32 // CRC of the footer. 161 } 162 163 const FooterSize = int(unsafe.Sizeof(Footer{})) 164 165 func (f *Footer) MarshalBinary() []byte { 166 b := make([]byte, FooterSize) 167 copy(b[0:4], f.Magic[:]) 168 binary.BigEndian.PutUint32(b[4:8], uint32(f.Version)) 169 binary.BigEndian.PutUint64(b[8:16], f.IndexOffset) 170 binary.BigEndian.PutUint32(b[16:20], 0) 171 binary.BigEndian.PutUint32(b[20:24], crc32.Checksum(b[0:20], castagnoli)) 172 return b 173 } 174 175 func (f *Footer) UnmarshalBinary(b []byte) error { 176 if len(b) != FooterSize { 177 return ErrInvalidSize 178 } 179 if copy(f.Magic[:], b[0:4]); !bytes.Equal(f.Magic[:], symdbMagic[:]) { 180 return ErrInvalidMagic 181 } 182 f.Version = FormatVersion(binary.BigEndian.Uint32(b[4:8])) 183 if f.Version >= unknownVersion { 184 return ErrUnknownVersion 185 } 186 f.IndexOffset = binary.BigEndian.Uint64(b[8:16]) 187 f.CRC = binary.BigEndian.Uint32(b[20:24]) 188 if crc32.Checksum(b[0:20], castagnoli) != f.CRC { 189 return ErrInvalidCRC 190 } 191 return nil 192 } 193 194 // Table of contents. 195 196 const tocEntrySize = int(unsafe.Sizeof(TOCEntry{})) 197 198 type TOC struct { 199 Entries []TOCEntry 200 } 201 202 // TOCEntry refers to a section within the index. 203 // Offset is relative to the header offset. 204 type TOCEntry struct { 205 Offset int64 206 Size int64 207 } 208 209 func (toc *TOC) Size() int { 210 return tocEntrySize * tocEntriesTotal 211 } 212 213 func (toc *TOC) MarshalBinary() ([]byte, error) { 214 b := make([]byte, len(toc.Entries)*tocEntrySize) 215 for i := range toc.Entries { 216 toc.Entries[i].marshal(b[i*tocEntrySize:]) 217 } 218 return b, nil 219 } 220 221 func (toc *TOC) UnmarshalBinary(b []byte) error { 222 s := len(b) 223 if s < tocEntrySize || s%tocEntrySize > 0 { 224 return ErrInvalidSize 225 } 226 toc.Entries = make([]TOCEntry, s/tocEntrySize) 227 for i := range toc.Entries { 228 off := i * tocEntrySize 229 toc.Entries[i].unmarshal(b[off : off+tocEntrySize]) 230 } 231 return nil 232 } 233 234 func (h *TOCEntry) marshal(b []byte) { 235 binary.BigEndian.PutUint64(b[0:8], uint64(h.Size)) 236 binary.BigEndian.PutUint64(b[8:16], uint64(h.Offset)) 237 } 238 239 func (h *TOCEntry) unmarshal(b []byte) { 240 h.Size = int64(binary.BigEndian.Uint64(b[0:8])) 241 h.Offset = int64(binary.BigEndian.Uint64(b[8:16])) 242 } 243 244 type PartitionHeaders []*PartitionHeader 245 246 type PartitionHeader struct { 247 Partition uint64 248 // TODO(kolesnikovae): Switch to SymbolsBlock encoding. 249 Stacktraces []StacktraceBlockHeader 250 V2 *PartitionHeaderV2 251 V3 *PartitionHeaderV3 252 } 253 254 func (h *PartitionHeaders) Size() int64 { 255 s := int64(4) 256 for _, p := range *h { 257 s += p.Size() 258 } 259 return s 260 } 261 262 func (h *PartitionHeaders) MarshalV3To(dst io.Writer) (_ int64, err error) { 263 w := withWriterOffset(dst) 264 buf := make([]byte, 4, 128) 265 binary.BigEndian.PutUint32(buf, uint32(len(*h))) 266 w.write(buf) 267 for _, p := range *h { 268 buf = slices.GrowLen(buf, int(p.Size())) 269 p.marshalV3(buf) 270 w.write(buf) 271 } 272 return w.offset, w.err 273 } 274 275 func (h *PartitionHeaders) MarshalV2To(dst io.Writer) (_ int64, err error) { 276 w := withWriterOffset(dst) 277 buf := make([]byte, 4, 128) 278 binary.BigEndian.PutUint32(buf, uint32(len(*h))) 279 w.write(buf) 280 for _, p := range *h { 281 s := p.Size() 282 if int(s) > cap(buf) { 283 buf = make([]byte, s) 284 } 285 buf = buf[:s] 286 p.marshalV2(buf) 287 w.write(buf) 288 } 289 return w.offset, w.err 290 } 291 292 func (h *PartitionHeaders) UnmarshalV1(b []byte) error { 293 s := len(b) 294 if s%stacktraceBlockHeaderSize > 0 { 295 return ErrInvalidSize 296 } 297 chunks := make([]StacktraceBlockHeader, s/stacktraceBlockHeaderSize) 298 for i := range chunks { 299 off := i * stacktraceBlockHeaderSize 300 chunks[i].unmarshal(b[off : off+stacktraceBlockHeaderSize]) 301 } 302 var p *PartitionHeader 303 for _, c := range chunks { 304 if p == nil || p.Partition != c.Partition { 305 p = &PartitionHeader{Partition: c.Partition} 306 *h = append(*h, p) 307 } 308 p.Stacktraces = append(p.Stacktraces, c) 309 } 310 return nil 311 } 312 313 func (h *PartitionHeaders) UnmarshalV2(b []byte) error { return h.unmarshal(b, FormatV2) } 314 315 func (h *PartitionHeaders) UnmarshalV3(b []byte) error { return h.unmarshal(b, FormatV3) } 316 317 func (h *PartitionHeaders) unmarshal(b []byte, version FormatVersion) error { 318 partitions := binary.BigEndian.Uint32(b[0:4]) 319 b = b[4:] 320 *h = make(PartitionHeaders, partitions) 321 for i := range *h { 322 var p PartitionHeader 323 if err := p.unmarshal(b, version); err != nil { 324 return err 325 } 326 b = b[p.Size():] 327 (*h)[i] = &p 328 } 329 return nil 330 } 331 332 func (h *PartitionHeader) marshalV2(buf []byte) { 333 binary.BigEndian.PutUint64(buf[0:8], h.Partition) 334 binary.BigEndian.PutUint32(buf[8:12], uint32(len(h.Stacktraces))) 335 binary.BigEndian.PutUint32(buf[12:16], uint32(len(h.V2.Locations))) 336 binary.BigEndian.PutUint32(buf[16:20], uint32(len(h.V2.Mappings))) 337 binary.BigEndian.PutUint32(buf[20:24], uint32(len(h.V2.Functions))) 338 binary.BigEndian.PutUint32(buf[24:28], uint32(len(h.V2.Strings))) 339 n := 28 340 for i := range h.Stacktraces { 341 h.Stacktraces[i].marshal(buf[n:]) 342 n += stacktraceBlockHeaderSize 343 } 344 n += marshalRowRangeReferences(buf[n:], h.V2.Locations) 345 n += marshalRowRangeReferences(buf[n:], h.V2.Mappings) 346 n += marshalRowRangeReferences(buf[n:], h.V2.Functions) 347 marshalRowRangeReferences(buf[n:], h.V2.Strings) 348 } 349 350 func (h *PartitionHeader) marshalV3(buf []byte) { 351 binary.BigEndian.PutUint64(buf[0:8], h.Partition) 352 binary.BigEndian.PutUint32(buf[8:12], uint32(len(h.Stacktraces))) 353 n := 12 354 for i := range h.Stacktraces { 355 h.Stacktraces[i].marshal(buf[n:]) 356 n += stacktraceBlockHeaderSize 357 } 358 n += marshalSymbolsBlockReferences(buf[n:], h.V3.Locations) 359 n += marshalSymbolsBlockReferences(buf[n:], h.V3.Mappings) 360 n += marshalSymbolsBlockReferences(buf[n:], h.V3.Functions) 361 marshalSymbolsBlockReferences(buf[n:], h.V3.Strings) 362 } 363 364 func (h *PartitionHeader) unmarshal(buf []byte, version FormatVersion) (err error) { 365 h.Partition = binary.BigEndian.Uint64(buf[0:8]) 366 h.Stacktraces = make([]StacktraceBlockHeader, int(binary.BigEndian.Uint32(buf[8:12]))) 367 switch version { 368 case FormatV2: 369 h.V2 = new(PartitionHeaderV2) 370 h.V2.Locations = make([]RowRangeReference, int(binary.BigEndian.Uint32(buf[12:16]))) 371 h.V2.Mappings = make([]RowRangeReference, int(binary.BigEndian.Uint32(buf[16:20]))) 372 h.V2.Functions = make([]RowRangeReference, int(binary.BigEndian.Uint32(buf[20:24]))) 373 h.V2.Strings = make([]RowRangeReference, int(binary.BigEndian.Uint32(buf[24:28]))) 374 buf = buf[28:] 375 stacktracesSize := len(h.Stacktraces) * stacktraceBlockHeaderSize 376 if err = h.unmarshalStacktraceBlockHeaders(buf[:stacktracesSize]); err != nil { 377 return err 378 } 379 err = h.V2.unmarshal(buf[stacktracesSize:]) 380 case FormatV3: 381 buf = buf[12:] 382 stacktracesSize := len(h.Stacktraces) * stacktraceBlockHeaderSize 383 if err = h.unmarshalStacktraceBlockHeaders(buf[:stacktracesSize]); err != nil { 384 return err 385 } 386 h.V3 = new(PartitionHeaderV3) 387 err = h.V3.unmarshal(buf[stacktracesSize:]) 388 default: 389 return fmt.Errorf("bug: unsupported version: %d", version) 390 } 391 // TODO(kolesnikovae): Validate headers. 392 return err 393 } 394 395 func (h *PartitionHeader) Size() int64 { 396 s := 12 // Partition 8b + number of stacktrace blocks. 397 s += len(h.Stacktraces) * stacktraceBlockHeaderSize 398 if h.V3 != nil { 399 s += h.V3.size() 400 } 401 if h.V2 != nil { 402 s += h.V2.size() 403 } 404 return int64(s) 405 } 406 407 type PartitionHeaderV3 struct { 408 Locations SymbolsBlockHeader 409 Mappings SymbolsBlockHeader 410 Functions SymbolsBlockHeader 411 Strings SymbolsBlockHeader 412 } 413 414 const partitionHeaderV3Size = int(unsafe.Sizeof(PartitionHeaderV3{})) 415 416 func (h *PartitionHeaderV3) size() int { return partitionHeaderV3Size } 417 418 func (h *PartitionHeaderV3) unmarshal(buf []byte) (err error) { 419 if len(buf) < symbolsBlockReferenceSize { 420 return ErrInvalidSize 421 } 422 h.Locations.unmarshal(buf[:symbolsBlockReferenceSize]) 423 buf = buf[symbolsBlockReferenceSize:] 424 h.Mappings.unmarshal(buf[:symbolsBlockReferenceSize]) 425 buf = buf[symbolsBlockReferenceSize:] 426 h.Functions.unmarshal(buf[:symbolsBlockReferenceSize]) 427 buf = buf[symbolsBlockReferenceSize:] 428 h.Strings.unmarshal(buf[:symbolsBlockReferenceSize]) 429 return nil 430 } 431 432 func (h *PartitionHeader) unmarshalStacktraceBlockHeaders(b []byte) error { 433 s := len(b) 434 if s%stacktraceBlockHeaderSize > 0 { 435 return ErrInvalidSize 436 } 437 for i := range h.Stacktraces { 438 off := i * stacktraceBlockHeaderSize 439 h.Stacktraces[i].unmarshal(b[off : off+stacktraceBlockHeaderSize]) 440 } 441 return nil 442 } 443 444 // SymbolsBlockHeader describes a collection of elements encoded in a 445 // content-specific way: symbolic information such as locations, functions, 446 // mappings, and strings is represented as Array of Structures in memory, 447 // and is encoded as Structure of Arrays when written on disk. 448 type SymbolsBlockHeader struct { 449 // Offset in the data file. 450 Offset uint64 451 // Size of the section. 452 Size uint32 453 // Checksum of the section. 454 CRC uint32 455 // Length denotes the total number of items encoded. 456 Length uint32 457 // BlockSize denotes the number of items per block. 458 BlockSize uint32 459 // BlockHeaderSize denotes the encoder block header size in bytes. 460 // This enables forward compatibility within the same format version: 461 // as long as fields are not removed or reordered, and the encoding 462 // scheme does not change, the format can be extended without updating 463 // the format version. Decoder is able to read the whole header and 464 // skip unknown fields. 465 BlockHeaderSize uint16 466 // Format of the encoded data. 467 // Change of the format _version_ may break forward compatibility. 468 Format SymbolsBlockFormat 469 } 470 471 type SymbolsBlockFormat uint16 472 473 const ( 474 _ SymbolsBlockFormat = iota 475 BlockLocationsV1 476 BlockFunctionsV1 477 BlockMappingsV1 478 BlockStringsV1 479 ) 480 481 type headerUnmarshaler interface { 482 unmarshal([]byte) 483 checksum() uint32 484 } 485 486 func readSymbolsBlockHeader(buf []byte, r io.Reader, v headerUnmarshaler) error { 487 if _, err := io.ReadFull(r, buf); err != nil { 488 return err 489 } 490 v.unmarshal(buf) 491 if crc32.Checksum(buf[:len(buf)-checksumSize], castagnoli) != v.checksum() { 492 return ErrInvalidCRC 493 } 494 return nil 495 } 496 497 const symbolsBlockReferenceSize = int(unsafe.Sizeof(SymbolsBlockHeader{})) 498 499 func (h *SymbolsBlockHeader) marshal(b []byte) { 500 binary.BigEndian.PutUint64(b[0:8], h.Offset) 501 binary.BigEndian.PutUint32(b[8:12], h.Size) 502 binary.BigEndian.PutUint32(b[12:16], h.CRC) 503 binary.BigEndian.PutUint32(b[16:20], h.Length) 504 binary.BigEndian.PutUint32(b[20:24], h.BlockSize) 505 binary.BigEndian.PutUint16(b[24:26], h.BlockHeaderSize) 506 binary.BigEndian.PutUint16(b[26:28], uint16(h.Format)) 507 } 508 509 func (h *SymbolsBlockHeader) unmarshal(b []byte) { 510 h.Offset = binary.BigEndian.Uint64(b[0:8]) 511 h.Size = binary.BigEndian.Uint32(b[8:12]) 512 h.CRC = binary.BigEndian.Uint32(b[12:16]) 513 h.Length = binary.BigEndian.Uint32(b[16:20]) 514 h.BlockSize = binary.BigEndian.Uint32(b[20:24]) 515 h.BlockHeaderSize = binary.BigEndian.Uint16(b[24:26]) 516 h.Format = SymbolsBlockFormat(binary.BigEndian.Uint16(b[26:28])) 517 } 518 519 func marshalSymbolsBlockReferences(b []byte, refs ...SymbolsBlockHeader) int { 520 var off int 521 for i := range refs { 522 refs[i].marshal(b[off : off+symbolsBlockReferenceSize]) 523 off += symbolsBlockReferenceSize 524 } 525 return off 526 } 527 528 type PartitionHeaderV2 struct { 529 Locations []RowRangeReference 530 Mappings []RowRangeReference 531 Functions []RowRangeReference 532 Strings []RowRangeReference 533 } 534 535 func (h *PartitionHeaderV2) size() int { 536 s := 16 // Length of row ranges per type. 537 r := len(h.Locations) + len(h.Mappings) + len(h.Functions) + len(h.Strings) 538 return s + rowRangeReferenceSize*r 539 } 540 541 func (h *PartitionHeaderV2) unmarshal(buf []byte) (err error) { 542 locationsSize := len(h.Locations) * rowRangeReferenceSize 543 if err = h.unmarshalRowRangeReferences(h.Locations, buf[:locationsSize]); err != nil { 544 return err 545 } 546 buf = buf[locationsSize:] 547 mappingsSize := len(h.Mappings) * rowRangeReferenceSize 548 if err = h.unmarshalRowRangeReferences(h.Mappings, buf[:mappingsSize]); err != nil { 549 return err 550 } 551 buf = buf[mappingsSize:] 552 functionsSize := len(h.Functions) * rowRangeReferenceSize 553 if err = h.unmarshalRowRangeReferences(h.Functions, buf[:functionsSize]); err != nil { 554 return err 555 } 556 buf = buf[functionsSize:] 557 stringsSize := len(h.Strings) * rowRangeReferenceSize 558 if err = h.unmarshalRowRangeReferences(h.Strings, buf[:stringsSize]); err != nil { 559 return err 560 } 561 return nil 562 } 563 564 func (h *PartitionHeaderV2) unmarshalRowRangeReferences(refs []RowRangeReference, b []byte) error { 565 s := len(b) 566 if s%rowRangeReferenceSize > 0 { 567 return ErrInvalidSize 568 } 569 for i := range refs { 570 off := i * rowRangeReferenceSize 571 refs[i].unmarshal(b[off : off+rowRangeReferenceSize]) 572 } 573 return nil 574 } 575 576 func marshalRowRangeReferences(b []byte, refs []RowRangeReference) int { 577 var off int 578 for i := range refs { 579 refs[i].marshal(b[off : off+rowRangeReferenceSize]) 580 off += rowRangeReferenceSize 581 } 582 return off 583 } 584 585 const rowRangeReferenceSize = int(unsafe.Sizeof(RowRangeReference{})) 586 587 type RowRangeReference struct { 588 RowGroup uint32 589 Index uint32 590 Rows uint32 591 } 592 593 func (r *RowRangeReference) marshal(b []byte) { 594 binary.BigEndian.PutUint32(b[0:4], r.RowGroup) 595 binary.BigEndian.PutUint32(b[4:8], r.Index) 596 binary.BigEndian.PutUint32(b[8:12], r.Rows) 597 } 598 599 func (r *RowRangeReference) unmarshal(b []byte) { 600 r.RowGroup = binary.BigEndian.Uint32(b[0:4]) 601 r.Index = binary.BigEndian.Uint32(b[4:8]) 602 r.Rows = binary.BigEndian.Uint32(b[8:12]) 603 } 604 605 func OpenIndex(b []byte) (f IndexFile, err error) { 606 s := len(b) 607 if !f.assertSizeIsValid(b) { 608 return f, ErrInvalidSize 609 } 610 f.CRC = binary.BigEndian.Uint32(b[s+indexChecksumOffset:]) 611 if f.CRC != crc32.Checksum(b[:s+indexChecksumOffset], castagnoli) { 612 return f, ErrInvalidCRC 613 } 614 if err = f.Header.UnmarshalBinary(b[:IndexHeaderSize]); err != nil { 615 return f, fmt.Errorf("unmarshal header: %w", err) 616 } 617 if err = f.TOC.UnmarshalBinary(b[IndexHeaderSize:f.dataOffset()]); err != nil { 618 return f, fmt.Errorf("unmarshal table of contents: %w", err) 619 } 620 621 // TODO: validate TOC 622 623 // Version-specific data section. 624 switch f.Header.Version { 625 default: 626 return f, fmt.Errorf("bug: unsupported version: %d", f.Header.Version) 627 628 case FormatV1: 629 sch := f.TOC.Entries[tocEntryStacktraceChunkHeaders] 630 if err = f.PartitionHeaders.UnmarshalV1(b[sch.Offset : sch.Offset+sch.Size]); err != nil { 631 return f, fmt.Errorf("unmarshal stacktraces: %w", err) 632 } 633 634 case FormatV2: 635 ph := f.TOC.Entries[tocEntryPartitionHeaders] 636 if err = f.PartitionHeaders.UnmarshalV2(b[ph.Offset : ph.Offset+ph.Size]); err != nil { 637 return f, fmt.Errorf("reading partition headers: %w", err) 638 } 639 640 case FormatV3: 641 ph := f.TOC.Entries[tocEntryPartitionHeaders] 642 if err = f.PartitionHeaders.UnmarshalV3(b[ph.Offset : ph.Offset+ph.Size]); err != nil { 643 return f, fmt.Errorf("reading partition headers: %w", err) 644 } 645 } 646 647 return f, nil 648 } 649 650 func (f *IndexFile) assertSizeIsValid(b []byte) bool { 651 return len(b) >= IndexHeaderSize+f.TOC.Size()+checksumSize 652 } 653 654 func (f *IndexFile) dataOffset() int { 655 return IndexHeaderSize + f.TOC.Size() 656 } 657 658 func (f *IndexFile) WriteTo(dst io.Writer) (n int64, err error) { 659 checksum := crc32.New(castagnoli) 660 w := withWriterOffset(io.MultiWriter(dst, checksum)) 661 if _, err = w.Write(f.Header.MarshalBinary()); err != nil { 662 return w.offset, fmt.Errorf("header write: %w", err) 663 } 664 665 toc := TOC{Entries: make([]TOCEntry, tocEntriesTotal)} 666 toc.Entries[tocEntryPartitionHeaders] = TOCEntry{ 667 Offset: int64(f.dataOffset()), 668 Size: f.PartitionHeaders.Size(), 669 } 670 tocBytes, _ := toc.MarshalBinary() 671 if _, err = w.Write(tocBytes); err != nil { 672 return w.offset, fmt.Errorf("toc write: %w", err) 673 } 674 675 switch f.Header.Version { 676 case FormatV3: 677 _, err = f.PartitionHeaders.MarshalV3To(w) 678 default: 679 _, err = f.PartitionHeaders.MarshalV2To(w) 680 } 681 if err != nil { 682 return w.offset, fmt.Errorf("partitions headers: %w", err) 683 } 684 685 f.CRC = checksum.Sum32() 686 if err = binary.Write(dst, binary.BigEndian, f.CRC); err != nil { 687 return w.offset, fmt.Errorf("checksum write: %w", err) 688 } 689 690 return w.offset, nil 691 } 692 693 type StacktraceBlockHeader struct { 694 Offset int64 695 Size int64 696 697 Partition uint64 // Used in v1. 698 BlockIndex uint16 // Used in v1. 699 700 Encoding ChunkEncoding 701 _ [5]byte // Reserved. 702 703 Stacktraces uint32 // Number of unique stack traces in the chunk. 704 StacktraceNodes uint32 // Number of nodes in the stacktrace tree. 705 StacktraceMaxDepth uint32 // Max stack trace depth in the tree. 706 StacktraceMaxNodes uint32 // Max number of nodes at the time of the chunk creation. 707 708 _ [12]byte // Padding. 64 bytes per chunk header. 709 CRC uint32 // Checksum of the chunk data [Offset:Size). 710 } 711 712 const stacktraceBlockHeaderSize = int(unsafe.Sizeof(StacktraceBlockHeader{})) 713 714 type ChunkEncoding byte 715 716 const ( 717 _ ChunkEncoding = iota 718 StacktraceEncodingGroupVarint 719 ) 720 721 func (h *StacktraceBlockHeader) marshal(b []byte) { 722 binary.BigEndian.PutUint64(b[0:8], uint64(h.Offset)) 723 binary.BigEndian.PutUint64(b[8:16], uint64(h.Size)) 724 binary.BigEndian.PutUint64(b[16:24], h.Partition) 725 binary.BigEndian.PutUint16(b[24:26], h.BlockIndex) 726 b[27] = byte(h.Encoding) 727 // 5 bytes reserved. 728 binary.BigEndian.PutUint32(b[32:36], h.Stacktraces) 729 binary.BigEndian.PutUint32(b[36:40], h.StacktraceNodes) 730 binary.BigEndian.PutUint32(b[40:44], h.StacktraceMaxDepth) 731 binary.BigEndian.PutUint32(b[44:48], h.StacktraceMaxNodes) 732 // 12 bytes reserved. 733 binary.BigEndian.PutUint32(b[60:64], h.CRC) 734 } 735 736 func (h *StacktraceBlockHeader) unmarshal(b []byte) { 737 h.Offset = int64(binary.BigEndian.Uint64(b[0:8])) 738 h.Size = int64(binary.BigEndian.Uint64(b[8:16])) 739 h.Partition = binary.BigEndian.Uint64(b[16:24]) 740 h.BlockIndex = binary.BigEndian.Uint16(b[24:26]) 741 h.Encoding = ChunkEncoding(b[27]) 742 // 5 bytes reserved. 743 h.Stacktraces = binary.BigEndian.Uint32(b[32:36]) 744 h.StacktraceNodes = binary.BigEndian.Uint32(b[36:40]) 745 h.StacktraceMaxDepth = binary.BigEndian.Uint32(b[40:44]) 746 h.StacktraceMaxNodes = binary.BigEndian.Uint32(b[44:48]) 747 // 12 bytes reserved. 748 h.CRC = binary.BigEndian.Uint32(b[60:64]) 749 } 750 751 type symbolsBlockEncoder[T any] interface { 752 encode(w io.Writer, block []T) error 753 format() SymbolsBlockFormat 754 headerSize() uintptr 755 } 756 757 type symbolsEncoder[T any] struct { 758 blockEncoder symbolsBlockEncoder[T] 759 blockSize int 760 } 761 762 const defaultSymbolsBlockSize = 1 << 10 763 764 func newSymbolsEncoder[T any](e symbolsBlockEncoder[T]) *symbolsEncoder[T] { 765 return &symbolsEncoder[T]{blockEncoder: e, blockSize: defaultSymbolsBlockSize} 766 } 767 768 func (e *symbolsEncoder[T]) encode(w io.Writer, items []T) (err error) { 769 l := len(items) 770 for i := 0; i < l; i += e.blockSize { 771 block := items[i:min(i+e.blockSize, l)] 772 if err = e.blockEncoder.encode(w, block); err != nil { 773 return err 774 } 775 } 776 return nil 777 } 778 779 type symbolsBlockDecoder[T any] interface { 780 decode(r io.Reader, dst []T) error 781 } 782 783 type symbolsDecoder[T any] struct { 784 h SymbolsBlockHeader 785 d symbolsBlockDecoder[T] 786 } 787 788 func newSymbolsDecoder[T any](h SymbolsBlockHeader, d symbolsBlockDecoder[T]) *symbolsDecoder[T] { 789 return &symbolsDecoder[T]{h: h, d: d} 790 } 791 792 func (d *symbolsDecoder[T]) decode(dst []T, r io.Reader) error { 793 if d.h.BlockSize == 0 || d.h.Length == 0 { 794 return nil 795 } 796 if len(dst) < int(d.h.Length) { 797 return fmt.Errorf("decoder buffer too short (format %d)", d.h.Format) 798 } 799 blocks := int((d.h.Length + d.h.BlockSize - 1) / d.h.BlockSize) 800 for i := 0; i < blocks; i++ { 801 lo := i * int(d.h.BlockSize) 802 hi := min(lo+int(d.h.BlockSize), int(d.h.Length)) 803 block := dst[lo:hi] 804 if err := d.d.decode(r, block); err != nil { 805 return fmt.Errorf("malformed block (format %d): %w", d.h.Format, err) 806 } 807 } 808 return nil 809 } 810 811 // NOTE(kolesnikovae): delta.BinaryPackedEncoding may 812 // silently fail on malformed data, producing empty slice. 813 814 func decodeBinaryPackedInt32(dst []int32, data []byte, length int) ([]int32, error) { 815 var enc delta.BinaryPackedEncoding 816 var err error 817 dst, err = enc.DecodeInt32(dst, data) 818 if err != nil { 819 return dst, err 820 } 821 if len(dst) != length { 822 return dst, fmt.Errorf("%w: binary packed: expected %d, got %d", ErrInvalidSize, length, len(dst)) 823 } 824 return dst, nil 825 } 826 827 func decodeBinaryPackedInt64(dst []int64, data []byte, length int) ([]int64, error) { 828 var enc delta.BinaryPackedEncoding 829 var err error 830 dst, err = enc.DecodeInt64(dst, data) 831 if err != nil { 832 return dst, err 833 } 834 if len(dst) != length { 835 return dst, fmt.Errorf("%w: binary packed: expected %d, got %d", ErrInvalidSize, length, len(dst)) 836 } 837 return dst, nil 838 }