| // Copyright 2017, OpenCensus 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 trace |
| |
| import ( |
| "sync" |
| "time" |
| |
| "go.opencensus.io/internal" |
| ) |
| |
| const ( |
| maxBucketSize = 100000 |
| defaultBucketSize = 10 |
| ) |
| |
| var ( |
| ssmu sync.RWMutex // protects spanStores |
| spanStores = make(map[string]*spanStore) |
| ) |
| |
| // This exists purely to avoid exposing internal methods used by z-Pages externally. |
| type internalOnly struct{} |
| |
| func init() { |
| //TODO(#412): remove |
| internal.Trace = &internalOnly{} |
| } |
| |
| // ReportActiveSpans returns the active spans for the given name. |
| func (i internalOnly) ReportActiveSpans(name string) []*SpanData { |
| s := spanStoreForName(name) |
| if s == nil { |
| return nil |
| } |
| var out []*SpanData |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| for span := range s.active { |
| out = append(out, span.makeSpanData()) |
| } |
| return out |
| } |
| |
| // ReportSpansByError returns a sample of error spans. |
| // |
| // If code is nonzero, only spans with that status code are returned. |
| func (i internalOnly) ReportSpansByError(name string, code int32) []*SpanData { |
| s := spanStoreForName(name) |
| if s == nil { |
| return nil |
| } |
| var out []*SpanData |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| if code != 0 { |
| if b, ok := s.errors[code]; ok { |
| for _, sd := range b.buffer { |
| if sd == nil { |
| break |
| } |
| out = append(out, sd) |
| } |
| } |
| } else { |
| for _, b := range s.errors { |
| for _, sd := range b.buffer { |
| if sd == nil { |
| break |
| } |
| out = append(out, sd) |
| } |
| } |
| } |
| return out |
| } |
| |
| // ConfigureBucketSizes sets the number of spans to keep per latency and error |
| // bucket for different span names. |
| func (i internalOnly) ConfigureBucketSizes(bcs []internal.BucketConfiguration) { |
| for _, bc := range bcs { |
| latencyBucketSize := bc.MaxRequestsSucceeded |
| if latencyBucketSize < 0 { |
| latencyBucketSize = 0 |
| } |
| if latencyBucketSize > maxBucketSize { |
| latencyBucketSize = maxBucketSize |
| } |
| errorBucketSize := bc.MaxRequestsErrors |
| if errorBucketSize < 0 { |
| errorBucketSize = 0 |
| } |
| if errorBucketSize > maxBucketSize { |
| errorBucketSize = maxBucketSize |
| } |
| spanStoreSetSize(bc.Name, latencyBucketSize, errorBucketSize) |
| } |
| } |
| |
| // ReportSpansPerMethod returns a summary of what spans are being stored for each span name. |
| func (i internalOnly) ReportSpansPerMethod() map[string]internal.PerMethodSummary { |
| out := make(map[string]internal.PerMethodSummary) |
| ssmu.RLock() |
| defer ssmu.RUnlock() |
| for name, s := range spanStores { |
| s.mu.Lock() |
| p := internal.PerMethodSummary{ |
| Active: len(s.active), |
| } |
| for code, b := range s.errors { |
| p.ErrorBuckets = append(p.ErrorBuckets, internal.ErrorBucketSummary{ |
| ErrorCode: code, |
| Size: b.size(), |
| }) |
| } |
| for i, b := range s.latency { |
| min, max := latencyBucketBounds(i) |
| p.LatencyBuckets = append(p.LatencyBuckets, internal.LatencyBucketSummary{ |
| MinLatency: min, |
| MaxLatency: max, |
| Size: b.size(), |
| }) |
| } |
| s.mu.Unlock() |
| out[name] = p |
| } |
| return out |
| } |
| |
| // ReportSpansByLatency returns a sample of successful spans. |
| // |
| // minLatency is the minimum latency of spans to be returned. |
| // maxLatency, if nonzero, is the maximum latency of spans to be returned. |
| func (i internalOnly) ReportSpansByLatency(name string, minLatency, maxLatency time.Duration) []*SpanData { |
| s := spanStoreForName(name) |
| if s == nil { |
| return nil |
| } |
| var out []*SpanData |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| for i, b := range s.latency { |
| min, max := latencyBucketBounds(i) |
| if i+1 != len(s.latency) && max <= minLatency { |
| continue |
| } |
| if maxLatency != 0 && maxLatency < min { |
| continue |
| } |
| for _, sd := range b.buffer { |
| if sd == nil { |
| break |
| } |
| if minLatency != 0 || maxLatency != 0 { |
| d := sd.EndTime.Sub(sd.StartTime) |
| if d < minLatency { |
| continue |
| } |
| if maxLatency != 0 && d > maxLatency { |
| continue |
| } |
| } |
| out = append(out, sd) |
| } |
| } |
| return out |
| } |
| |
| // spanStore keeps track of spans stored for a particular span name. |
| // |
| // It contains all active spans; a sample of spans for failed requests, |
| // categorized by error code; and a sample of spans for successful requests, |
| // bucketed by latency. |
| type spanStore struct { |
| mu sync.Mutex // protects everything below. |
| active map[*Span]struct{} |
| errors map[int32]*bucket |
| latency []bucket |
| maxSpansPerErrorBucket int |
| } |
| |
| // newSpanStore creates a span store. |
| func newSpanStore(name string, latencyBucketSize int, errorBucketSize int) *spanStore { |
| s := &spanStore{ |
| active: make(map[*Span]struct{}), |
| latency: make([]bucket, len(defaultLatencies)+1), |
| maxSpansPerErrorBucket: errorBucketSize, |
| } |
| for i := range s.latency { |
| s.latency[i] = makeBucket(latencyBucketSize) |
| } |
| return s |
| } |
| |
| // spanStoreForName returns the spanStore for the given name. |
| // |
| // It returns nil if it doesn't exist. |
| func spanStoreForName(name string) *spanStore { |
| var s *spanStore |
| ssmu.RLock() |
| s, _ = spanStores[name] |
| ssmu.RUnlock() |
| return s |
| } |
| |
| // spanStoreForNameCreateIfNew returns the spanStore for the given name. |
| // |
| // It creates it if it didn't exist. |
| func spanStoreForNameCreateIfNew(name string) *spanStore { |
| ssmu.RLock() |
| s, ok := spanStores[name] |
| ssmu.RUnlock() |
| if ok { |
| return s |
| } |
| ssmu.Lock() |
| defer ssmu.Unlock() |
| s, ok = spanStores[name] |
| if ok { |
| return s |
| } |
| s = newSpanStore(name, defaultBucketSize, defaultBucketSize) |
| spanStores[name] = s |
| return s |
| } |
| |
| // spanStoreSetSize resizes the spanStore for the given name. |
| // |
| // It creates it if it didn't exist. |
| func spanStoreSetSize(name string, latencyBucketSize int, errorBucketSize int) { |
| ssmu.RLock() |
| s, ok := spanStores[name] |
| ssmu.RUnlock() |
| if ok { |
| s.resize(latencyBucketSize, errorBucketSize) |
| return |
| } |
| ssmu.Lock() |
| defer ssmu.Unlock() |
| s, ok = spanStores[name] |
| if ok { |
| s.resize(latencyBucketSize, errorBucketSize) |
| return |
| } |
| s = newSpanStore(name, latencyBucketSize, errorBucketSize) |
| spanStores[name] = s |
| } |
| |
| func (s *spanStore) resize(latencyBucketSize int, errorBucketSize int) { |
| s.mu.Lock() |
| for i := range s.latency { |
| s.latency[i].resize(latencyBucketSize) |
| } |
| for _, b := range s.errors { |
| b.resize(errorBucketSize) |
| } |
| s.maxSpansPerErrorBucket = errorBucketSize |
| s.mu.Unlock() |
| } |
| |
| // add adds a span to the active bucket of the spanStore. |
| func (s *spanStore) add(span *Span) { |
| s.mu.Lock() |
| s.active[span] = struct{}{} |
| s.mu.Unlock() |
| } |
| |
| // finished removes a span from the active set, and adds a corresponding |
| // SpanData to a latency or error bucket. |
| func (s *spanStore) finished(span *Span, sd *SpanData) { |
| latency := sd.EndTime.Sub(sd.StartTime) |
| if latency < 0 { |
| latency = 0 |
| } |
| code := sd.Status.Code |
| |
| s.mu.Lock() |
| delete(s.active, span) |
| if code == 0 { |
| s.latency[latencyBucket(latency)].add(sd) |
| } else { |
| if s.errors == nil { |
| s.errors = make(map[int32]*bucket) |
| } |
| if b := s.errors[code]; b != nil { |
| b.add(sd) |
| } else { |
| b := makeBucket(s.maxSpansPerErrorBucket) |
| s.errors[code] = &b |
| b.add(sd) |
| } |
| } |
| s.mu.Unlock() |
| } |