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

Side by Side 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, 3 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package bundler
6
7 import (
8 "errors"
9 "fmt"
10 "reflect"
11 "strconv"
12 "strings"
13
14 "github.com/golang/protobuf/proto"
15 "github.com/luci/luci-go/common/logdog/protocol"
16 "github.com/luci/luci-go/common/logdog/protocol/protoutil"
17 "github.com/luci/luci-go/common/logdog/types"
18 )
19
20 var (
21 bundleEntryTagSize int
22 logEntryTagSize int
23
24 errMalformedProtobufField = errors.New("malformed protobuf field")
25 )
26
27 func init() {
28 bundleEntryTagSize = mustCalculateTagSize(&protocol.ButlerLogBundle{}, " Entries")
29 logEntryTagSize = mustCalculateTagSize(&protocol.ButlerLogBundle_Entry{} , "Logs")
30 }
31
32 type fastSizer struct {
33 size int64
34
35 // As we add LogEntry to a ButlerLogBundle_Entry, the bundle entry's siz e (and
36 // therefore it's protobuf size prefix) will grow. We account for the gr owth
37 // by tracking the size of each ButlerLogBundle_Entry and updating it wh en
38 // we add a LogEntry to it. This is stored independently from the cumula tive
39 // size and factored in when Size() is calculated.
40 beSize map[types.StreamPath]int64
41
42 undoSize int64
43 undoPath *types.StreamPath
44 undoBeSize int64
45 }
46
47 // NewFastSizer is a Sizer that is optimized for LogDog protobufs.
48 //
49 // In exchange for rapid size calculation, it performs worst-case estimates on
50 // the unknown protocol overheads, leading to potential size overestimation.
51 func NewFastSizer(b *protocol.ButlerLogBundle) Sizer {
52 return &fastSizer{
53 size: int64(proto.Size(b)),
54 beSize: map[types.StreamPath]int64{},
55 }
56 }
57
58 func (b *fastSizer) Size() int64 {
59 size := b.size
60
61 for _, v := range b.beSize {
62 if v > 0 {
63 size += int64(varintLength(uint64(v)))
64 }
65 }
66 return size
67 }
68
69 func (b *fastSizer) Append(be *protocol.ButlerLogBundle_Entry, e *protocol.LogEn try) {
70 path := protoutil.DescriptorPath(be.GetDesc())
71 size := int64(0)
72 if !b.hasStream(path) {
73 // This is the first time we've seen this ButlerLogBundle_Entry. Add its
74 // static cost.
75 size += int64(bundleEntryTagSize + proto.Size(be))
76 }
77
78 if e != nil {
79 ps := proto.Size(e)
80 size += int64(logEntryTagSize + varintLength(uint64(ps)) + ps)
81 }
82
83 b.size += size
84 b.beSize[path] += size
85
86 b.undoSize = size
87 b.undoPath = &path
88 b.undoBeSize = size
89 }
90
91 func (b *fastSizer) Undo() {
92 b.size -= b.undoSize
93 if b.undoPath != nil {
94 b.beSize[*b.undoPath] -= b.undoBeSize
95 }
96
97 b.undoSize = 0
98 b.undoPath = nil
99 b.undoBeSize = 0
100 }
101
102 func (b *fastSizer) hasStream(path types.StreamPath) bool {
103 _, ok := b.beSize[path]
104 return ok
105 }
106
107 func mustCalculateTagSize(i interface{}, field string) int {
108 value, err := calculateTagSize(i, field)
109 if err != nil {
110 panic(err)
111 }
112 return value
113 }
114
115 func calculateTagSize(i interface{}, field string) (int, error) {
116 v := reflect.TypeOf(i)
117 if v.Kind() == reflect.Ptr {
118 v = v.Elem()
119 }
120 if v.Kind() != reflect.Struct {
121 return 0, fmt.Errorf("sizer: %s is not a struct", v)
122 }
123
124 f, ok := v.FieldByName(field)
125 if !ok {
126 return 0, fmt.Errorf("sizer: could not find field %s.%s", v, fie ld)
127 }
128
129 tag, err := protobufTag(f)
130 if err != nil {
131 return 0, fmt.Errorf("sizer: field %s.%s has no protobuf tag: %s ", v, field, err)
132 }
133
134 // Protobuf encodes the tag and wire type in the same varint. It does th is
135 // by allocating three bits for wire type at the base of the tag.
136 //
137 // https://developers.google.com/protocol-buffers/docs/encoding#structur e
138 return varintLength(uint64(tag) << 3), nil
139 }
140
141 func varintLength(val uint64) int {
142 switch {
143 case val < 0x80:
144 return 1
145 case val < 0x4000:
146 return 2
147 case val < 0x200000:
148 return 3
149 case val < 0x10000000:
150 return 4
151 case val < 0x800000000:
152 return 5
153 case val < 0x40000000000:
154 return 6
155 case val < 0x2000000000000:
156 return 7
157 case val < 0x100000000000000:
158 return 8
159 case val < 0x8000000000000000:
160 return 9
161 default:
162 // Maximum uvarint size.
163 return 10
164 }
165 }
166
167 func protobufTag(f reflect.StructField) (int, error) {
168 // If this field doesn't have a "protobuf" tag, ignore it.
169 value := f.Tag.Get("protobuf")
170 parts := strings.Split(value, ",")
171 if len(parts) < 2 {
172 return 0, errMalformedProtobufField
173 }
174 tag, err := strconv.Atoi(parts[1])
175 if err != nil {
176 return 0, errMalformedProtobufField
177 }
178 return tag, nil
179 }
OLDNEW
« 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