Просмотр исходного кода

bingen update

Signed-off-by: Sean Holcomb <seanholcomb@gmail.com>
Sean Holcomb 1 месяц назад
Родитель
Сommit
5fabab715b

+ 1 - 1
core/pkg/model/kubemodel/bingen.go

@@ -19,5 +19,5 @@ package kubemodel
 // }
 //
 ////////////////////////////////////////////////////////////////////////////////
-
+// @bingen:import:github.com/opencost/opencost/core/pkg/model/shared
 //go:generate bingen -package=kubemodel -version=2 -buffer=github.com/opencost/opencost/core/pkg/util

Разница между файлами не показана из-за своего большого размера
+ 559 - 167
core/pkg/model/kubemodel/kubemodel_codecs.go


+ 1 - 1
core/pkg/model/pricingmodel/bingen.go

@@ -19,5 +19,5 @@ package pricingmodel
 // }
 //
 ////////////////////////////////////////////////////////////////////////////////
-
+// @bingen:import:github.com/opencost/opencost/core/pkg/model/shared
 //go:generate bingen -package=pricingmodel -version=1 -buffer=github.com/opencost/opencost/core/pkg/util

+ 558 - 166
core/pkg/model/pricingmodel/pricingmodel_codecs.go

@@ -1,22 +1,26 @@
-////////////////////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////////////////
 //
-//                             DO NOT MODIFY
+//	                 DO NOT MODIFY
 //
-//                          ┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻
+//	              ┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻
 //
 //
-//            This source file was automatically generated by bingen.
+//	This source file was automatically generated by bingen.
 //
-////////////////////////////////////////////////////////////////////////////////
-
+// //////////////////////////////////////////////////////////////////////////////
+// TODO This file was infact modified due to an issue with including aliased string types from 'shared'
 package pricingmodel
 
 import (
 	"fmt"
+	"io"
+	"iter"
+	"os"
 	"reflect"
 	"strings"
 	"sync"
 	"time"
+	"unsafe"
 
 	"github.com/opencost/opencost/core/pkg/model/shared"
 	"github.com/opencost/opencost/core/pkg/util"
@@ -39,6 +43,63 @@ const (
 	DefaultCodecVersion uint8 = 1
 )
 
+//--------------------------------------------------------------------------
+//  Configuration
+//--------------------------------------------------------------------------
+
+var (
+	bingenConfigLock sync.RWMutex
+	bingenConfig     *BingenConfiguration = DefaultBingenConfiguration()
+)
+
+// BingenConfiguration is used to set any custom configuration in the way files are encoded
+// or decoded.
+type BingenConfiguration struct {
+	// FileBackedStringTableEnabled enables the use of file-backed string tables for streaming
+	// bingen decoding.
+	FileBackedStringTableEnabled bool
+
+	// FileBackedStringTableDir is the directory to write the string table files for reading.
+	FileBackedStringTableDir string
+}
+
+// DefaultBingenConfiguration creates the default implementation of the bingen configuration
+// and returns it.
+func DefaultBingenConfiguration() *BingenConfiguration {
+	return &BingenConfiguration{
+		FileBackedStringTableEnabled: false,
+		FileBackedStringTableDir:     os.TempDir(),
+	}
+}
+
+// ConfigureBingen accepts a new *BingenConfiguration instance which updates the internal decoder
+// and encoder behavior.
+func ConfigureBingen(config *BingenConfiguration) {
+	bingenConfigLock.Lock()
+	defer bingenConfigLock.Unlock()
+
+	if config == nil {
+		config = DefaultBingenConfiguration()
+	}
+	bingenConfig = config
+}
+
+// IsBingenFileBackedStringTableEnabled accessor for file backed string table configuration
+func IsBingenFileBackedStringTableEnabled() bool {
+	bingenConfigLock.RLock()
+	defer bingenConfigLock.RUnlock()
+
+	return bingenConfig.FileBackedStringTableEnabled
+}
+
+// BingenFileBackedStringTableDir returns the directory configured for file backed string tables.
+func BingenFileBackedStringTableDir() string {
+	bingenConfigLock.RLock()
+	defer bingenConfigLock.RUnlock()
+
+	return bingenConfig.FileBackedStringTableDir
+}
+
 //--------------------------------------------------------------------------
 //  Type Map
 //--------------------------------------------------------------------------
@@ -46,9 +107,9 @@ const (
 // Generated type map for resolving interface implementations to
 // to concrete types
 var typeMap map[string]reflect.Type = map[string]reflect.Type{
-	"NodeKey":         reflect.TypeOf((*NodeKey)(nil)).Elem(),
-	"NodePricing":     reflect.TypeOf((*NodePricing)(nil)).Elem(),
-	"PricingModelSet": reflect.TypeOf((*PricingModelSet)(nil)).Elem(),
+	"NodeKey":         reflect.TypeFor[NodeKey](),
+	"NodePricing":     reflect.TypeFor[NodePricing](),
+	"PricingModelSet": reflect.TypeFor[PricingModelSet](),
 }
 
 //--------------------------------------------------------------------------
@@ -57,6 +118,23 @@ var typeMap map[string]reflect.Type = map[string]reflect.Type{
 
 // isBinaryTag returns true when the first bytes in the provided binary matches the tag
 func isBinaryTag(data []byte, tag string) bool {
+	if len(data) < len(tag) {
+		return false
+	}
+
+	return string(data[:len(tag)]) == tag
+}
+
+// isReaderBinaryTag is used to peek the header for an io.Reader Buffer
+func isReaderBinaryTag(buff *util.Buffer, tag string) bool {
+	data, err := buff.Peek(len(tag))
+	if err != nil && err != io.EOF {
+		panic(fmt.Sprintf("called Peek() on a non buffered reader: %s", err))
+	}
+	if len(data) < len(tag) {
+		return false
+	}
+
 	return string(data[:len(tag)]) == tag
 }
 
@@ -68,7 +146,7 @@ func appendBytes(a []byte, b []byte) []byte {
 
 	// allocate a new byte array for the combined
 	// use native copy for speedy byte copying
-	result := make([]byte, tl, tl)
+	result := make([]byte, tl)
 	copy(result, a)
 	copy(result[al:], b)
 
@@ -111,21 +189,91 @@ func resolveType(t string) (pkg string, name string, isPtr bool) {
 }
 
 //--------------------------------------------------------------------------
-//  StringTable
+//  Stream Helpers
+//--------------------------------------------------------------------------
+
+// StreamFactoryFunc is an alias for a func that creates a BingenStream implementation.
+type StreamFactoryFunc func(io.Reader) BingenStream
+
+// Generated streamable factory map for finding the specific new stream methods
+// by T type
+var streamFactoryMap map[reflect.Type]StreamFactoryFunc = map[reflect.Type]StreamFactoryFunc{}
+
+// NewStreamFor accepts an io.Reader, and returns a new BingenStream for the generic T
+// type provided _if_ it is a registered bingen type that is annotated as 'streamable'. See
+// the streamFactoryMap for generated type listings.
+func NewStreamFor[T any](reader io.Reader) (BingenStream, error) {
+	typeKey := reflect.TypeFor[T]()
+
+	factory, ok := streamFactoryMap[typeKey]
+	if !ok {
+		return nil, fmt.Errorf("the type: %s is not a registered bingen streamable type", typeKey.Name())
+	}
+
+	return factory(reader), nil
+}
+
+// BingenStream is the stream interface for all streamable types
+type BingenStream interface {
+	// Stream returns the iterator which will stream each field of the target type and
+	// return the field info as well as the value.
+	Stream() iter.Seq2[BingenFieldInfo, *BingenValue]
+
+	// Close will close any dynamic io.Reader used to stream in the fields
+	Close()
+
+	// Error returns an error if one occurred during the process of streaming the type's fields.
+	// This can be checked after iterating through the Stream().
+	Error() error
+}
+
+// BingenValue contains the value of a field as well as any index/key associated with that value.
+type BingenValue struct {
+	Value any
+	Index any
+}
+
+// IsNil is just a method accessor way to check to see if the value returned was nil
+func (bv *BingenValue) IsNil() bool {
+	return bv == nil
+}
+
+// creates a single BingenValue instance without a key or index
+func singleV(value any) *BingenValue {
+	return &BingenValue{
+		Value: value,
+	}
+}
+
+// creates a pair of key/index and value.
+func pairV(index any, value any) *BingenValue {
+	return &BingenValue{
+		Value: value,
+		Index: index,
+	}
+}
+
+// BingenFieldInfo contains the type of the field being streamed as well as the name of the field.
+type BingenFieldInfo struct {
+	Type reflect.Type
+	Name string
+}
+
+//--------------------------------------------------------------------------
+//  String Table Writer
 //--------------------------------------------------------------------------
 
-// StringTable maps strings to specific indices for encoding
-type StringTable struct {
-	l       *sync.Mutex
+// StringTableWriter maps strings to specific indices for encoding
+type StringTableWriter struct {
+	l       sync.Mutex
 	indices map[string]int
 	next    int
 }
 
-// NewStringTable Creates a new StringTable instance with provided contents
-func NewStringTable(contents ...string) *StringTable {
-	st := &StringTable{
-		l:       new(sync.Mutex),
-		indices: make(map[string]int),
+// NewStringTableWriter Creates a new StringTableWriter instance with provided contents
+func NewStringTableWriter(contents ...string) *StringTableWriter {
+	st := &StringTableWriter{
+		indices: make(map[string]int, len(contents)),
 		next:    len(contents),
 	}
 
@@ -138,7 +286,7 @@ func NewStringTable(contents ...string) *StringTable {
 
 // AddOrGet atomically retrieves a string entry's index if it exist. Otherwise, it will
 // add the entry and return the index.
-func (st *StringTable) AddOrGet(s string) int {
+func (st *StringTableWriter) AddOrGet(s string) int {
 	st.l.Lock()
 	defer st.l.Unlock()
 
@@ -154,7 +302,7 @@ func (st *StringTable) AddOrGet(s string) int {
 }
 
 // ToSlice Converts the contents to a string array for encoding.
-func (st *StringTable) ToSlice() []string {
+func (st *StringTableWriter) ToSlice() []string {
 	st.l.Lock()
 	defer st.l.Unlock()
 
@@ -162,7 +310,7 @@ func (st *StringTable) ToSlice() []string {
 		return []string{}
 	}
 
-	sl := make([]string, st.next, st.next)
+	sl := make([]string, st.next)
 	for s, i := range st.indices {
 		sl[i] = s
 	}
@@ -170,7 +318,7 @@ func (st *StringTable) ToSlice() []string {
 }
 
 // ToBytes Converts the contents to a binary encoded representation
-func (st *StringTable) ToBytes() []byte {
+func (st *StringTableWriter) ToBytes() []byte {
 	buff := util.NewBuffer()
 	buff.WriteBytes([]byte(BinaryTagStringTable)) // bingen table header
 
@@ -184,6 +332,201 @@ func (st *StringTable) ToBytes() []byte {
 	return buff.Bytes()
 }
 
+//--------------------------------------------------------------------------
+//  String Table Reader
+//--------------------------------------------------------------------------
+
+// StringTableReader is the interface used to read the string table from the decoding.
+type StringTableReader interface {
+	// At returns the string entry at a specific index, or panics on out of bounds.
+	At(index int) string
+
+	// Len returns the total number of strings loaded in the string table.
+	Len() int
+
+	// Close will clear the loaded table, and drop any external resources used.
+	Close() error
+}
+
+// SliceStringTableReader is a basic pre-loaded []string that provides index-based access.
+// The cost of this implementation is holding all strings in memory, which provides faster
+// lookup performance for memory usage.
+type SliceStringTableReader struct {
+	table []string
+}
+
+// NewSliceStringTableReaderFrom creates a new SliceStringTableReader instance loading
+// data directly from the buffer. The buffer's position should start at the table length.
+func NewSliceStringTableReaderFrom(buffer *util.Buffer) StringTableReader {
+	// table length
+	tl := buffer.ReadInt()
+
+	var table []string
+	if tl > 0 {
+		table = make([]string, tl)
+		for i := range tl {
+			table[i] = buffer.ReadString()
+		}
+	}
+
+	return &SliceStringTableReader{
+		table: table,
+	}
+}
+
+// At returns the string entry at a specific index, or panics on out of bounds.
+func (sstr *SliceStringTableReader) At(index int) string {
+	if index < 0 || index >= len(sstr.table) {
+		panic(fmt.Errorf("%s: string table index out of bounds: %d", GeneratorPackageName, index))
+	}
+
+	return sstr.table[index]
+}
+
+// Len returns the total number of strings loaded in the string table.
+func (sstr *SliceStringTableReader) Len() int {
+	if sstr == nil {
+		return 0
+	}
+
+	return len(sstr.table)
+}
+
+// Close for the slice tables just nils out the slice and returns
+func (sstr *SliceStringTableReader) Close() error {
+	sstr.table = nil
+	return nil
+}
+
+// fileStringRef maps a bingen string-table index to a payload stored in a temp file.
+type fileStringRef struct {
+	off    int64
+	length int
+}
+
+// FileStringTableReader leverages a local file to write string table data for lookup. On
+// memory focused systems, this allows a slower parse with a significant decrease in memory
+// usage. This implementation is often pair with streaming readers for high throughput with
+// reduced memory usage.
+type FileStringTableReader struct {
+	f    *os.File
+	refs []fileStringRef
+}
+
+// NewFileStringTableFromBuffer reads exactly tl length-prefixed (uint16) string payloads from buffer
+// and appends each payload to a new temp file. It does not retain full strings in memory.
+func NewFileStringTableReaderFrom(buffer *util.Buffer, dir string) StringTableReader {
+	// helper func to cast a string in-place to a byte slice.
+	// NOTE: Return value is READ-ONLY. DO NOT MODIFY!
+	byteSliceFor := func(s string) []byte {
+		return unsafe.Slice(unsafe.StringData(s), len(s))
+	}
+
+	err := os.MkdirAll(dir, 0755)
+	if err != nil {
+		panic(fmt.Errorf("%s: failed to create string table directory: %w", GeneratorPackageName, err))
+	}
+
+	f, err := os.CreateTemp(dir, fmt.Sprintf("%s-bgst-*", GeneratorPackageName))
+	if err != nil {
+		panic(fmt.Errorf("%s: failed to create string table file: %w", GeneratorPackageName, err))
+	}
+
+	var writeErr error
+	defer func() {
+		if writeErr != nil {
+			_ = f.Close()
+		}
+	}()
+
+	// table length
+	tl := buffer.ReadInt()
+
+	var refs []fileStringRef
+	if tl > 0 {
+		refs = make([]fileStringRef, tl)
+
+		for i := range tl {
+			payload := byteSliceFor(buffer.ReadString())
+
+			var off int64
+			if len(payload) > 0 {
+				off, err = f.Seek(0, io.SeekEnd)
+				if err != nil {
+					writeErr = fmt.Errorf("%s: failed to seek string table file: %w", GeneratorPackageName, err)
+					panic(writeErr)
+				}
+				if _, err := f.Write(payload); err != nil {
+					writeErr = fmt.Errorf("%s: failed to write string table entry %d: %w", GeneratorPackageName, i, err)
+					panic(writeErr)
+				}
+			}
+
+			refs[i] = fileStringRef{
+				off:    off,
+				length: len(payload),
+			}
+		}
+	}
+
+	return &FileStringTableReader{
+		f:    f,
+		refs: refs,
+	}
+}
+
+// At returns the string from the internal file using the reference's offset and length.
+func (fstr *FileStringTableReader) At(index int) string {
+	if fstr == nil || fstr.f == nil {
+		panic(fmt.Errorf("%s: failed to read file string table data", GeneratorPackageName))
+	}
+	if index < 0 || index >= len(fstr.refs) {
+		panic(fmt.Errorf("%s: string table index out of bounds: %d", GeneratorPackageName, index))
+	}
+
+	ref := fstr.refs[index]
+	if ref.length == 0 {
+		return ""
+	}
+
+	b := make([]byte, ref.length)
+	_, err := fstr.f.ReadAt(b, ref.off)
+	if err != nil {
+		return ""
+	}
+
+	// cast the allocated bytes to a string in-place, as we
+	// were the ones that allocated the bytes
+	return unsafe.String(unsafe.SliceData(b), len(b))
+}
+
+// Len returns the total number of strings loaded in the string table.
+func (fstr *FileStringTableReader) Len() int {
+	if fstr == nil {
+		return 0
+	}
+
+	return len(fstr.refs)
+}
+
+// Close for the file string table reader closes the file and deletes it.
+func (fstr *FileStringTableReader) Close() error {
+	if fstr == nil || fstr.f == nil {
+		return nil
+	}
+
+	path := fstr.f.Name()
+	err := fstr.f.Close()
+	fstr.f = nil
+	fstr.refs = nil
+
+	if path != "" {
+		_ = os.Remove(path)
+	}
+
+	return err
+}
+
 //--------------------------------------------------------------------------
 //  Codec Context
 //--------------------------------------------------------------------------
@@ -192,7 +535,7 @@ func (st *StringTable) ToBytes() []byte {
 // and table data
 type EncodingContext struct {
 	Buffer *util.Buffer
-	Table  *StringTable
+	Table  *StringTableWriter
 }
 
 // IsStringTable returns true if the table is available
@@ -204,12 +547,66 @@ func (ec *EncodingContext) IsStringTable() bool {
 // reuse as much data as possible
 type DecodingContext struct {
 	Buffer *util.Buffer
-	Table  []string
+	Table  StringTableReader
+}
+
+// NewDecodingContextFromBytes creates a new DecodingContext instance using an byte slice
+func NewDecodingContextFromBytes(data []byte) *DecodingContext {
+	var table StringTableReader
+
+	buff := util.NewBufferFromBytes(data)
+
+	// string table header validation
+	if isBinaryTag(data, BinaryTagStringTable) {
+		buff.ReadBytes(len(BinaryTagStringTable)) // strip tag length
+
+		// always use a slice string table with a byte array since the
+		// data is already in memory
+		table = NewSliceStringTableReaderFrom(buff)
+	}
+
+	return &DecodingContext{
+		Buffer: buff,
+		Table:  table,
+	}
+}
+
+// NewDecodingContextFromReader creates a new DecodingContext instance using an io.Reader
+// implementation
+func NewDecodingContextFromReader(reader io.Reader) *DecodingContext {
+	var table StringTableReader
+
+	buff := util.NewBufferFromReader(reader)
+
+	if isReaderBinaryTag(buff, BinaryTagStringTable) {
+		buff.ReadBytes(len(BinaryTagStringTable)) // strip tag length
+
+		// create correct string table implementation
+		if IsBingenFileBackedStringTableEnabled() {
+			table = NewFileStringTableReaderFrom(buff, BingenFileBackedStringTableDir())
+		} else {
+			table = NewSliceStringTableReaderFrom(buff)
+		}
+	}
+
+	return &DecodingContext{
+		Buffer: buff,
+		Table:  table,
+	}
 }
 
 // IsStringTable returns true if the table is available
 func (dc *DecodingContext) IsStringTable() bool {
-	return len(dc.Table) > 0
+	return dc.Table != nil && dc.Table.Len() > 0
+}
+
+// Close will ensure that any string table resources and buffer resources are
+// cleaned up.
+func (dc *DecodingContext) Close() {
+	if dc.Table != nil {
+		_ = dc.Table.Close()
+		dc.Table = nil
+	}
 }
 
 //--------------------------------------------------------------------------
@@ -275,74 +672,69 @@ func (target *NodeKey) MarshalBinaryWithContext(ctx *EncodingContext) (err error
 	}
 	// --- [end][write][alias](shared.Provider) ---
 
+	// --- [begin][write][alias](NodePricingType) ---
 	if ctx.IsStringTable() {
-		b := ctx.Table.AddOrGet(target.Region)
+		b := ctx.Table.AddOrGet(string(target.PricingType))
 		buff.WriteInt(b) // write table index
 	} else {
-		buff.WriteString(target.Region) // write string
+		buff.WriteString(string(target.PricingType)) // write string
 	}
+	// --- [end][write][alias](NodePricingType) ---
+
+	// --- [begin][write][alias](shared.UsageType) ---
 	if ctx.IsStringTable() {
-		c := ctx.Table.AddOrGet(target.NodeType)
+		c := ctx.Table.AddOrGet(string(target.UsageType))
 		buff.WriteInt(c) // write table index
 	} else {
-		buff.WriteString(target.NodeType) // write string
+		buff.WriteString(string(target.UsageType)) // write string
 	}
-	// --- [begin][write][alias](shared.UsageType) ---
+	// --- [end][write][alias](shared.UsageType) ---
+
 	if ctx.IsStringTable() {
-		d := ctx.Table.AddOrGet(string(target.UsageType))
+		d := ctx.Table.AddOrGet(target.Region)
 		buff.WriteInt(d) // write table index
 	} else {
-		buff.WriteString(string(target.UsageType)) // write string
+		buff.WriteString(target.Region) // write string
 	}
-	// --- [end][write][alias](shared.UsageType) ---
-
 	if ctx.IsStringTable() {
-		e := ctx.Table.AddOrGet(target.Family)
+		e := ctx.Table.AddOrGet(target.NodeType)
 		buff.WriteInt(e) // write table index
 	} else {
-		buff.WriteString(target.Family) // write string
+		buff.WriteString(target.NodeType) // write string
 	}
 	if ctx.IsStringTable() {
-		f := ctx.Table.AddOrGet(target.DeviceType)
+		f := ctx.Table.AddOrGet(target.Family)
 		buff.WriteInt(f) // write table index
 	} else {
-		buff.WriteString(target.DeviceType) // write string
+		buff.WriteString(target.Family) // write string
 	}
-	// --- [begin][write][alias](NodePricingType) ---
 	if ctx.IsStringTable() {
-		g := ctx.Table.AddOrGet(string(target.PricingType))
+		g := ctx.Table.AddOrGet(target.DeviceType)
 		buff.WriteInt(g) // write table index
 	} else {
-		buff.WriteString(string(target.PricingType)) // write string
+		buff.WriteString(target.DeviceType) // write string
 	}
-	// --- [end][write][alias](NodePricingType) ---
-
 	return nil
 }
 
 // UnmarshalBinary uses the data passed byte array to set all the internal properties of
 // the NodeKey type
 func (target *NodeKey) UnmarshalBinary(data []byte) error {
-	var table []string
-	buff := util.NewBufferFromBytes(data)
-
-	// string table header validation
-	if isBinaryTag(data, BinaryTagStringTable) {
-		buff.ReadBytes(len(BinaryTagStringTable)) // strip tag length
-		tl := buff.ReadInt()                      // table length
-		if tl > 0 {
-			table = make([]string, tl, tl)
-			for i := 0; i < tl; i++ {
-				table[i] = buff.ReadString()
-			}
-		}
+	ctx := NewDecodingContextFromBytes(data)
+	defer ctx.Close()
+	err := target.UnmarshalBinaryWithContext(ctx)
+	if err != nil {
+		return err
 	}
 
-	ctx := &DecodingContext{
-		Buffer: buff,
-		Table:  table,
-	}
+	return nil
+}
 
+// UnmarshalBinaryFromReader uses the io.Reader data to set all the internal properties of
+// the NodeKey type
+func (target *NodeKey) UnmarshalBinaryFromReader(reader io.Reader) error {
+	ctx := NewDecodingContextFromReader(reader)
+	defer ctx.Close()
 	err := target.UnmarshalBinaryWithContext(ctx)
 	if err != nil {
 		return err
@@ -378,78 +770,78 @@ func (target *NodeKey) UnmarshalBinaryWithContext(ctx *DecodingContext) (err err
 	var a string
 	if ctx.IsStringTable() {
 		b := buff.ReadInt() // read string index
-		a = ctx.Table[b]
+		a = ctx.Table.At(b)
 	} else {
 		a = buff.ReadString() // read string
 	}
 	target.Provider = shared.Provider(a)
 	// --- [end][read][alias](shared.Provider) ---
 
-	var e string
+	// --- [begin][read][alias](NodePricingType) ---
+	var d string
+	var f string
 	if ctx.IsStringTable() {
-		f := buff.ReadInt() // read string index
-		e = ctx.Table[f]
+		g := buff.ReadInt() // read string index
+		f = ctx.Table.At(g)
 	} else {
-		e = buff.ReadString() // read string
+		f = buff.ReadString() // read string
 	}
-	d := e
-	target.Region = d
+	e := f
+	d = e
+
+	target.PricingType = NodePricingType(d)
+	// --- [end][read][alias](NodePricingType) ---
 
+	// --- [begin][read][alias](shared.UsageType) ---
 	var h string
 	if ctx.IsStringTable() {
-		k := buff.ReadInt() // read string index
-		h = ctx.Table[k]
+		l := buff.ReadInt() // read string index
+		h = ctx.Table.At(l)
 	} else {
 		h = buff.ReadString() // read string
 	}
-	g := h
-	target.NodeType = g
+	target.UsageType = shared.UsageType(h)
+	// --- [end][read][alias](shared.UsageType) ---
 
-	// --- [begin][read][alias](shared.UsageType) ---
-	var l string
+	var o string
 	if ctx.IsStringTable() {
-		m := buff.ReadInt() // read string index
-		l = ctx.Table[m]
+		p := buff.ReadInt() // read string index
+		o = ctx.Table.At(p)
 	} else {
-		l = buff.ReadString() // read string
+		o = buff.ReadString() // read string
 	}
-	target.UsageType = shared.UsageType(l)
-	// --- [end][read][alias](shared.UsageType) ---
+	n := o
+	target.Region = n
 
-	var p string
+	var r string
 	if ctx.IsStringTable() {
-		q := buff.ReadInt() // read string index
-		p = ctx.Table[q]
+		s := buff.ReadInt() // read string index
+		r = ctx.Table.At(s)
 	} else {
-		p = buff.ReadString() // read string
+		r = buff.ReadString() // read string
 	}
-	o := p
-	target.Family = o
+	q := r
+	target.NodeType = q
 
-	var s string
+	var u string
 	if ctx.IsStringTable() {
-		t := buff.ReadInt() // read string index
-		s = ctx.Table[t]
+		w := buff.ReadInt() // read string index
+		u = ctx.Table.At(w)
 	} else {
-		s = buff.ReadString() // read string
+		u = buff.ReadString() // read string
 	}
-	r := s
-	target.DeviceType = r
+	t := u
+	target.Family = t
 
-	// --- [begin][read][alias](NodePricingType) ---
-	var u string
-	var x string
+	var y string
 	if ctx.IsStringTable() {
-		y := buff.ReadInt() // read string index
-		x = ctx.Table[y]
+		aa := buff.ReadInt() // read string index
+		y = ctx.Table.At(aa)
 	} else {
-		x = buff.ReadString() // read string
+		y = buff.ReadString() // read string
 	}
-	w := x
-	u = w
-
-	target.PricingType = NodePricingType(u)
-	// --- [end][read][alias](NodePricingType) ---
+	x := y
+	target.DeviceType = x
 
 	return nil
 }
@@ -501,26 +893,21 @@ func (target *NodePricing) MarshalBinaryWithContext(ctx *EncodingContext) (err e
 // UnmarshalBinary uses the data passed byte array to set all the internal properties of
 // the NodePricing type
 func (target *NodePricing) UnmarshalBinary(data []byte) error {
-	var table []string
-	buff := util.NewBufferFromBytes(data)
-
-	// string table header validation
-	if isBinaryTag(data, BinaryTagStringTable) {
-		buff.ReadBytes(len(BinaryTagStringTable)) // strip tag length
-		tl := buff.ReadInt()                      // table length
-		if tl > 0 {
-			table = make([]string, tl, tl)
-			for i := 0; i < tl; i++ {
-				table[i] = buff.ReadString()
-			}
-		}
+	ctx := NewDecodingContextFromBytes(data)
+	defer ctx.Close()
+	err := target.UnmarshalBinaryWithContext(ctx)
+	if err != nil {
+		return err
 	}
 
-	ctx := &DecodingContext{
-		Buffer: buff,
-		Table:  table,
-	}
+	return nil
+}
 
+// UnmarshalBinaryFromReader uses the io.Reader data to set all the internal properties of
+// the NodePricing type
+func (target *NodePricing) UnmarshalBinaryFromReader(reader io.Reader) error {
+	ctx := NewDecodingContextFromReader(reader)
+	defer ctx.Close()
 	err := target.UnmarshalBinaryWithContext(ctx)
 	if err != nil {
 		return err
@@ -607,12 +994,21 @@ func (target *PricingModelSet) MarshalBinaryWithContext(ctx *EncodingContext) (e
 	buff.WriteBytes(a)
 	// --- [end][write][reference](time.Time) ---
 
+	// --- [begin][write][alias](PricingSourceType) ---
 	if ctx.IsStringTable() {
 		b := ctx.Table.AddOrGet(string(target.SourceType))
 		buff.WriteInt(b) // write table index
 	} else {
 		buff.WriteString(string(target.SourceType)) // write string
 	}
+	// --- [end][write][alias](PricingSourceType) ---
+
+	if ctx.IsStringTable() {
+		c := ctx.Table.AddOrGet(target.SourceKey)
+		buff.WriteInt(c) // write table index
+	} else {
+		buff.WriteString(target.SourceKey) // write string
+	}
 	if target.NodePricing == nil {
 		buff.WriteUInt8(uint8(0)) // write nil byte
 	} else {
@@ -641,38 +1037,27 @@ func (target *PricingModelSet) MarshalBinaryWithContext(ctx *EncodingContext) (e
 		// --- [end][write][map](map[NodeKey]NodePricing) ---
 
 	}
-	if ctx.IsStringTable() {
-		i := ctx.Table.AddOrGet(target.SourceKey)
-		buff.WriteInt(i) // write table index
-	} else {
-		buff.WriteString(target.SourceKey) // write string
-	}
 	return nil
 }
 
 // UnmarshalBinary uses the data passed byte array to set all the internal properties of
 // the PricingModelSet type
 func (target *PricingModelSet) UnmarshalBinary(data []byte) error {
-	var table []string
-	buff := util.NewBufferFromBytes(data)
-
-	// string table header validation
-	if isBinaryTag(data, BinaryTagStringTable) {
-		buff.ReadBytes(len(BinaryTagStringTable)) // strip tag length
-		tl := buff.ReadInt()                      // table length
-		if tl > 0 {
-			table = make([]string, tl, tl)
-			for i := 0; i < tl; i++ {
-				table[i] = buff.ReadString()
-			}
-		}
+	ctx := NewDecodingContextFromBytes(data)
+	defer ctx.Close()
+	err := target.UnmarshalBinaryWithContext(ctx)
+	if err != nil {
+		return err
 	}
 
-	ctx := &DecodingContext{
-		Buffer: buff,
-		Table:  table,
-	}
+	return nil
+}
 
+// UnmarshalBinaryFromReader uses the io.Reader data to set all the internal properties of
+// the PricingModelSet type
+func (target *PricingModelSet) UnmarshalBinaryFromReader(reader io.Reader) error {
+	ctx := NewDecodingContextFromReader(reader)
+	defer ctx.Close()
 	err := target.UnmarshalBinaryWithContext(ctx)
 	if err != nil {
 		return err
@@ -715,56 +1100,63 @@ func (target *PricingModelSet) UnmarshalBinaryWithContext(ctx *DecodingContext)
 	target.TimeStamp = *a
 	// --- [end][read][reference](time.Time) ---
 
-	var e string
+	// --- [begin][read][alias](PricingSourceType) ---
+	var d string
+	var f string
 	if ctx.IsStringTable() {
-		f := buff.ReadInt() // read string index
-		e = ctx.Table[f]
+		g := buff.ReadInt() // read string index
+		f = ctx.Table.At(g)
 	} else {
-		e = buff.ReadString() // read string
+		f = buff.ReadString() // read string
 	}
-	d := e
+	e := f
+	d = e
+
 	target.SourceType = PricingSourceType(d)
+	// --- [end][read][alias](PricingSourceType) ---
+
+	var l string
+	if ctx.IsStringTable() {
+		m := buff.ReadInt() // read string index
+		l = ctx.Table.At(m)
+	} else {
+		l = buff.ReadString() // read string
+	}
+	h := l
+	target.SourceKey = h
 
 	if buff.ReadUInt8() == uint8(0) {
 		target.NodePricing = nil
 	} else {
 		// --- [begin][read][map](map[NodeKey]NodePricing) ---
-		h := buff.ReadInt() // map len
-		g := make(map[NodeKey]NodePricing, h)
-		for i := 0; i < h; i++ {
+		o := buff.ReadInt() // map len
+		n := make(map[NodeKey]NodePricing, o)
+		for i := 0; i < o; i++ {
 			// --- [begin][read][struct](NodeKey) ---
-			k := &NodeKey{}
+			p := &NodeKey{}
 			buff.ReadInt() // [compatibility, unused]
-			errB := k.UnmarshalBinaryWithContext(ctx)
+			errB := p.UnmarshalBinaryWithContext(ctx)
 			if errB != nil {
 				return errB
 			}
-			v := *k
+			v := *p
 			// --- [end][read][struct](NodeKey) ---
 
 			// --- [begin][read][struct](NodePricing) ---
-			l := &NodePricing{}
+			q := &NodePricing{}
 			buff.ReadInt() // [compatibility, unused]
-			errC := l.UnmarshalBinaryWithContext(ctx)
+			errC := q.UnmarshalBinaryWithContext(ctx)
 			if errC != nil {
 				return errC
 			}
-			z := *l
+			z := *q
 			// --- [end][read][struct](NodePricing) ---
 
-			g[v] = z
+			n[v] = z
 		}
-		target.NodePricing = g
+		target.NodePricing = n
 		// --- [end][read][map](map[NodeKey]NodePricing) ---
 
 	}
-	var m string
-	if ctx.IsStringTable() {
-		n := buff.ReadInt() // read string index
-		m = ctx.Table[n]
-	} else {
-		m = buff.ReadString() // read string
-	}
-	target.SourceKey = m
 	return nil
 }

+ 414 - 17
core/pkg/model/shared/shared_codecs.go

@@ -14,9 +14,13 @@ package shared
 import (
 	"fmt"
 	util "github.com/opencost/opencost/core/pkg/util"
+	"io"
+	"iter"
+	"os"
 	"reflect"
 	"strings"
 	"sync"
+	"unsafe"
 )
 
 const (
@@ -36,6 +40,63 @@ const (
 	DefaultCodecVersion uint8 = 1
 )
 
+//--------------------------------------------------------------------------
+//  Configuration
+//--------------------------------------------------------------------------
+
+var (
+	bingenConfigLock sync.RWMutex
+	bingenConfig     *BingenConfiguration = DefaultBingenConfiguration()
+)
+
+// BingenConfiguration is used to set any custom configuration in the way files are encoded
+// or decoded.
+type BingenConfiguration struct {
+	// FileBackedStringTableEnabled enables the use of file-backed string tables for streaming
+	// bingen decoding.
+	FileBackedStringTableEnabled bool
+
+	// FileBackedStringTableDir is the directory to write the string table files for reading.
+	FileBackedStringTableDir string
+}
+
+// DefaultBingenConfiguration creates the default implementation of the bingen configuration
+// and returns it.
+func DefaultBingenConfiguration() *BingenConfiguration {
+	return &BingenConfiguration{
+		FileBackedStringTableEnabled: false,
+		FileBackedStringTableDir:     os.TempDir(),
+	}
+}
+
+// ConfigureBingen accepts a new *BingenConfiguration instance which updates the internal decoder
+// and encoder behavior.
+func ConfigureBingen(config *BingenConfiguration) {
+	bingenConfigLock.Lock()
+	defer bingenConfigLock.Unlock()
+
+	if config == nil {
+		config = DefaultBingenConfiguration()
+	}
+	bingenConfig = config
+}
+
+// IsBingenFileBackedStringTableEnabled accessor for file backed string table configuration
+func IsBingenFileBackedStringTableEnabled() bool {
+	bingenConfigLock.RLock()
+	defer bingenConfigLock.RUnlock()
+
+	return bingenConfig.FileBackedStringTableEnabled
+}
+
+// BingenFileBackedStringTableDir returns the directory configured for file backed string tables.
+func BingenFileBackedStringTableDir() string {
+	bingenConfigLock.RLock()
+	defer bingenConfigLock.RUnlock()
+
+	return bingenConfig.FileBackedStringTableDir
+}
+
 //--------------------------------------------------------------------------
 //  Type Map
 //--------------------------------------------------------------------------
@@ -50,6 +111,23 @@ var typeMap map[string]reflect.Type = map[string]reflect.Type{}
 
 // isBinaryTag returns true when the first bytes in the provided binary matches the tag
 func isBinaryTag(data []byte, tag string) bool {
+	if len(data) < len(tag) {
+		return false
+	}
+
+	return string(data[:len(tag)]) == tag
+}
+
+// isReaderBinaryTag is used to peek the header for an io.Reader Buffer
+func isReaderBinaryTag(buff *util.Buffer, tag string) bool {
+	data, err := buff.Peek(len(tag))
+	if err != nil && err != io.EOF {
+		panic(fmt.Sprintf("called Peek() on a non buffered reader: %s", err))
+	}
+	if len(data) < len(tag) {
+		return false
+	}
+
 	return string(data[:len(tag)]) == tag
 }
 
@@ -61,7 +139,7 @@ func appendBytes(a []byte, b []byte) []byte {
 
 	// allocate a new byte array for the combined
 	// use native copy for speedy byte copying
-	result := make([]byte, tl, tl)
+	result := make([]byte, tl)
 	copy(result, a)
 	copy(result[al:], b)
 
@@ -104,21 +182,91 @@ func resolveType(t string) (pkg string, name string, isPtr bool) {
 }
 
 //--------------------------------------------------------------------------
-//  StringTable
+//  Stream Helpers
 //--------------------------------------------------------------------------
 
-// StringTable maps strings to specific indices for encoding
-type StringTable struct {
-	l       *sync.Mutex
+// StreamFactoryFunc is an alias for a func that creates a BingenStream implementation.
+type StreamFactoryFunc func(io.Reader) BingenStream
+
+// Generated streamable factory map for finding the specific new stream methods
+// by T type
+var streamFactoryMap map[reflect.Type]StreamFactoryFunc = map[reflect.Type]StreamFactoryFunc{}
+
+// NewStreamFor accepts an io.Reader, and returns a new BingenStream for the generic T
+// type provided _if_ it is a registered bingen type that is annotated as 'streamable'. See
+// the streamFactoryMap for generated type listings.
+func NewStreamFor[T any](reader io.Reader) (BingenStream, error) {
+	typeKey := reflect.TypeFor[T]()
+
+	factory, ok := streamFactoryMap[typeKey]
+	if !ok {
+		return nil, fmt.Errorf("the type: %s is not a registered bingen streamable type", typeKey.Name())
+	}
+
+	return factory(reader), nil
+}
+
+// BingenStream is the stream interface for all streamable types
+type BingenStream interface {
+	// Stream returns the iterator which will stream each field of the target type and
+	// return the field info as well as the value.
+	Stream() iter.Seq2[BingenFieldInfo, *BingenValue]
+
+	// Close will close any dynamic io.Reader used to stream in the fields
+	Close()
+
+	// Error returns an error if one occurred during the process of streaming the type's fields.
+	// This can be checked after iterating through the Stream().
+	Error() error
+}
+
+// BingenValue contains the value of a field as well as any index/key associated with that value.
+type BingenValue struct {
+	Value any
+	Index any
+}
+
+// IsNil is just a method accessor way to check to see if the value returned was nil
+func (bv *BingenValue) IsNil() bool {
+	return bv == nil
+}
+
+// creates a single BingenValue instance without a key or index
+func singleV(value any) *BingenValue {
+	return &BingenValue{
+		Value: value,
+	}
+}
+
+// creates a pair of key/index and value.
+func pairV(index any, value any) *BingenValue {
+	return &BingenValue{
+		Value: value,
+		Index: index,
+	}
+}
+
+// BingenFieldInfo contains the type of the field being streamed as well as the name of the field.
+type BingenFieldInfo struct {
+	Type reflect.Type
+	Name string
+}
+
+//--------------------------------------------------------------------------
+//  String Table Writer
+//--------------------------------------------------------------------------
+
+// StringTableWriter maps strings to specific indices for encoding
+type StringTableWriter struct {
+	l       sync.Mutex
 	indices map[string]int
 	next    int
 }
 
-// NewStringTable Creates a new StringTable instance with provided contents
-func NewStringTable(contents ...string) *StringTable {
-	st := &StringTable{
-		l:       new(sync.Mutex),
-		indices: make(map[string]int),
+// NewStringTableWriter Creates a new StringTableWriter instance with provided contents
+func NewStringTableWriter(contents ...string) *StringTableWriter {
+	st := &StringTableWriter{
+		indices: make(map[string]int, len(contents)),
 		next:    len(contents),
 	}
 
@@ -131,7 +279,7 @@ func NewStringTable(contents ...string) *StringTable {
 
 // AddOrGet atomically retrieves a string entry's index if it exist. Otherwise, it will
 // add the entry and return the index.
-func (st *StringTable) AddOrGet(s string) int {
+func (st *StringTableWriter) AddOrGet(s string) int {
 	st.l.Lock()
 	defer st.l.Unlock()
 
@@ -147,7 +295,7 @@ func (st *StringTable) AddOrGet(s string) int {
 }
 
 // ToSlice Converts the contents to a string array for encoding.
-func (st *StringTable) ToSlice() []string {
+func (st *StringTableWriter) ToSlice() []string {
 	st.l.Lock()
 	defer st.l.Unlock()
 
@@ -155,7 +303,7 @@ func (st *StringTable) ToSlice() []string {
 		return []string{}
 	}
 
-	sl := make([]string, st.next, st.next)
+	sl := make([]string, st.next)
 	for s, i := range st.indices {
 		sl[i] = s
 	}
@@ -163,7 +311,7 @@ func (st *StringTable) ToSlice() []string {
 }
 
 // ToBytes Converts the contents to a binary encoded representation
-func (st *StringTable) ToBytes() []byte {
+func (st *StringTableWriter) ToBytes() []byte {
 	buff := util.NewBuffer()
 	buff.WriteBytes([]byte(BinaryTagStringTable)) // bingen table header
 
@@ -177,6 +325,201 @@ func (st *StringTable) ToBytes() []byte {
 	return buff.Bytes()
 }
 
+//--------------------------------------------------------------------------
+//  String Table Reader
+//--------------------------------------------------------------------------
+
+// StringTableReader is the interface used to read the string table from the decoding.
+type StringTableReader interface {
+	// At returns the string entry at a specific index, or panics on out of bounds.
+	At(index int) string
+
+	// Len returns the total number of strings loaded in the string table.
+	Len() int
+
+	// Close will clear the loaded table, and drop any external resources used.
+	Close() error
+}
+
+// SliceStringTableReader is a basic pre-loaded []string that provides index-based access.
+// The cost of this implementation is holding all strings in memory, which provides faster
+// lookup performance for memory usage.
+type SliceStringTableReader struct {
+	table []string
+}
+
+// NewSliceStringTableReaderFrom creates a new SliceStringTableReader instance loading
+// data directly from the buffer. The buffer's position should start at the table length.
+func NewSliceStringTableReaderFrom(buffer *util.Buffer) StringTableReader {
+	// table length
+	tl := buffer.ReadInt()
+
+	var table []string
+	if tl > 0 {
+		table = make([]string, tl)
+		for i := range tl {
+			table[i] = buffer.ReadString()
+		}
+	}
+
+	return &SliceStringTableReader{
+		table: table,
+	}
+}
+
+// At returns the string entry at a specific index, or panics on out of bounds.
+func (sstr *SliceStringTableReader) At(index int) string {
+	if index < 0 || index >= len(sstr.table) {
+		panic(fmt.Errorf("%s: string table index out of bounds: %d", GeneratorPackageName, index))
+	}
+
+	return sstr.table[index]
+}
+
+// Len returns the total number of strings loaded in the string table.
+func (sstr *SliceStringTableReader) Len() int {
+	if sstr == nil {
+		return 0
+	}
+
+	return len(sstr.table)
+}
+
+// Close for the slice tables just nils out the slice and returns
+func (sstr *SliceStringTableReader) Close() error {
+	sstr.table = nil
+	return nil
+}
+
+// fileStringRef maps a bingen string-table index to a payload stored in a temp file.
+type fileStringRef struct {
+	off    int64
+	length int
+}
+
+// FileStringTableReader leverages a local file to write string table data for lookup. On
+// memory focused systems, this allows a slower parse with a significant decrease in memory
+// usage. This implementation is often pair with streaming readers for high throughput with
+// reduced memory usage.
+type FileStringTableReader struct {
+	f    *os.File
+	refs []fileStringRef
+}
+
+// NewFileStringTableFromBuffer reads exactly tl length-prefixed (uint16) string payloads from buffer
+// and appends each payload to a new temp file. It does not retain full strings in memory.
+func NewFileStringTableReaderFrom(buffer *util.Buffer, dir string) StringTableReader {
+	// helper func to cast a string in-place to a byte slice.
+	// NOTE: Return value is READ-ONLY. DO NOT MODIFY!
+	byteSliceFor := func(s string) []byte {
+		return unsafe.Slice(unsafe.StringData(s), len(s))
+	}
+
+	err := os.MkdirAll(dir, 0755)
+	if err != nil {
+		panic(fmt.Errorf("%s: failed to create string table directory: %w", GeneratorPackageName, err))
+	}
+
+	f, err := os.CreateTemp(dir, fmt.Sprintf("%s-bgst-*", GeneratorPackageName))
+	if err != nil {
+		panic(fmt.Errorf("%s: failed to create string table file: %w", GeneratorPackageName, err))
+	}
+
+	var writeErr error
+	defer func() {
+		if writeErr != nil {
+			_ = f.Close()
+		}
+	}()
+
+	// table length
+	tl := buffer.ReadInt()
+
+	var refs []fileStringRef
+	if tl > 0 {
+		refs = make([]fileStringRef, tl)
+
+		for i := range tl {
+			payload := byteSliceFor(buffer.ReadString())
+
+			var off int64
+			if len(payload) > 0 {
+				off, err = f.Seek(0, io.SeekEnd)
+				if err != nil {
+					writeErr = fmt.Errorf("%s: failed to seek string table file: %w", GeneratorPackageName, err)
+					panic(writeErr)
+				}
+				if _, err := f.Write(payload); err != nil {
+					writeErr = fmt.Errorf("%s: failed to write string table entry %d: %w", GeneratorPackageName, i, err)
+					panic(writeErr)
+				}
+			}
+
+			refs[i] = fileStringRef{
+				off:    off,
+				length: len(payload),
+			}
+		}
+	}
+
+	return &FileStringTableReader{
+		f:    f,
+		refs: refs,
+	}
+}
+
+// At returns the string from the internal file using the reference's offset and length.
+func (fstr *FileStringTableReader) At(index int) string {
+	if fstr == nil || fstr.f == nil {
+		panic(fmt.Errorf("%s: failed to read file string table data", GeneratorPackageName))
+	}
+	if index < 0 || index >= len(fstr.refs) {
+		panic(fmt.Errorf("%s: string table index out of bounds: %d", GeneratorPackageName, index))
+	}
+
+	ref := fstr.refs[index]
+	if ref.length == 0 {
+		return ""
+	}
+
+	b := make([]byte, ref.length)
+	_, err := fstr.f.ReadAt(b, ref.off)
+	if err != nil {
+		return ""
+	}
+
+	// cast the allocated bytes to a string in-place, as we
+	// were the ones that allocated the bytes
+	return unsafe.String(unsafe.SliceData(b), len(b))
+}
+
+// Len returns the total number of strings loaded in the string table.
+func (fstr *FileStringTableReader) Len() int {
+	if fstr == nil {
+		return 0
+	}
+
+	return len(fstr.refs)
+}
+
+// Close for the file string table reader closes the file and deletes it.
+func (fstr *FileStringTableReader) Close() error {
+	if fstr == nil || fstr.f == nil {
+		return nil
+	}
+
+	path := fstr.f.Name()
+	err := fstr.f.Close()
+	fstr.f = nil
+	fstr.refs = nil
+
+	if path != "" {
+		_ = os.Remove(path)
+	}
+
+	return err
+}
+
 //--------------------------------------------------------------------------
 //  Codec Context
 //--------------------------------------------------------------------------
@@ -185,7 +528,7 @@ func (st *StringTable) ToBytes() []byte {
 // and table data
 type EncodingContext struct {
 	Buffer *util.Buffer
-	Table  *StringTable
+	Table  *StringTableWriter
 }
 
 // IsStringTable returns true if the table is available
@@ -197,12 +540,66 @@ func (ec *EncodingContext) IsStringTable() bool {
 // reuse as much data as possible
 type DecodingContext struct {
 	Buffer *util.Buffer
-	Table  []string
+	Table  StringTableReader
+}
+
+// NewDecodingContextFromBytes creates a new DecodingContext instance using an byte slice
+func NewDecodingContextFromBytes(data []byte) *DecodingContext {
+	var table StringTableReader
+
+	buff := util.NewBufferFromBytes(data)
+
+	// string table header validation
+	if isBinaryTag(data, BinaryTagStringTable) {
+		buff.ReadBytes(len(BinaryTagStringTable)) // strip tag length
+
+		// always use a slice string table with a byte array since the
+		// data is already in memory
+		table = NewSliceStringTableReaderFrom(buff)
+	}
+
+	return &DecodingContext{
+		Buffer: buff,
+		Table:  table,
+	}
+}
+
+// NewDecodingContextFromReader creates a new DecodingContext instance using an io.Reader
+// implementation
+func NewDecodingContextFromReader(reader io.Reader) *DecodingContext {
+	var table StringTableReader
+
+	buff := util.NewBufferFromReader(reader)
+
+	if isReaderBinaryTag(buff, BinaryTagStringTable) {
+		buff.ReadBytes(len(BinaryTagStringTable)) // strip tag length
+
+		// create correct string table implementation
+		if IsBingenFileBackedStringTableEnabled() {
+			table = NewFileStringTableReaderFrom(buff, BingenFileBackedStringTableDir())
+		} else {
+			table = NewSliceStringTableReaderFrom(buff)
+		}
+	}
+
+	return &DecodingContext{
+		Buffer: buff,
+		Table:  table,
+	}
 }
 
 // IsStringTable returns true if the table is available
 func (dc *DecodingContext) IsStringTable() bool {
-	return len(dc.Table) > 0
+	return dc.Table != nil && dc.Table.Len() > 0
+}
+
+// Close will ensure that any string table resources and buffer resources are
+// cleaned up.
+func (dc *DecodingContext) Close() {
+	if dc.Table != nil {
+		_ = dc.Table.Close()
+		dc.Table = nil
+	}
 }
 
 //--------------------------------------------------------------------------

Некоторые файлы не были показаны из-за большого количества измененных файлов