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

     1  package symdb
     2  
     3  import (
     4  	"fmt"
     5  	"hash/crc32"
     6  	"io"
     7  	"math"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/grafana/pyroscope/pkg/phlaredb/block"
    12  	v1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1"
    13  )
    14  
    15  type writerV3 struct {
    16  	config *Config
    17  	index  IndexFile
    18  	footer Footer
    19  	files  []block.File
    20  	encodersV3
    21  }
    22  
    23  type encodersV3 struct {
    24  	stringsEncoder   *symbolsEncoder[string]
    25  	mappingsEncoder  *symbolsEncoder[v1.InMemoryMapping]
    26  	functionsEncoder *symbolsEncoder[v1.InMemoryFunction]
    27  	locationsEncoder *symbolsEncoder[v1.InMemoryLocation]
    28  }
    29  
    30  func newWriterV3(c *Config) *writerV3 {
    31  	return &writerV3{
    32  		config:     c,
    33  		index:      newIndexFileV3(),
    34  		footer:     newFooterV3(),
    35  		encodersV3: newEncodersV3(),
    36  	}
    37  }
    38  
    39  func newIndexFileV3() IndexFile {
    40  	return IndexFile{
    41  		Header: IndexHeader{
    42  			Magic:   symdbMagic,
    43  			Version: FormatV3,
    44  		},
    45  	}
    46  }
    47  
    48  func newFooterV3() Footer {
    49  	return Footer{
    50  		Magic:   symdbMagic,
    51  		Version: FormatV3,
    52  	}
    53  }
    54  
    55  func newEncodersV3() encodersV3 {
    56  	return encodersV3{
    57  		stringsEncoder:   newStringsEncoder(),
    58  		mappingsEncoder:  newMappingsEncoder(),
    59  		functionsEncoder: newFunctionsEncoder(),
    60  		locationsEncoder: newLocationsEncoder(),
    61  	}
    62  }
    63  
    64  func (w *writerV3) writePartitions(partitions []*PartitionWriter) (err error) {
    65  	if dst := w.config.Writer; dst != nil {
    66  		defer func() {
    67  			_ = w.config.Writer.Close()
    68  		}()
    69  		return w.writePartitionsWithWriter(withWriterOffset(dst), partitions)
    70  	}
    71  	if err = os.MkdirAll(w.config.Dir, 0o755); err != nil {
    72  		return fmt.Errorf("failed to create directory %q: %w", w.config.Dir, err)
    73  	}
    74  	var f *fileWriter
    75  	f, err = w.newFile(DefaultFileName)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	defer func() {
    80  		_ = f.Close()
    81  		w.files = []block.File{f.meta()}
    82  	}()
    83  	return w.writePartitionsWithWriter(f.w, partitions)
    84  }
    85  
    86  func (w *writerV3) writePartitionsWithWriter(f *writerOffset, partitions []*PartitionWriter) (err error) {
    87  	for _, p := range partitions {
    88  		if err = writePartitionV3(f, &w.encodersV3, p); err != nil {
    89  			return fmt.Errorf("failed to write partition: %w", err)
    90  		}
    91  		w.index.PartitionHeaders = append(w.index.PartitionHeaders, &p.header)
    92  	}
    93  	w.footer.IndexOffset = uint64(f.offset)
    94  	if _, err = w.index.WriteTo(f); err != nil {
    95  		return fmt.Errorf("failed to write index: %w", err)
    96  	}
    97  	if _, err = f.Write(w.footer.MarshalBinary()); err != nil {
    98  		return fmt.Errorf("failed to write footer: %w", err)
    99  	}
   100  	return nil
   101  }
   102  
   103  func (w *writerV3) meta() []block.File { return w.files }
   104  
   105  func (w *writerV3) newFile(path string) (f *fileWriter, err error) {
   106  	path = filepath.Join(w.config.Dir, path)
   107  	if f, err = newFileWriter(path); err != nil {
   108  		return nil, fmt.Errorf("failed to create %q: %w", path, err)
   109  	}
   110  	return f, err
   111  }
   112  
   113  func writePartitionV3(w *writerOffset, e *encodersV3, p *PartitionWriter) (err error) {
   114  	if p.header.V3.Strings, err = writeSymbolsBlock(w, p.strings.slice, e.stringsEncoder); err != nil {
   115  		return err
   116  	}
   117  	if p.header.V3.Mappings, err = writeSymbolsBlock(w, p.mappings.slice, e.mappingsEncoder); err != nil {
   118  		return err
   119  	}
   120  	if p.header.V3.Functions, err = writeSymbolsBlock(w, p.functions.slice, e.functionsEncoder); err != nil {
   121  		return err
   122  	}
   123  	if p.header.V3.Locations, err = writeSymbolsBlock(w, p.locations.slice, e.locationsEncoder); err != nil {
   124  		return err
   125  	}
   126  
   127  	h := StacktraceBlockHeader{
   128  		Offset:             w.offset,
   129  		Partition:          p.header.Partition,
   130  		Encoding:           StacktraceEncodingGroupVarint,
   131  		Stacktraces:        uint32(len(p.stacktraces.hashToIdx)),
   132  		StacktraceNodes:    p.stacktraces.tree.len(),
   133  		StacktraceMaxNodes: math.MaxUint32,
   134  	}
   135  	crc := crc32.New(castagnoli)
   136  	if h.Size, err = p.stacktraces.WriteTo(io.MultiWriter(crc, w)); err != nil {
   137  		return fmt.Errorf("writing stacktrace chunk data: %w", err)
   138  	}
   139  	h.CRC = crc.Sum32()
   140  	p.header.Stacktraces = append(p.header.Stacktraces, h)
   141  
   142  	return nil
   143  }
   144  
   145  func writeSymbolsBlock[T any](w *writerOffset, s []T, e *symbolsEncoder[T]) (h SymbolsBlockHeader, err error) {
   146  	h.Offset = uint64(w.offset)
   147  	crc := crc32.New(castagnoli)
   148  	mw := io.MultiWriter(crc, w)
   149  	if err = e.encode(mw, s); err != nil {
   150  		return h, err
   151  	}
   152  	h.Size = uint32(w.offset) - uint32(h.Offset)
   153  	h.CRC = crc.Sum32()
   154  	h.Length = uint32(len(s))
   155  	h.BlockSize = uint32(e.blockSize)
   156  	h.BlockHeaderSize = uint16(e.blockEncoder.headerSize())
   157  	h.Format = e.blockEncoder.format()
   158  	return h, nil
   159  }
   160  
   161  func WritePartition(p *PartitionWriter, dst io.Writer) error {
   162  	index := newIndexFileV3()
   163  	footer := newFooterV3()
   164  	encoders := newEncodersV3()
   165  	w := withWriterOffset(dst)
   166  
   167  	if err := writePartitionV3(w, &encoders, p); err != nil {
   168  		return fmt.Errorf("failed to write partition: %w", err)
   169  	}
   170  	index.PartitionHeaders = append(index.PartitionHeaders, &p.header)
   171  	footer.IndexOffset = uint64(w.offset)
   172  	if _, err := index.WriteTo(w); err != nil {
   173  		return fmt.Errorf("failed to write index: %w", err)
   174  	}
   175  	if _, err := w.Write(footer.MarshalBinary()); err != nil {
   176  		return fmt.Errorf("failed to write footer: %w", err)
   177  	}
   178  	return nil
   179  }