1 // Copyright 2017, OpenCensus Authors
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
27 "go.opencensus.io/metric/metricdata"
28 "go.opencensus.io/stats"
29 "go.opencensus.io/tag"
32 // View allows users to aggregate the recorded stats.Measurements.
33 // Views need to be passed to the Register function to be before data will be
34 // collected and sent to Exporters.
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.
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.
43 // Measure is a stats.Measure to aggregate in this view.
46 // Aggregation is the aggregation function tp apply to the set of Measurements.
47 Aggregation *Aggregation
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
53 func (v *View) WithName(name string) *View {
59 // same compares two views and returns true if they represent the same aggregation.
60 func (v *View) same(other *View) bool {
67 return reflect.DeepEqual(v.Aggregation, other.Aggregation) &&
68 v.Measure.Name() == other.Measure.Name()
71 // ErrNegativeBucketBounds error returned if histogram contains negative bounds.
73 // Deprecated: this should not be public.
74 var ErrNegativeBucketBounds = errors.New("negative bucket bounds not supported")
76 // canonicalize canonicalizes v by setting explicit
77 // defaults for Name and Description and sorting the TagKeys
78 func (v *View) canonicalize() error {
80 return fmt.Errorf("cannot register view %q: measure not set", v.Name)
82 if v.Aggregation == nil {
83 return fmt.Errorf("cannot register view %q: aggregation not set", v.Name)
86 v.Name = v.Measure.Name()
88 if v.Description == "" {
89 v.Description = v.Measure.Description()
91 if err := checkViewName(v.Name); err != nil {
94 sort.Slice(v.TagKeys, func(i, j int) bool {
95 return v.TagKeys[i].Name() < v.TagKeys[j].Name()
97 sort.Float64s(v.Aggregation.Buckets)
98 for _, b := range v.Aggregation.Buckets {
100 return ErrNegativeBucketBounds
103 // drop 0 bucket silently.
104 v.Aggregation.Buckets = dropZeroBounds(v.Aggregation.Buckets...)
109 func dropZeroBounds(bounds ...float64) []float64 {
110 for i, bound := range bounds {
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
123 metricDescriptor *metricdata.Descriptor
126 func newViewInternal(v *View) (*viewInternal, error) {
127 return &viewInternal{
129 collector: &collector{make(map[string]AggregationData), v.Aggregation},
130 metricDescriptor: viewToMetricDescriptor(v),
134 func (v *viewInternal) subscribe() {
135 atomic.StoreUint32(&v.subscribed, 1)
138 func (v *viewInternal) unsubscribe() {
139 atomic.StoreUint32(&v.subscribed, 0)
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
148 func (v *viewInternal) clearRows() {
149 v.collector.clearRows()
152 func (v *viewInternal) collectedRows() []*Row {
153 return v.collector.collectedRows(v.view.TagKeys)
156 func (v *viewInternal) addSample(m *tag.Map, val float64, attachments map[string]interface{}, t time.Time) {
157 if !v.isSubscribed() {
160 sig := string(encodeWithKeys(m, v.view.TagKeys))
161 v.collector.addSample(sig, val, attachments, t)
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.
172 // Row is the collected value for a specific set of key value pairs a.k.a tags.
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))
185 buffer.WriteString(" }")
186 buffer.WriteString(fmt.Sprintf("%v", r.Data))
187 buffer.WriteString(" }")
188 return buffer.String()
191 // Equal returns true if both rows are equal. Tags are expected to be ordered
192 // by the key name. Even 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 {
198 return reflect.DeepEqual(r.Tags, other.Tags) && r.Data.equal(other.Data)
201 const maxNameLength = 255
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 <= '~') {
213 func checkViewName(name string) error {
214 if len(name) > maxNameLength {
215 return fmt.Errorf("view name cannot be larger than %v", maxNameLength)
217 if !isPrintable(name) {
218 return fmt.Errorf("view name needs to be an ASCII string")