1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
/*
*
* Copyright 2019 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package profiling contains two logical components: buffer.go and
// profiling.go. The former implements a circular buffer (a.k.a. ring buffer)
// in a lock-free manner using atomics. This ring buffer is used by
// profiling.go to store various statistics. For example, StreamStats is a
// circular buffer of Stat objects, each of which is comprised of Timers.
//
// This abstraction is designed to accommodate more stats in the future; for
// example, if one wants to profile the load balancing layer, which is
// independent of RPC queries, a separate CircularBuffer can be used.
//
// Note that the circular buffer simply takes any interface{}. In the future,
// more types of measurements (such as the number of memory allocations) could
// be measured, which might require a different type of object being pushed
// into the circular buffer.
package profiling
import (
"errors"
"sync"
"sync/atomic"
"time"
"google.golang.org/grpc/internal/profiling/buffer"
)
// 0 or 1 representing profiling off and on, respectively. Use IsEnabled and
// Enable to get and set this in a safe manner.
var profilingEnabled uint32
// IsEnabled returns whether or not profiling is enabled.
func IsEnabled() bool {
return atomic.LoadUint32(&profilingEnabled) > 0
}
// Enable turns profiling on and off.
//
// Note that it is impossible to enable profiling for one server and leave it
// turned off for another. This is intentional and by design -- if the status
// of profiling was server-specific, clients wouldn't be able to profile
// themselves. As a result, Enable turns profiling on and off for all servers
// and clients in the binary. Each stat will be, however, tagged with whether
// it's a client stat or a server stat; so you should be able to filter for the
// right type of stats in post-processing.
func Enable(enabled bool) {
if enabled {
atomic.StoreUint32(&profilingEnabled, 1)
} else {
atomic.StoreUint32(&profilingEnabled, 0)
}
}
// A Timer represents the wall-clock beginning and ending of a logical
// operation.
type Timer struct {
// Tags is a comma-separated list of strings (usually forward-slash-separated
// hierarchical strings) used to categorize a Timer.
Tags string
// Begin marks the beginning of this timer. The timezone is unspecified, but
// must use the same timezone as End; this is so shave off the small, but
// non-zero time required to convert to a standard timezone such as UTC.
Begin time.Time
// End marks the end of a timer.
End time.Time
// Each Timer must be started and ended within the same goroutine; GoID
// captures this goroutine ID. The Go runtime does not typically expose this
// information, so this is set to zero in the typical case. However, a
// trivial patch to the runtime package can make this field useful. See
// goid_modified.go in this package for more details.
GoID int64
}
// NewTimer creates and returns a new Timer object. This is useful when you
// don't already have a Stat object to associate this Timer with; for example,
// before the context of a new RPC query is created, a Timer may be needed to
// measure transport-related operations.
//
// Use AppendTimer to append the returned Timer to a Stat.
func NewTimer(tags string) *Timer {
return &Timer{
Tags: tags,
Begin: time.Now(),
GoID: goid(),
}
}
// Egress sets the End field of a timer to the current time.
func (timer *Timer) Egress() {
if timer == nil {
return
}
timer.End = time.Now()
}
// A Stat is a collection of Timers that represent timing information for
// different components within this Stat. For example, a Stat may be used to
// reference the entire lifetime of an RPC request, with Timers within it
// representing different components such as encoding, compression, and
// transport.
//
// The user is expected to use the included helper functions to do operations
// on the Stat such as creating or appending a new timer. Direct operations on
// the Stat's exported fields (which are exported for encoding reasons) may
// lead to data races.
type Stat struct {
// Tags is a comma-separated list of strings used to categorize a Stat.
Tags string
// Stats may also need to store other unstructured information specific to
// this stat. For example, a StreamStat will use these bytes to encode the
// connection ID and stream ID for each RPC to uniquely identify it. The
// encoding that must be used is unspecified.
Metadata []byte
// A collection of *Timers and a mutex for append operations on the slice.
mu sync.Mutex
Timers []*Timer
}
// A power of two that's large enough to hold all timers within an average RPC
// request (defined to be a unary request) without any reallocation. A typical
// unary RPC creates 80-100 timers for various things. While this number is
// purely anecdotal and may change in the future as the resolution of profiling
// increases or decreases, it serves as a good estimate for what the initial
// allocation size should be.
const defaultStatAllocatedTimers int32 = 128
// NewStat creates and returns a new Stat object.
func NewStat(tags string) *Stat {
return &Stat{
Tags: tags,
Timers: make([]*Timer, 0, defaultStatAllocatedTimers),
}
}
// NewTimer creates a Timer object within the given stat if stat is non-nil.
// The value passed in tags will be attached to the newly created Timer.
// NewTimer also automatically sets the Begin value of the Timer to the current
// time. The user is expected to call stat.Egress with the returned index as
// argument to mark the end.
func (stat *Stat) NewTimer(tags string) *Timer {
if stat == nil {
return nil
}
timer := &Timer{
Tags: tags,
GoID: goid(),
Begin: time.Now(),
}
stat.mu.Lock()
stat.Timers = append(stat.Timers, timer)
stat.mu.Unlock()
return timer
}
// AppendTimer appends a given Timer object to the internal slice of timers. A
// deep copy of the timer is made (i.e. no reference is retained to this
// pointer) and the user is expected to lose their reference to the timer to
// allow the Timer object to be garbage collected.
func (stat *Stat) AppendTimer(timer *Timer) {
if stat == nil || timer == nil {
return
}
stat.mu.Lock()
stat.Timers = append(stat.Timers, timer)
stat.mu.Unlock()
}
// statsInitialized is 0 before InitStats has been called. Changed to 1 by
// exactly one call to InitStats.
var statsInitialized int32
// Stats for the last defaultStreamStatsBufsize RPCs will be stored in memory.
// This is can be configured by the registering server at profiling service
// initialization with google.golang.org/grpc/profiling/service.ProfilingConfig
const defaultStreamStatsSize uint32 = 16 << 10
// StreamStats is a CircularBuffer containing data from the last N RPC calls
// served, where N is set by the user. This will contain both server stats and
// client stats (but each stat will be tagged with whether it's a server or a
// client in its Tags).
var StreamStats *buffer.CircularBuffer
var errAlreadyInitialized = errors.New("profiling may be initialized at most once")
// InitStats initializes all the relevant Stat objects. Must be called exactly
// once per lifetime of a process; calls after the first one will return an
// error.
func InitStats(streamStatsSize uint32) error {
var err error
if !atomic.CompareAndSwapInt32(&statsInitialized, 0, 1) {
return errAlreadyInitialized
}
if streamStatsSize == 0 {
streamStatsSize = defaultStreamStatsSize
}
StreamStats, err = buffer.NewCircularBuffer(streamStatsSize)
if err != nil {
return err
}
return nil
}
|