Chromium Code Reviews| Index: client/internal/logdog/butler/bundler/sizer.go |
| diff --git a/client/internal/logdog/butler/bundler/sizer.go b/client/internal/logdog/butler/bundler/sizer.go |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..e28c73e634b90f6c07ffbedd639ac747e25f25fd |
| --- /dev/null |
| +++ b/client/internal/logdog/butler/bundler/sizer.go |
| @@ -0,0 +1,120 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
|
estaab
2015/11/12 00:51:48
Can you add some file-level comments describing th
dnj (Google)
2015/11/12 02:00:41
Done.
|
| +package bundler |
| + |
| +import ( |
| + "errors" |
| + "fmt" |
| + "reflect" |
| + "strconv" |
| + "strings" |
| + |
| + "github.com/golang/protobuf/proto" |
| + "github.com/luci/luci-go/common/logdog/protocol" |
| +) |
| + |
| +var ( |
| + sizeOfBundleEntryTag int |
| + sizeOfLogEntryTag int |
| + sizeOfTerminalTag int |
| + sizeOfTerminalIndexTag int |
| + |
| + errMalformedProtobufField = errors.New("malformed protobuf field") |
| +) |
| + |
| +const ( |
| + // sizeOfBoolTrue is the size of the "true" boolean value. |
| + sizeOfBoolTrue = 1 |
| +) |
| + |
| +func init() { |
| + b := &protocol.ButlerLogBundle{} |
| + sizeOfBundleEntryTag = mustCalculateTagSize(b, "Entries") |
| + |
| + be := &protocol.ButlerLogBundle_Entry{} |
| + sizeOfLogEntryTag = mustCalculateTagSize(be, "Logs") |
| + sizeOfTerminalTag = mustCalculateTagSize(be, "Terminal") |
| + sizeOfTerminalIndexTag = mustCalculateTagSize(be, "TerminalIndex") |
| +} |
| + |
| +func mustCalculateTagSize(i interface{}, field string) int { |
| + value, err := calculateTagSize(i, field) |
| + if err != nil { |
| + panic(err) |
| + } |
| + return value |
| +} |
| + |
| +func protoSize(m proto.Message) int { |
| + return proto.Size(m) |
| +} |
| + |
| +func calculateTagSize(i interface{}, field string) (int, error) { |
| + v := reflect.TypeOf(i) |
| + if v.Kind() == reflect.Ptr { |
| + v = v.Elem() |
| + } |
| + if v.Kind() != reflect.Struct { |
| + return 0, fmt.Errorf("sizer: %s is not a struct", v) |
| + } |
| + |
| + f, ok := v.FieldByName(field) |
| + if !ok { |
| + return 0, fmt.Errorf("sizer: could not find field %s.%s", v, field) |
| + } |
| + |
| + tag, err := protobufTag(f) |
| + if err != nil { |
| + return 0, fmt.Errorf("sizer: field %s.%s has no protobuf tag: %s", v, field, err) |
| + } |
| + |
| + // Protobuf encodes the tag and wire type in the same varint. It does this |
| + // by allocating three bits for wire type at the base of the tag. |
| + // |
| + // https://developers.google.com/protocol-buffers/docs/encoding#structure |
| + return varintLength(uint64(tag) << 3), nil |
| +} |
| + |
| +func varintLength(val uint64) int { |
| + switch { |
| + case val == 0: |
| + return 0 |
| + case val < 0x80: |
| + return 1 |
| + case val < 0x4000: |
| + return 2 |
| + case val < 0x200000: |
| + return 3 |
| + case val < 0x10000000: |
| + return 4 |
| + case val < 0x800000000: |
| + return 5 |
| + case val < 0x40000000000: |
| + return 6 |
| + case val < 0x2000000000000: |
| + return 7 |
| + case val < 0x100000000000000: |
| + return 8 |
| + case val < 0x8000000000000000: |
| + return 9 |
| + default: |
| + // Maximum uvarint size. |
| + return 10 |
| + } |
| +} |
| + |
| +func protobufTag(f reflect.StructField) (int, error) { |
| + // If this field doesn't have a "protobuf" tag, ignore it. |
| + value := f.Tag.Get("protobuf") |
| + parts := strings.Split(value, ",") |
| + if len(parts) < 2 { |
| + return 0, errMalformedProtobufField |
| + } |
| + tag, err := strconv.Atoi(parts[1]) |
| + if err != nil { |
| + return 0, errMalformedProtobufField |
| + } |
| + return tag, nil |
| +} |