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 }