| /* | |
| * | |
| * 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 type. 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 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 | |
| } |