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
16package view
17
18import (
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 to be before data will be
34// collected and sent to Exporters.
35type 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 tp 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.
53func (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.
60func (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.
74var 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
78func (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
109func 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.
119type 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
126func 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
134func (v *viewInternal) subscribe() {
135	atomic.StoreUint32(&v.subscribed, 1)
136}
137
138func (v *viewInternal) unsubscribe() {
139	atomic.StoreUint32(&v.subscribed, 0)
140}
141
142// isSubscribed returns true if the view is exporting
143// data by subscription.
144func (v *viewInternal) isSubscribed() bool {
145	return atomic.LoadUint32(&v.subscribed) == 1
146}
147
148func (v *viewInternal) clearRows() {
149	v.collector.clearRows()
150}
151
152func (v *viewInternal) collectedRows() []*Row {
153	return v.collector.collectedRows(v.view.TagKeys)
154}
155
156func (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.
166type 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.
173type Row struct {
174	Tags []tag.Tag
175	Data AggregationData
176}
177
178func (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 both rows have the same tags but the tags appear in
193// different orders it will return false.
194func (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
201const maxNameLength = 255
202
203// Returns true if the given string contains only printable characters.
204func isPrintable(str string) bool {
205	for _, r := range str {
206		if !(r >= ' ' && r <= '~') {
207			return false
208		}
209	}
210	return true
211}
212
213func 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