Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(23)

Unified Diff: client/internal/logdog/butler/bundler/sizer_fast.go

Issue 1276923003: logdog: Add bundler library. (Closed) Base URL: https://github.com/luci/luci-go@logdog-review-streamserver
Patch Set: Updated from review. Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: client/internal/logdog/butler/bundler/sizer_fast.go
diff --git a/client/internal/logdog/butler/bundler/sizer_fast.go b/client/internal/logdog/butler/bundler/sizer_fast.go
new file mode 100644
index 0000000000000000000000000000000000000000..ebcdf71d9fd684d9036e55b90bb7a7384c2a7586
--- /dev/null
+++ b/client/internal/logdog/butler/bundler/sizer_fast.go
@@ -0,0 +1,179 @@
+// 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.
+
+package bundler
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+
+ "github.com/golang/protobuf/proto"
+ "github.com/luci/luci-go/common/logdog/protocol"
+ "github.com/luci/luci-go/common/logdog/protocol/protoutil"
+ "github.com/luci/luci-go/common/logdog/types"
+)
+
+var (
+ bundleEntryTagSize int
+ logEntryTagSize int
+
+ errMalformedProtobufField = errors.New("malformed protobuf field")
+)
+
+func init() {
+ bundleEntryTagSize = mustCalculateTagSize(&protocol.ButlerLogBundle{}, "Entries")
+ logEntryTagSize = mustCalculateTagSize(&protocol.ButlerLogBundle_Entry{}, "Logs")
+}
+
+type fastSizer struct {
+ size int64
+
+ // As we add LogEntry to a ButlerLogBundle_Entry, the bundle entry's size (and
+ // therefore it's protobuf size prefix) will grow. We account for the growth
+ // by tracking the size of each ButlerLogBundle_Entry and updating it when
+ // we add a LogEntry to it. This is stored independently from the cumulative
+ // size and factored in when Size() is calculated.
+ beSize map[types.StreamPath]int64
+
+ undoSize int64
+ undoPath *types.StreamPath
+ undoBeSize int64
+}
+
+// NewFastSizer is a Sizer that is optimized for LogDog protobufs.
+//
+// In exchange for rapid size calculation, it performs worst-case estimates on
+// the unknown protocol overheads, leading to potential size overestimation.
+func NewFastSizer(b *protocol.ButlerLogBundle) Sizer {
+ return &fastSizer{
+ size: int64(proto.Size(b)),
+ beSize: map[types.StreamPath]int64{},
+ }
+}
+
+func (b *fastSizer) Size() int64 {
+ size := b.size
+
+ for _, v := range b.beSize {
+ if v > 0 {
+ size += int64(varintLength(uint64(v)))
+ }
+ }
+ return size
+}
+
+func (b *fastSizer) Append(be *protocol.ButlerLogBundle_Entry, e *protocol.LogEntry) {
+ path := protoutil.DescriptorPath(be.GetDesc())
+ size := int64(0)
+ if !b.hasStream(path) {
+ // This is the first time we've seen this ButlerLogBundle_Entry. Add its
+ // static cost.
+ size += int64(bundleEntryTagSize + proto.Size(be))
+ }
+
+ if e != nil {
+ ps := proto.Size(e)
+ size += int64(logEntryTagSize + varintLength(uint64(ps)) + ps)
+ }
+
+ b.size += size
+ b.beSize[path] += size
+
+ b.undoSize = size
+ b.undoPath = &path
+ b.undoBeSize = size
+}
+
+func (b *fastSizer) Undo() {
+ b.size -= b.undoSize
+ if b.undoPath != nil {
+ b.beSize[*b.undoPath] -= b.undoBeSize
+ }
+
+ b.undoSize = 0
+ b.undoPath = nil
+ b.undoBeSize = 0
+}
+
+func (b *fastSizer) hasStream(path types.StreamPath) bool {
+ _, ok := b.beSize[path]
+ return ok
+}
+
+func mustCalculateTagSize(i interface{}, field string) int {
+ value, err := calculateTagSize(i, field)
+ if err != nil {
+ panic(err)
+ }
+ return value
+}
+
+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 < 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
+}
« no previous file with comments | « client/internal/logdog/butler/bundler/doc.go ('k') | client/internal/logdog/butler/bundler/sizer_fast_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698