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