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