| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- package opencost
- import (
- "fmt"
- "io"
- "os"
- util "github.com/opencost/opencost/core/pkg/util"
- )
- // fileStringRef maps a bingen string-table index to a payload stored in a temp file.
- type fileStringRef struct {
- off int64
- length int
- }
- // FileStringTable holds string-table payloads on disk and resolves indices on demand.
- type FileStringTable struct {
- f *os.File
- refs []fileStringRef
- }
- // NewFileStringTableFromBuffer reads exactly tl length-prefixed (uint16) string payloads from buff
- // and appends each payload to a new temp file. It does not retain full strings in memory.
- func NewFileStringTableFromBuffer(buff *util.Buffer, tl int) (*FileStringTable, error) {
- os.MkdirAll("/var/lib/clickhouse/tmp", 0755)
- f, err := os.CreateTemp("/var/lib/clickhouse/tmp", "opencost-bgst-*")
- if err != nil {
- return nil, fmt.Errorf("opencost: create string table file: %w", err)
- }
- t := &FileStringTable{f: f, refs: make([]fileStringRef, tl)}
- var writeErr error
- defer func() {
- if writeErr != nil {
- _ = t.Close()
- }
- }()
- for i := 0; i < tl; i++ {
- payload, err := buff.ReadStringBytes()
- if err != nil {
- writeErr = err
- return nil, fmt.Errorf("opencost: read string table entry %d: %w", i, err)
- }
- var off int64
- if len(payload) > 0 {
- off, err = f.Seek(0, io.SeekEnd)
- if err != nil {
- writeErr = err
- return nil, fmt.Errorf("opencost: seek string table file: %w", err)
- }
- if _, err := f.Write(payload); err != nil {
- writeErr = err
- return nil, fmt.Errorf("opencost: write string table entry %d: %w", i, err)
- }
- }
- t.refs[i] = fileStringRef{off: off, length: len(payload)}
- }
- return t, nil
- }
- // Len returns the number of strings in the table.
- func (t *FileStringTable) Len() int {
- if t == nil {
- return 0
- }
- return len(t.refs)
- }
- // StringAt returns the string for wire index i, reading from the backing file.
- func (t *FileStringTable) StringAt(i int) (string, error) {
- if t == nil || t.f == nil {
- return "", fmt.Errorf("opencost: closed or nil string table")
- }
- if i < 0 || i >= len(t.refs) {
- return "", fmt.Errorf("opencost: string table index %d out of range [0,%d)", i, len(t.refs))
- }
- ref := t.refs[i]
- if ref.length == 0 {
- return "", nil
- }
- buf := make([]byte, ref.length)
- n, err := t.f.ReadAt(buf, ref.off)
- if err != nil {
- return "", err
- }
- if n != ref.length {
- return "", fmt.Errorf("opencost: short read in string table at %d", i)
- }
- return string(buf), nil
- }
- // Close releases the backing file (and removes the temp file).
- func (t *FileStringTable) Close() error {
- if t == nil || t.f == nil {
- return nil
- }
- path := t.f.Name()
- err := t.f.Close()
- t.f = nil
- t.refs = nil
- if path != "" {
- _ = os.Remove(path)
- }
- return err
- }
- func newDecodingContextFromBytes(data []byte) (*DecodingContext, error) {
- buff := util.NewBufferFromBytes(data)
- if !isBinaryTag(data, BinaryTagStringTable) {
- return &DecodingContext{Buffer: buff}, nil
- }
- buff.ReadBytes(len(BinaryTagStringTable))
- tl := buff.ReadInt()
- if tl <= 0 {
- return &DecodingContext{Buffer: buff}, nil
- }
- ft, err := NewFileStringTableFromBuffer(buff, tl)
- if err != nil {
- return nil, err
- }
- return &DecodingContext{Buffer: buff, FileTable: ft}, nil
- }
- // IsStringTable returns true if a non-empty string table is present (in-memory and/or file-backed).
- func (dc *DecodingContext) IsStringTable() bool {
- return dc != nil && (len(dc.Table) > 0 || (dc.FileTable != nil && dc.FileTable.Len() > 0))
- }
- // CloseFileTable closes and removes the backing file for the string table, if any.
- func (dc *DecodingContext) CloseFileTable() {
- if dc == nil || dc.FileTable == nil {
- return
- }
- _ = dc.FileTable.Close()
- dc.FileTable = nil
- }
- func (dc *DecodingContext) tableString(i int) string {
- if len(dc.Table) > 0 {
- if i < 0 || i >= len(dc.Table) {
- panic(fmt.Sprintf("opencost: string table index %d out of range [0,%d)", i, len(dc.Table)))
- }
- return dc.Table[i]
- }
- if dc.FileTable == nil {
- panic(fmt.Sprintf("opencost: string table lookup with no file table (index %d)", i))
- }
- s, err := dc.FileTable.StringAt(i)
- if err != nil {
- panic(err)
- }
- return s
- }
- func newDecodingContextFromReader(reader io.Reader) (*DecodingContext, error) {
- buff := util.NewBufferFromReader(reader)
- if !isReaderBinaryTag(buff, BinaryTagStringTable) {
- return &DecodingContext{Buffer: buff}, nil
- }
- buff.ReadBytes(len(BinaryTagStringTable))
- tl := buff.ReadInt()
- if tl <= 0 {
- return &DecodingContext{Buffer: buff}, nil
- }
- ft, err := NewFileStringTableFromBuffer(buff, tl)
- if err != nil {
- return nil, err
- }
- return &DecodingContext{Buffer: buff, FileTable: ft}, nil
- }
|