go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/butler/bundler/sizer.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 "errors" 19 "fmt" 20 "reflect" 21 "strconv" 22 "strings" 23 24 "github.com/golang/protobuf/proto" 25 "go.chromium.org/luci/logdog/api/logpb" 26 ) 27 28 var ( 29 sizeOfBundleEntryTag int 30 sizeOfLogEntryTag int 31 sizeOfTerminalTag int 32 sizeOfTerminalIndexTag int 33 34 errMalformedProtobufField = errors.New("malformed protobuf field") 35 ) 36 37 const ( 38 // sizeOfBoolTrue is the size of the "true" boolean value. 39 sizeOfBoolTrue = 1 40 ) 41 42 func init() { 43 b := &logpb.ButlerLogBundle{} 44 sizeOfBundleEntryTag = mustCalculateTagSize(b, "Entries") 45 46 be := &logpb.ButlerLogBundle_Entry{} 47 sizeOfLogEntryTag = mustCalculateTagSize(be, "Logs") 48 sizeOfTerminalTag = mustCalculateTagSize(be, "Terminal") 49 sizeOfTerminalIndexTag = mustCalculateTagSize(be, "TerminalIndex") 50 } 51 52 func mustCalculateTagSize(i any, field string) int { 53 value, err := calculateTagSize(i, field) 54 if err != nil { 55 panic(err) 56 } 57 return value 58 } 59 60 func protoSize(m proto.Message) int { 61 return proto.Size(m) 62 } 63 64 func calculateTagSize(i any, field string) (int, error) { 65 v := reflect.TypeOf(i) 66 if v.Kind() == reflect.Ptr { 67 v = v.Elem() 68 } 69 if v.Kind() != reflect.Struct { 70 return 0, fmt.Errorf("sizer: %s is not a struct", v) 71 } 72 73 f, ok := v.FieldByName(field) 74 if !ok { 75 return 0, fmt.Errorf("sizer: could not find field %s.%s", v, field) 76 } 77 78 tag, err := protobufTag(f) 79 if err != nil { 80 return 0, fmt.Errorf("sizer: field %s.%s has no protobuf tag: %s", v, field, err) 81 } 82 83 // Protobuf encodes the tag and wire type in the same varint. It does this 84 // by allocating three bits for wire type at the base of the tag. 85 // 86 // https://developers.google.com/protocol-buffers/docs/encoding#structure 87 return varintLength(uint64(tag) << 3), nil 88 } 89 90 func varintLength(val uint64) int { 91 switch { 92 case val == 0: 93 return 0 94 case val < 0x80: 95 return 1 96 case val < 0x4000: 97 return 2 98 case val < 0x200000: 99 return 3 100 case val < 0x10000000: 101 return 4 102 case val < 0x800000000: 103 return 5 104 case val < 0x40000000000: 105 return 6 106 case val < 0x2000000000000: 107 return 7 108 case val < 0x100000000000000: 109 return 8 110 case val < 0x8000000000000000: 111 return 9 112 default: 113 // Maximum uvarint size. 114 return 10 115 } 116 } 117 118 func protobufTag(f reflect.StructField) (int, error) { 119 // If this field doesn't have a "protobuf" tag, ignore it. 120 value := f.Tag.Get("protobuf") 121 parts := strings.Split(value, ",") 122 if len(parts) < 2 { 123 return 0, errMalformedProtobufField 124 } 125 tag, err := strconv.Atoi(parts[1]) 126 if err != nil { 127 return 0, errMalformedProtobufField 128 } 129 return tag, nil 130 }