go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/butler/bundler/builder.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package bundler
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  
    21  	"go.chromium.org/luci/logdog/api/logpb"
    22  )
    23  
    24  // builderStream is builder data that is tracked for each individual stream.
    25  type builderStream struct {
    26  	// ButlerLogBundle_Entry is the stream's in-progress bundle entry.
    27  	logpb.ButlerLogBundle_Entry
    28  	// size incrementally tracks the size of the stream's entry.
    29  	size int
    30  }
    31  
    32  // builder incrementally constructs ButlerLogBundle entries.
    33  type builder struct {
    34  	// size is the maximum permitted bundle size.
    35  	size int
    36  
    37  	// template is the base bundle template.
    38  	template logpb.ButlerLogBundle
    39  	// templateCachedSize is the cached size of the ButlerLogBundle template.
    40  	templateCachedSize int
    41  
    42  	// smap maps the builder state for each individual stream by stream name.
    43  	streams map[string]*builderStream
    44  }
    45  
    46  func (b *builder) remaining() int {
    47  	return b.size - b.bundleSize()
    48  }
    49  
    50  func (b *builder) ready() bool {
    51  	// Have we reached our desired size?
    52  	return b.hasContent() && (b.bundleSize() >= b.size)
    53  }
    54  
    55  func (b *builder) bundleSize() int {
    56  	if b.templateCachedSize == 0 {
    57  		b.templateCachedSize = protoSize(&b.template)
    58  	}
    59  
    60  	size := b.templateCachedSize
    61  	for _, bs := range b.streams {
    62  		size += sizeOfBundleEntryTag + varintLength(uint64(bs.size)) + bs.size
    63  	}
    64  
    65  	return size
    66  }
    67  
    68  func (b *builder) hasContent() bool {
    69  	return len(b.streams) > 0
    70  }
    71  
    72  func (b *builder) add(template *logpb.ButlerLogBundle_Entry, le *logpb.LogEntry) {
    73  	bs := b.getCreateBuilderStream(template)
    74  
    75  	bs.Logs = append(bs.Logs, le)
    76  	psize := protoSize(le)
    77  
    78  	// Pay the cost of the additional LogEntry.
    79  	bs.size += sizeOfLogEntryTag + varintLength(uint64(psize)) + psize
    80  }
    81  
    82  func (b *builder) setStreamTerminal(template *logpb.ButlerLogBundle_Entry, tidx uint64) {
    83  	bs := b.getCreateBuilderStream(template)
    84  	if bs.Terminal {
    85  		if bs.TerminalIndex != tidx {
    86  			panic(fmt.Errorf("attempt to change terminal index %d => %d", bs.TerminalIndex, tidx))
    87  		}
    88  		return
    89  	}
    90  
    91  	bs.Terminal = true
    92  	bs.TerminalIndex = tidx
    93  
    94  	// Pay the cost of the additional terminal fields.
    95  	bs.size += ((sizeOfTerminalTag + sizeOfBoolTrue) +
    96  		(sizeOfTerminalIndexTag + varintLength(bs.TerminalIndex)))
    97  }
    98  
    99  func (b *builder) bundle() *logpb.ButlerLogBundle {
   100  	bundle := b.template
   101  
   102  	names := make([]string, 0, len(b.streams))
   103  	for k := range b.streams {
   104  		names = append(names, k)
   105  	}
   106  	sort.Strings(names)
   107  
   108  	bundle.Entries = make([]*logpb.ButlerLogBundle_Entry, len(names))
   109  	for idx, name := range names {
   110  		bundle.Entries[idx] = &b.streams[name].ButlerLogBundle_Entry
   111  	}
   112  
   113  	return &bundle
   114  }
   115  
   116  func (b *builder) getCreateBuilderStream(template *logpb.ButlerLogBundle_Entry) *builderStream {
   117  	if bs := b.streams[template.Desc.Name]; bs != nil {
   118  		return bs
   119  	}
   120  
   121  	// Initialize our maps (first time only).
   122  	if b.streams == nil {
   123  		b.streams = map[string]*builderStream{}
   124  	}
   125  
   126  	bs := builderStream{
   127  		ButlerLogBundle_Entry: *template,
   128  		size:                  protoSize(template),
   129  	}
   130  	b.streams[template.Desc.Name] = &bs
   131  	return &bs
   132  }