...

Source file src/go.opencensus.io/stats/view/view.go

Documentation: go.opencensus.io/stats/view

     1  // Copyright 2017, OpenCensus Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  
    16  package view
    17  
    18  import (
    19  	"bytes"
    20  	"errors"
    21  	"fmt"
    22  	"reflect"
    23  	"sort"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"go.opencensus.io/metric/metricdata"
    28  	"go.opencensus.io/stats"
    29  	"go.opencensus.io/tag"
    30  )
    31  
    32  // View allows users to aggregate the recorded stats.Measurements.
    33  // Views need to be passed to the Register function before data will be
    34  // collected and sent to Exporters.
    35  type View struct {
    36  	Name        string // Name of View. Must be unique. If unset, will default to the name of the Measure.
    37  	Description string // Description is a human-readable description for this view.
    38  
    39  	// TagKeys are the tag keys describing the grouping of this view.
    40  	// A single Row will be produced for each combination of associated tag values.
    41  	TagKeys []tag.Key
    42  
    43  	// Measure is a stats.Measure to aggregate in this view.
    44  	Measure stats.Measure
    45  
    46  	// Aggregation is the aggregation function to apply to the set of Measurements.
    47  	Aggregation *Aggregation
    48  }
    49  
    50  // WithName returns a copy of the View with a new name. This is useful for
    51  // renaming views to cope with limitations placed on metric names by various
    52  // backends.
    53  func (v *View) WithName(name string) *View {
    54  	vNew := *v
    55  	vNew.Name = name
    56  	return &vNew
    57  }
    58  
    59  // same compares two views and returns true if they represent the same aggregation.
    60  func (v *View) same(other *View) bool {
    61  	if v == other {
    62  		return true
    63  	}
    64  	if v == nil {
    65  		return false
    66  	}
    67  	return reflect.DeepEqual(v.Aggregation, other.Aggregation) &&
    68  		v.Measure.Name() == other.Measure.Name()
    69  }
    70  
    71  // ErrNegativeBucketBounds error returned if histogram contains negative bounds.
    72  //
    73  // Deprecated: this should not be public.
    74  var ErrNegativeBucketBounds = errors.New("negative bucket bounds not supported")
    75  
    76  // canonicalize canonicalizes v by setting explicit
    77  // defaults for Name and Description and sorting the TagKeys
    78  func (v *View) canonicalize() error {
    79  	if v.Measure == nil {
    80  		return fmt.Errorf("cannot register view %q: measure not set", v.Name)
    81  	}
    82  	if v.Aggregation == nil {
    83  		return fmt.Errorf("cannot register view %q: aggregation not set", v.Name)
    84  	}
    85  	if v.Name == "" {
    86  		v.Name = v.Measure.Name()
    87  	}
    88  	if v.Description == "" {
    89  		v.Description = v.Measure.Description()
    90  	}
    91  	if err := checkViewName(v.Name); err != nil {
    92  		return err
    93  	}
    94  	sort.Slice(v.TagKeys, func(i, j int) bool {
    95  		return v.TagKeys[i].Name() < v.TagKeys[j].Name()
    96  	})
    97  	sort.Float64s(v.Aggregation.Buckets)
    98  	for _, b := range v.Aggregation.Buckets {
    99  		if b < 0 {
   100  			return ErrNegativeBucketBounds
   101  		}
   102  	}
   103  	// drop 0 bucket silently.
   104  	v.Aggregation.Buckets = dropZeroBounds(v.Aggregation.Buckets...)
   105  
   106  	return nil
   107  }
   108  
   109  func dropZeroBounds(bounds ...float64) []float64 {
   110  	for i, bound := range bounds {
   111  		if bound > 0 {
   112  			return bounds[i:]
   113  		}
   114  	}
   115  	return []float64{}
   116  }
   117  
   118  // viewInternal is the internal representation of a View.
   119  type viewInternal struct {
   120  	view             *View  // view is the canonicalized View definition associated with this view.
   121  	subscribed       uint32 // 1 if someone is subscribed and data need to be exported, use atomic to access
   122  	collector        *collector
   123  	metricDescriptor *metricdata.Descriptor
   124  }
   125  
   126  func newViewInternal(v *View) (*viewInternal, error) {
   127  	return &viewInternal{
   128  		view:             v,
   129  		collector:        &collector{make(map[string]AggregationData), v.Aggregation},
   130  		metricDescriptor: viewToMetricDescriptor(v),
   131  	}, nil
   132  }
   133  
   134  func (v *viewInternal) subscribe() {
   135  	atomic.StoreUint32(&v.subscribed, 1)
   136  }
   137  
   138  func (v *viewInternal) unsubscribe() {
   139  	atomic.StoreUint32(&v.subscribed, 0)
   140  }
   141  
   142  // isSubscribed returns true if the view is exporting
   143  // data by subscription.
   144  func (v *viewInternal) isSubscribed() bool {
   145  	return atomic.LoadUint32(&v.subscribed) == 1
   146  }
   147  
   148  func (v *viewInternal) clearRows() {
   149  	v.collector.clearRows()
   150  }
   151  
   152  func (v *viewInternal) collectedRows() []*Row {
   153  	return v.collector.collectedRows(v.view.TagKeys)
   154  }
   155  
   156  func (v *viewInternal) addSample(m *tag.Map, val float64, attachments map[string]interface{}, t time.Time) {
   157  	if !v.isSubscribed() {
   158  		return
   159  	}
   160  	sig := string(encodeWithKeys(m, v.view.TagKeys))
   161  	v.collector.addSample(sig, val, attachments, t)
   162  }
   163  
   164  // A Data is a set of rows about usage of the single measure associated
   165  // with the given view. Each row is specific to a unique set of tags.
   166  type Data struct {
   167  	View       *View
   168  	Start, End time.Time
   169  	Rows       []*Row
   170  }
   171  
   172  // Row is the collected value for a specific set of key value pairs a.k.a tags.
   173  type Row struct {
   174  	Tags []tag.Tag
   175  	Data AggregationData
   176  }
   177  
   178  func (r *Row) String() string {
   179  	var buffer bytes.Buffer
   180  	buffer.WriteString("{ ")
   181  	buffer.WriteString("{ ")
   182  	for _, t := range r.Tags {
   183  		buffer.WriteString(fmt.Sprintf("{%v %v}", t.Key.Name(), t.Value))
   184  	}
   185  	buffer.WriteString(" }")
   186  	buffer.WriteString(fmt.Sprintf("%v", r.Data))
   187  	buffer.WriteString(" }")
   188  	return buffer.String()
   189  }
   190  
   191  // Equal returns true if both rows are equal. Tags are expected to be ordered
   192  // by the key name. Even if both rows have the same tags but the tags appear in
   193  // different orders it will return false.
   194  func (r *Row) Equal(other *Row) bool {
   195  	if r == other {
   196  		return true
   197  	}
   198  	return reflect.DeepEqual(r.Tags, other.Tags) && r.Data.equal(other.Data)
   199  }
   200  
   201  const maxNameLength = 255
   202  
   203  // Returns true if the given string contains only printable characters.
   204  func isPrintable(str string) bool {
   205  	for _, r := range str {
   206  		if !(r >= ' ' && r <= '~') {
   207  			return false
   208  		}
   209  	}
   210  	return true
   211  }
   212  
   213  func checkViewName(name string) error {
   214  	if len(name) > maxNameLength {
   215  		return fmt.Errorf("view name cannot be larger than %v", maxNameLength)
   216  	}
   217  	if !isPrintable(name) {
   218  		return fmt.Errorf("view name needs to be an ASCII string")
   219  	}
   220  	return nil
   221  }
   222  

View as plain text