Browse code

bump go.opencensus.io v0.22.3

full diff: https://github.com/census-instrumentation/opencensus-go/compare/v0.11.0...v0.22.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2019/08/26 22:52:43
Showing 70 changed files
... ...
@@ -117,7 +117,8 @@ github.com/bsphere/le_go                            7a984a84b5492ae539b79b62fb4a
117 117
 # gcplogs deps
118 118
 golang.org/x/oauth2                                 bf48bf16ab8d622ce64ec6ce98d2c98f916b6303
119 119
 google.golang.org/api                               de943baf05a022a8f921b544b7827bacaba1aed5
120
-go.opencensus.io                                    c3ed530f775d85e577ca652cb052a52c078aad26 # v0.11.0
120
+github.com/golang/groupcache                        869f871628b6baa9cfbc11732cdf6546b17c1298
121
+go.opencensus.io                                    d835ff86be02193d324330acdb7d65546b05f814 # v0.22.3
121 122
 cloud.google.com/go                                 ceeb313ad77b789a7fa5287b36a1d127b69b7093 # v0.44.3
122 123
 github.com/googleapis/gax-go                        bd5b16380fd03dc758d11cef74ba2e3bc8b0e8c2 # v2.0.5
123 124
 google.golang.org/genproto                          3f1135a288c9a07e340ae8ba4cc6c7065a3160e8
124 125
new file mode 100644
... ...
@@ -0,0 +1,191 @@
0
+Apache License
1
+Version 2.0, January 2004
2
+http://www.apache.org/licenses/
3
+
4
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
5
+
6
+1. Definitions.
7
+
8
+"License" shall mean the terms and conditions for use, reproduction, and
9
+distribution as defined by Sections 1 through 9 of this document.
10
+
11
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
12
+owner that is granting the License.
13
+
14
+"Legal Entity" shall mean the union of the acting entity and all other entities
15
+that control, are controlled by, or are under common control with that entity.
16
+For the purposes of this definition, "control" means (i) the power, direct or
17
+indirect, to cause the direction or management of such entity, whether by
18
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
19
+outstanding shares, or (iii) beneficial ownership of such entity.
20
+
21
+"You" (or "Your") shall mean an individual or Legal Entity exercising
22
+permissions granted by this License.
23
+
24
+"Source" form shall mean the preferred form for making modifications, including
25
+but not limited to software source code, documentation source, and configuration
26
+files.
27
+
28
+"Object" form shall mean any form resulting from mechanical transformation or
29
+translation of a Source form, including but not limited to compiled object code,
30
+generated documentation, and conversions to other media types.
31
+
32
+"Work" shall mean the work of authorship, whether in Source or Object form, made
33
+available under the License, as indicated by a copyright notice that is included
34
+in or attached to the work (an example is provided in the Appendix below).
35
+
36
+"Derivative Works" shall mean any work, whether in Source or Object form, that
37
+is based on (or derived from) the Work and for which the editorial revisions,
38
+annotations, elaborations, or other modifications represent, as a whole, an
39
+original work of authorship. For the purposes of this License, Derivative Works
40
+shall not include works that remain separable from, or merely link (or bind by
41
+name) to the interfaces of, the Work and Derivative Works thereof.
42
+
43
+"Contribution" shall mean any work of authorship, including the original version
44
+of the Work and any modifications or additions to that Work or Derivative Works
45
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
46
+by the copyright owner or by an individual or Legal Entity authorized to submit
47
+on behalf of the copyright owner. For the purposes of this definition,
48
+"submitted" means any form of electronic, verbal, or written communication sent
49
+to the Licensor or its representatives, including but not limited to
50
+communication on electronic mailing lists, source code control systems, and
51
+issue tracking systems that are managed by, or on behalf of, the Licensor for
52
+the purpose of discussing and improving the Work, but excluding communication
53
+that is conspicuously marked or otherwise designated in writing by the copyright
54
+owner as "Not a Contribution."
55
+
56
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
57
+of whom a Contribution has been received by Licensor and subsequently
58
+incorporated within the Work.
59
+
60
+2. Grant of Copyright License.
61
+
62
+Subject to the terms and conditions of this License, each Contributor hereby
63
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
64
+irrevocable copyright license to reproduce, prepare Derivative Works of,
65
+publicly display, publicly perform, sublicense, and distribute the Work and such
66
+Derivative Works in Source or Object form.
67
+
68
+3. Grant of Patent License.
69
+
70
+Subject to the terms and conditions of this License, each Contributor hereby
71
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
72
+irrevocable (except as stated in this section) patent license to make, have
73
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
74
+such license applies only to those patent claims licensable by such Contributor
75
+that are necessarily infringed by their Contribution(s) alone or by combination
76
+of their Contribution(s) with the Work to which such Contribution(s) was
77
+submitted. If You institute patent litigation against any entity (including a
78
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
79
+Contribution incorporated within the Work constitutes direct or contributory
80
+patent infringement, then any patent licenses granted to You under this License
81
+for that Work shall terminate as of the date such litigation is filed.
82
+
83
+4. Redistribution.
84
+
85
+You may reproduce and distribute copies of the Work or Derivative Works thereof
86
+in any medium, with or without modifications, and in Source or Object form,
87
+provided that You meet the following conditions:
88
+
89
+You must give any other recipients of the Work or Derivative Works a copy of
90
+this License; and
91
+You must cause any modified files to carry prominent notices stating that You
92
+changed the files; and
93
+You must retain, in the Source form of any Derivative Works that You distribute,
94
+all copyright, patent, trademark, and attribution notices from the Source form
95
+of the Work, excluding those notices that do not pertain to any part of the
96
+Derivative Works; and
97
+If the Work includes a "NOTICE" text file as part of its distribution, then any
98
+Derivative Works that You distribute must include a readable copy of the
99
+attribution notices contained within such NOTICE file, excluding those notices
100
+that do not pertain to any part of the Derivative Works, in at least one of the
101
+following places: within a NOTICE text file distributed as part of the
102
+Derivative Works; within the Source form or documentation, if provided along
103
+with the Derivative Works; or, within a display generated by the Derivative
104
+Works, if and wherever such third-party notices normally appear. The contents of
105
+the NOTICE file are for informational purposes only and do not modify the
106
+License. You may add Your own attribution notices within Derivative Works that
107
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
108
+provided that such additional attribution notices cannot be construed as
109
+modifying the License.
110
+You may add Your own copyright statement to Your modifications and may provide
111
+additional or different license terms and conditions for use, reproduction, or
112
+distribution of Your modifications, or for any such Derivative Works as a whole,
113
+provided Your use, reproduction, and distribution of the Work otherwise complies
114
+with the conditions stated in this License.
115
+
116
+5. Submission of Contributions.
117
+
118
+Unless You explicitly state otherwise, any Contribution intentionally submitted
119
+for inclusion in the Work by You to the Licensor shall be under the terms and
120
+conditions of this License, without any additional terms or conditions.
121
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
122
+any separate license agreement you may have executed with Licensor regarding
123
+such Contributions.
124
+
125
+6. Trademarks.
126
+
127
+This License does not grant permission to use the trade names, trademarks,
128
+service marks, or product names of the Licensor, except as required for
129
+reasonable and customary use in describing the origin of the Work and
130
+reproducing the content of the NOTICE file.
131
+
132
+7. Disclaimer of Warranty.
133
+
134
+Unless required by applicable law or agreed to in writing, Licensor provides the
135
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
136
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
137
+including, without limitation, any warranties or conditions of TITLE,
138
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
139
+solely responsible for determining the appropriateness of using or
140
+redistributing the Work and assume any risks associated with Your exercise of
141
+permissions under this License.
142
+
143
+8. Limitation of Liability.
144
+
145
+In no event and under no legal theory, whether in tort (including negligence),
146
+contract, or otherwise, unless required by applicable law (such as deliberate
147
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
148
+liable to You for damages, including any direct, indirect, special, incidental,
149
+or consequential damages of any character arising as a result of this License or
150
+out of the use or inability to use the Work (including but not limited to
151
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
152
+any and all other commercial damages or losses), even if such Contributor has
153
+been advised of the possibility of such damages.
154
+
155
+9. Accepting Warranty or Additional Liability.
156
+
157
+While redistributing the Work or Derivative Works thereof, You may choose to
158
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
159
+other liability obligations and/or rights consistent with this License. However,
160
+in accepting such obligations, You may act only on Your own behalf and on Your
161
+sole responsibility, not on behalf of any other Contributor, and only if You
162
+agree to indemnify, defend, and hold each Contributor harmless for any liability
163
+incurred by, or claims asserted against, such Contributor by reason of your
164
+accepting any such warranty or additional liability.
165
+
166
+END OF TERMS AND CONDITIONS
167
+
168
+APPENDIX: How to apply the Apache License to your work
169
+
170
+To apply the Apache License to your work, attach the following boilerplate
171
+notice, with the fields enclosed by brackets "[]" replaced with your own
172
+identifying information. (Don't include the brackets!) The text should be
173
+enclosed in the appropriate comment syntax for the file format. We also
174
+recommend that a file or class name and description of purpose be included on
175
+the same "printed page" as the copyright notice for easier identification within
176
+third-party archives.
177
+
178
+   Copyright [yyyy] [name of copyright owner]
179
+
180
+   Licensed under the Apache License, Version 2.0 (the "License");
181
+   you may not use this file except in compliance with the License.
182
+   You may obtain a copy of the License at
183
+
184
+     http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+   Unless required by applicable law or agreed to in writing, software
187
+   distributed under the License is distributed on an "AS IS" BASIS,
188
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+   See the License for the specific language governing permissions and
190
+   limitations under the License.
0 191
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+# groupcache
1
+
2
+## Summary
3
+
4
+groupcache is a caching and cache-filling library, intended as a
5
+replacement for memcached in many cases.
6
+
7
+For API docs and examples, see http://godoc.org/github.com/golang/groupcache
8
+
9
+## Comparison to memcached
10
+
11
+### **Like memcached**, groupcache:
12
+
13
+ * shards by key to select which peer is responsible for that key
14
+
15
+### **Unlike memcached**, groupcache:
16
+
17
+ * does not require running a separate set of servers, thus massively
18
+   reducing deployment/configuration pain.  groupcache is a client
19
+   library as well as a server.  It connects to its own peers.
20
+
21
+ * comes with a cache filling mechanism.  Whereas memcached just says
22
+   "Sorry, cache miss", often resulting in a thundering herd of
23
+   database (or whatever) loads from an unbounded number of clients
24
+   (which has resulted in several fun outages), groupcache coordinates
25
+   cache fills such that only one load in one process of an entire
26
+   replicated set of processes populates the cache, then multiplexes
27
+   the loaded value to all callers.
28
+
29
+ * does not support versioned values.  If key "foo" is value "bar",
30
+   key "foo" must always be "bar".  There are neither cache expiration
31
+   times, nor explicit cache evictions.  Thus there is also no CAS,
32
+   nor Increment/Decrement.  This also means that groupcache....
33
+
34
+ * ... supports automatic mirroring of super-hot items to multiple
35
+   processes.  This prevents memcached hot spotting where a machine's
36
+   CPU and/or NIC are overloaded by very popular keys/values.
37
+
38
+ * is currently only available for Go.  It's very unlikely that I
39
+   (bradfitz@) will port the code to any other language.
40
+
41
+## Loading process
42
+
43
+In a nutshell, a groupcache lookup of **Get("foo")** looks like:
44
+
45
+(On machine #5 of a set of N machines running the same code)
46
+
47
+ 1. Is the value of "foo" in local memory because it's super hot?  If so, use it.
48
+
49
+ 2. Is the value of "foo" in local memory because peer #5 (the current
50
+    peer) is the owner of it?  If so, use it.
51
+
52
+ 3. Amongst all the peers in my set of N, am I the owner of the key
53
+    "foo"?  (e.g. does it consistent hash to 5?)  If so, load it.  If
54
+    other callers come in, via the same process or via RPC requests
55
+    from peers, they block waiting for the load to finish and get the
56
+    same answer.  If not, RPC to the peer that's the owner and get
57
+    the answer.  If the RPC fails, just load it locally (still with
58
+    local dup suppression).
59
+
60
+## Users
61
+
62
+groupcache is in production use by dl.google.com (its original user),
63
+parts of Blogger, parts of Google Code, parts of Google Fiber, parts
64
+of Google production monitoring systems, etc.
65
+
66
+## Presentations
67
+
68
+See http://talks.golang.org/2013/oscon-dl.slide
69
+
70
+## Help
71
+
72
+Use the golang-nuts mailing list for any discussion or questions.
0 73
new file mode 100644
... ...
@@ -0,0 +1,133 @@
0
+/*
1
+Copyright 2013 Google Inc.
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 lru implements an LRU cache.
17
+package lru
18
+
19
+import "container/list"
20
+
21
+// Cache is an LRU cache. It is not safe for concurrent access.
22
+type Cache struct {
23
+	// MaxEntries is the maximum number of cache entries before
24
+	// an item is evicted. Zero means no limit.
25
+	MaxEntries int
26
+
27
+	// OnEvicted optionally specifies a callback function to be
28
+	// executed when an entry is purged from the cache.
29
+	OnEvicted func(key Key, value interface{})
30
+
31
+	ll    *list.List
32
+	cache map[interface{}]*list.Element
33
+}
34
+
35
+// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators
36
+type Key interface{}
37
+
38
+type entry struct {
39
+	key   Key
40
+	value interface{}
41
+}
42
+
43
+// New creates a new Cache.
44
+// If maxEntries is zero, the cache has no limit and it's assumed
45
+// that eviction is done by the caller.
46
+func New(maxEntries int) *Cache {
47
+	return &Cache{
48
+		MaxEntries: maxEntries,
49
+		ll:         list.New(),
50
+		cache:      make(map[interface{}]*list.Element),
51
+	}
52
+}
53
+
54
+// Add adds a value to the cache.
55
+func (c *Cache) Add(key Key, value interface{}) {
56
+	if c.cache == nil {
57
+		c.cache = make(map[interface{}]*list.Element)
58
+		c.ll = list.New()
59
+	}
60
+	if ee, ok := c.cache[key]; ok {
61
+		c.ll.MoveToFront(ee)
62
+		ee.Value.(*entry).value = value
63
+		return
64
+	}
65
+	ele := c.ll.PushFront(&entry{key, value})
66
+	c.cache[key] = ele
67
+	if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
68
+		c.RemoveOldest()
69
+	}
70
+}
71
+
72
+// Get looks up a key's value from the cache.
73
+func (c *Cache) Get(key Key) (value interface{}, ok bool) {
74
+	if c.cache == nil {
75
+		return
76
+	}
77
+	if ele, hit := c.cache[key]; hit {
78
+		c.ll.MoveToFront(ele)
79
+		return ele.Value.(*entry).value, true
80
+	}
81
+	return
82
+}
83
+
84
+// Remove removes the provided key from the cache.
85
+func (c *Cache) Remove(key Key) {
86
+	if c.cache == nil {
87
+		return
88
+	}
89
+	if ele, hit := c.cache[key]; hit {
90
+		c.removeElement(ele)
91
+	}
92
+}
93
+
94
+// RemoveOldest removes the oldest item from the cache.
95
+func (c *Cache) RemoveOldest() {
96
+	if c.cache == nil {
97
+		return
98
+	}
99
+	ele := c.ll.Back()
100
+	if ele != nil {
101
+		c.removeElement(ele)
102
+	}
103
+}
104
+
105
+func (c *Cache) removeElement(e *list.Element) {
106
+	c.ll.Remove(e)
107
+	kv := e.Value.(*entry)
108
+	delete(c.cache, kv.key)
109
+	if c.OnEvicted != nil {
110
+		c.OnEvicted(kv.key, kv.value)
111
+	}
112
+}
113
+
114
+// Len returns the number of items in the cache.
115
+func (c *Cache) Len() int {
116
+	if c.cache == nil {
117
+		return 0
118
+	}
119
+	return c.ll.Len()
120
+}
121
+
122
+// Clear purges all stored items from the cache.
123
+func (c *Cache) Clear() {
124
+	if c.OnEvicted != nil {
125
+		for _, e := range c.cache {
126
+			kv := e.Value.(*entry)
127
+			c.OnEvicted(kv.key, kv.value)
128
+		}
129
+	}
130
+	c.ll = nil
131
+	c.cache = nil
132
+}
... ...
@@ -7,7 +7,9 @@
7 7
 
8 8
 OpenCensus Go is a Go implementation of OpenCensus, a toolkit for
9 9
 collecting application performance and behavior monitoring data.
10
-Currently it consists of three major components: tags, stats, and tracing.
10
+Currently it consists of three major components: tags, stats and tracing.
11
+
12
+#### OpenCensus and OpenTracing have merged to form OpenTelemetry, which serves as the next major version of OpenCensus and OpenTracing. OpenTelemetry will offer backwards compatibility with existing OpenCensus integrations, and we will continue to make security patches to existing OpenCensus libraries for two years. Read more about the merger [here](https://medium.com/opentracing/a-roadmap-to-convergence-b074e5815289).
11 13
 
12 14
 ## Installation
13 15
 
... ...
@@ -22,17 +24,42 @@ The use of vendoring or a dependency management tool is recommended.
22 22
 
23 23
 OpenCensus Go libraries require Go 1.8 or later.
24 24
 
25
+## Getting Started
26
+
27
+The easiest way to get started using OpenCensus in your application is to use an existing
28
+integration with your RPC framework:
29
+
30
+* [net/http](https://godoc.org/go.opencensus.io/plugin/ochttp)
31
+* [gRPC](https://godoc.org/go.opencensus.io/plugin/ocgrpc)
32
+* [database/sql](https://godoc.org/github.com/opencensus-integrations/ocsql)
33
+* [Go kit](https://godoc.org/github.com/go-kit/kit/tracing/opencensus)
34
+* [Groupcache](https://godoc.org/github.com/orijtech/groupcache)
35
+* [Caddy webserver](https://godoc.org/github.com/orijtech/caddy)
36
+* [MongoDB](https://godoc.org/github.com/orijtech/mongo-go-driver)
37
+* [Redis gomodule/redigo](https://godoc.org/github.com/orijtech/redigo)
38
+* [Redis goredis/redis](https://godoc.org/github.com/orijtech/redis)
39
+* [Memcache](https://godoc.org/github.com/orijtech/gomemcache)
40
+
41
+If you're using a framework not listed here, you could either implement your own middleware for your
42
+framework or use [custom stats](#stats) and [spans](#spans) directly in your application.
43
+
25 44
 ## Exporters
26 45
 
27
-OpenCensus can export instrumentation data to various backends. 
28
-Currently, OpenCensus supports:
46
+OpenCensus can export instrumentation data to various backends.
47
+OpenCensus has exporter implementations for the following, users
48
+can implement their own exporters by implementing the exporter interfaces
49
+([stats](https://godoc.org/go.opencensus.io/stats/view#Exporter),
50
+[trace](https://godoc.org/go.opencensus.io/trace#Exporter)):
29 51
 
30 52
 * [Prometheus][exporter-prom] for stats
31 53
 * [OpenZipkin][exporter-zipkin] for traces
32
-* Stackdriver [Monitoring][exporter-stackdriver] and [Trace][exporter-stackdriver]
54
+* [Stackdriver][exporter-stackdriver] Monitoring for stats and Trace for traces
33 55
 * [Jaeger][exporter-jaeger] for traces
34 56
 * [AWS X-Ray][exporter-xray] for traces
35
-
57
+* [Datadog][exporter-datadog] for stats and traces
58
+* [Graphite][exporter-graphite] for stats
59
+* [Honeycomb][exporter-honeycomb] for traces
60
+* [New Relic][exporter-newrelic] for stats and traces
36 61
 
37 62
 ## Overview
38 63
 
... ...
@@ -43,13 +70,6 @@ multiple services until there is a response. OpenCensus allows
43 43
 you to instrument your services and collect diagnostics data all
44 44
 through your services end-to-end.
45 45
 
46
-Start with instrumenting HTTP and gRPC clients and servers,
47
-then add additional custom instrumentation if needed.
48
-
49
-* [HTTP guide](https://github.com/census-instrumentation/opencensus-go/tree/master/examples/http)
50
-* [gRPC guide](https://github.com/census-instrumentation/opencensus-go/tree/master/examples/grpc)
51
-
52
-
53 46
 ## Tags
54 47
 
55 48
 Tags represent propagated key-value pairs. They are propagated using `context.Context`
... ...
@@ -57,11 +77,11 @@ in the same process or can be encoded to be transmitted on the wire. Usually, th
57 57
 be handled by an integration plugin, e.g. `ocgrpc.ServerHandler` and `ocgrpc.ClientHandler`
58 58
 for gRPC.
59 59
 
60
-Package tag allows adding or modifying tags in the current context.
60
+Package `tag` allows adding or modifying tags in the current context.
61 61
 
62 62
 [embedmd]:# (internal/readme/tags.go new)
63 63
 ```go
64
-ctx, err = tag.New(ctx,
64
+ctx, err := tag.New(ctx,
65 65
 	tag.Insert(osKey, "macOS-10.12.5"),
66 66
 	tag.Upsert(userIDKey, "cde36753ed"),
67 67
 )
... ...
@@ -106,7 +126,7 @@ Currently three types of aggregations are supported:
106 106
 
107 107
 [embedmd]:# (internal/readme/stats.go aggs)
108 108
 ```go
109
-distAgg := view.Distribution(0, 1<<32, 2<<32, 3<<32)
109
+distAgg := view.Distribution(1<<32, 2<<32, 3<<32)
110 110
 countAgg := view.Count()
111 111
 sumAgg := view.Sum()
112 112
 ```
... ...
@@ -116,26 +136,79 @@ Here we create a view with the DistributionAggregation over our measure.
116 116
 [embedmd]:# (internal/readme/stats.go view)
117 117
 ```go
118 118
 if err := view.Register(&view.View{
119
-	Name:        "my.org/video_size_distribution",
119
+	Name:        "example.com/video_size_distribution",
120 120
 	Description: "distribution of processed video size over time",
121 121
 	Measure:     videoSize,
122
-	Aggregation: view.Distribution(0, 1<<32, 2<<32, 3<<32),
122
+	Aggregation: view.Distribution(1<<32, 2<<32, 3<<32),
123 123
 }); err != nil {
124
-	log.Fatalf("Failed to subscribe to view: %v", err)
124
+	log.Fatalf("Failed to register view: %v", err)
125 125
 }
126 126
 ```
127 127
 
128
-Subscribe begins collecting data for the view. Subscribed views' data will be
128
+Register begins collecting data for the view. Registered views' data will be
129 129
 exported via the registered exporters.
130 130
 
131 131
 ## Traces
132 132
 
133
+A distributed trace tracks the progression of a single user request as
134
+it is handled by the services and processes that make up an application.
135
+Each step is called a span in the trace. Spans include metadata about the step,
136
+including especially the time spent in the step, called the span’s latency.
137
+
138
+Below you see a trace and several spans underneath it.
139
+
140
+![Traces and spans](https://i.imgur.com/7hZwRVj.png)
141
+
142
+### Spans
143
+
144
+Span is the unit step in a trace. Each span has a name, latency, status and
145
+additional metadata.
146
+
147
+Below we are starting a span for a cache read and ending it
148
+when we are done:
149
+
133 150
 [embedmd]:# (internal/readme/trace.go startend)
134 151
 ```go
135
-ctx, span := trace.StartSpan(ctx, "your choice of name")
152
+ctx, span := trace.StartSpan(ctx, "cache.Get")
136 153
 defer span.End()
154
+
155
+// Do work to get from cache.
137 156
 ```
138 157
 
158
+### Propagation
159
+
160
+Spans can have parents or can be root spans if they don't have any parents.
161
+The current span is propagated in-process and across the network to allow associating
162
+new child spans with the parent.
163
+
164
+In the same process, `context.Context` is used to propagate spans.
165
+`trace.StartSpan` creates a new span as a root if the current context
166
+doesn't contain a span. Or, it creates a child of the span that is
167
+already in current context. The returned context can be used to keep
168
+propagating the newly created span in the current context.
169
+
170
+[embedmd]:# (internal/readme/trace.go startend)
171
+```go
172
+ctx, span := trace.StartSpan(ctx, "cache.Get")
173
+defer span.End()
174
+
175
+// Do work to get from cache.
176
+```
177
+
178
+Across the network, OpenCensus provides different propagation
179
+methods for different protocols.
180
+
181
+* gRPC integrations use the OpenCensus' [binary propagation format](https://godoc.org/go.opencensus.io/trace/propagation).
182
+* HTTP integrations use Zipkin's [B3](https://github.com/openzipkin/b3-propagation)
183
+  by default but can be configured to use a custom propagation method by setting another
184
+  [propagation.HTTPFormat](https://godoc.org/go.opencensus.io/trace/propagation#HTTPFormat).
185
+
186
+## Execution Tracer
187
+
188
+With Go 1.11, OpenCensus Go will support integration with the Go execution tracer.
189
+See [Debugging Latency in Go](https://medium.com/observability/debugging-latency-in-go-1-11-9f97a7910d68)
190
+for an example of their mutual use.
191
+
139 192
 ## Profiles
140 193
 
141 194
 OpenCensus tags can be applied as profiler labels
... ...
@@ -167,7 +240,7 @@ Before version 1.0.0, the following deprecation policy will be observed:
167 167
 
168 168
 No backwards-incompatible changes will be made except for the removal of symbols that have
169 169
 been marked as *Deprecated* for at least one minor release (e.g. 0.9.0 to 0.10.0). A release
170
-removing the *Deprecated* functionality will be made no sooner than 28 days after the first 
170
+removing the *Deprecated* functionality will be made no sooner than 28 days after the first
171 171
 release in which the functionality was marked *Deprecated*.
172 172
 
173 173
 [travis-image]: https://travis-ci.org/census-instrumentation/opencensus-go.svg?branch=master
... ...
@@ -183,8 +256,12 @@ release in which the functionality was marked *Deprecated*.
183 183
 [new-ex]: https://godoc.org/go.opencensus.io/tag#example-NewMap
184 184
 [new-replace-ex]: https://godoc.org/go.opencensus.io/tag#example-NewMap--Replace
185 185
 
186
-[exporter-prom]: https://godoc.org/go.opencensus.io/exporter/prometheus
186
+[exporter-prom]: https://godoc.org/contrib.go.opencensus.io/exporter/prometheus
187 187
 [exporter-stackdriver]: https://godoc.org/contrib.go.opencensus.io/exporter/stackdriver
188
-[exporter-zipkin]: https://godoc.org/go.opencensus.io/exporter/zipkin
189
-[exporter-jaeger]: https://godoc.org/go.opencensus.io/exporter/jaeger
190
-[exporter-xray]: https://github.com/census-instrumentation/opencensus-go-exporter-aws
188
+[exporter-zipkin]: https://godoc.org/contrib.go.opencensus.io/exporter/zipkin
189
+[exporter-jaeger]: https://godoc.org/contrib.go.opencensus.io/exporter/jaeger
190
+[exporter-xray]: https://github.com/census-ecosystem/opencensus-go-exporter-aws
191
+[exporter-datadog]: https://github.com/DataDog/opencensus-go-exporter-datadog
192
+[exporter-graphite]: https://github.com/census-ecosystem/opencensus-go-exporter-graphite
193
+[exporter-honeycomb]: https://github.com/honeycombio/opencensus-exporter
194
+[exporter-newrelic]: https://github.com/newrelic/newrelic-opencensus-exporter-go
191 195
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+module go.opencensus.io
1
+
2
+require (
3
+	github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6
4
+	github.com/golang/protobuf v1.3.1
5
+	github.com/google/go-cmp v0.3.0
6
+	github.com/stretchr/testify v1.4.0
7
+	golang.org/x/net v0.0.0-20190620200207-3b0461eec859
8
+	golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd // indirect
9
+	golang.org/x/text v0.3.2 // indirect
10
+	google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb // indirect
11
+	google.golang.org/grpc v1.20.1
12
+)
13
+
14
+go 1.13
... ...
@@ -14,11 +14,16 @@
14 14
 
15 15
 package internal // import "go.opencensus.io/internal"
16 16
 
17
-import "time"
17
+import (
18
+	"fmt"
19
+	"time"
20
+
21
+	opencensus "go.opencensus.io"
22
+)
18 23
 
19 24
 // UserAgent is the user agent to be added to the outgoing
20 25
 // requests from the exporters.
21
-const UserAgent = "opencensus-go [0.11.0]"
26
+var UserAgent = fmt.Sprintf("opencensus-go/%s", opencensus.Version())
22 27
 
23 28
 // MonotonicEndTime returns the end time at present
24 29
 // but offset from start, monotonically.
... ...
@@ -28,5 +33,5 @@ const UserAgent = "opencensus-go [0.11.0]"
28 28
 // end as a monotonic time.
29 29
 // See https://golang.org/pkg/time/#hdr-Monotonic_Clocks
30 30
 func MonotonicEndTime(start time.Time) time.Time {
31
-	return start.Add(time.Now().Sub(start))
31
+	return start.Add(time.Since(start))
32 32
 }
... ...
@@ -17,6 +17,7 @@
17 17
 // used interally by the stats collector.
18 18
 package tagencoding // import "go.opencensus.io/internal/tagencoding"
19 19
 
20
+// Values represent the encoded buffer for the values.
20 21
 type Values struct {
21 22
 	Buffer     []byte
22 23
 	WriteIndex int
... ...
@@ -31,6 +32,7 @@ func (vb *Values) growIfRequired(expected int) {
31 31
 	}
32 32
 }
33 33
 
34
+// WriteValue is the helper method to encode Values from map[Key][]byte.
34 35
 func (vb *Values) WriteValue(v []byte) {
35 36
 	length := len(v) & 0xff
36 37
 	vb.growIfRequired(1 + length)
... ...
@@ -49,7 +51,7 @@ func (vb *Values) WriteValue(v []byte) {
49 49
 	vb.WriteIndex += length
50 50
 }
51 51
 
52
-// ReadValue is the helper method to read the values when decoding valuesBytes to a map[Key][]byte.
52
+// ReadValue is the helper method to decode Values to a map[Key][]byte.
53 53
 func (vb *Values) ReadValue() []byte {
54 54
 	// read length of v
55 55
 	length := int(vb.Buffer[vb.ReadIndex])
... ...
@@ -67,6 +69,7 @@ func (vb *Values) ReadValue() []byte {
67 67
 	return v
68 68
 }
69 69
 
70
+// Bytes returns a reference to already written bytes in the Buffer.
70 71
 func (vb *Values) Bytes() []byte {
71 72
 	return vb.Buffer[:vb.WriteIndex]
72 73
 }
... ...
@@ -22,6 +22,7 @@ import (
22 22
 // TODO(#412): remove this
23 23
 var Trace interface{}
24 24
 
25
+// LocalSpanStoreEnabled true if the local span store is enabled.
25 26
 var LocalSpanStoreEnabled bool
26 27
 
27 28
 // BucketConfiguration stores the number of samples to store for span buckets
28 29
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+// Package metricdata contains the metrics data model.
15
+//
16
+// This is an EXPERIMENTAL package, and may change in arbitrary ways without
17
+// notice.
18
+package metricdata // import "go.opencensus.io/metric/metricdata"
0 19
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package metricdata
15
+
16
+import (
17
+	"time"
18
+)
19
+
20
+// Exemplars keys.
21
+const (
22
+	AttachmentKeySpanContext = "SpanContext"
23
+)
24
+
25
+// Exemplar is an example data point associated with each bucket of a
26
+// distribution type aggregation.
27
+//
28
+// Their purpose is to provide an example of the kind of thing
29
+// (request, RPC, trace span, etc.) that resulted in that measurement.
30
+type Exemplar struct {
31
+	Value       float64     // the value that was recorded
32
+	Timestamp   time.Time   // the time the value was recorded
33
+	Attachments Attachments // attachments (if any)
34
+}
35
+
36
+// Attachments is a map of extra values associated with a recorded data point.
37
+type Attachments map[string]interface{}
0 38
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package metricdata
15
+
16
+// LabelKey represents key of a label. It has optional
17
+// description attribute.
18
+type LabelKey struct {
19
+	Key         string
20
+	Description string
21
+}
22
+
23
+// LabelValue represents the value of a label.
24
+// The zero value represents a missing label value, which may be treated
25
+// differently to an empty string value by some back ends.
26
+type LabelValue struct {
27
+	Value   string // string value of the label
28
+	Present bool   // flag that indicated whether a value is present or not
29
+}
30
+
31
+// NewLabelValue creates a new non-nil LabelValue that represents the given string.
32
+func NewLabelValue(val string) LabelValue {
33
+	return LabelValue{Value: val, Present: true}
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package metricdata
15
+
16
+import (
17
+	"time"
18
+
19
+	"go.opencensus.io/resource"
20
+)
21
+
22
+// Descriptor holds metadata about a metric.
23
+type Descriptor struct {
24
+	Name        string     // full name of the metric
25
+	Description string     // human-readable description
26
+	Unit        Unit       // units for the measure
27
+	Type        Type       // type of measure
28
+	LabelKeys   []LabelKey // label keys
29
+}
30
+
31
+// Metric represents a quantity measured against a resource with different
32
+// label value combinations.
33
+type Metric struct {
34
+	Descriptor Descriptor         // metric descriptor
35
+	Resource   *resource.Resource // resource against which this was measured
36
+	TimeSeries []*TimeSeries      // one time series for each combination of label values
37
+}
38
+
39
+// TimeSeries is a sequence of points associated with a combination of label
40
+// values.
41
+type TimeSeries struct {
42
+	LabelValues []LabelValue // label values, same order as keys in the metric descriptor
43
+	Points      []Point      // points sequence
44
+	StartTime   time.Time    // time we started recording this time series
45
+}
0 46
new file mode 100644
... ...
@@ -0,0 +1,193 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package metricdata
15
+
16
+import (
17
+	"time"
18
+)
19
+
20
+// Point is a single data point of a time series.
21
+type Point struct {
22
+	// Time is the point in time that this point represents in a time series.
23
+	Time time.Time
24
+	// Value is the value of this point. Prefer using ReadValue to switching on
25
+	// the value type, since new value types might be added.
26
+	Value interface{}
27
+}
28
+
29
+//go:generate stringer -type ValueType
30
+
31
+// NewFloat64Point creates a new Point holding a float64 value.
32
+func NewFloat64Point(t time.Time, val float64) Point {
33
+	return Point{
34
+		Value: val,
35
+		Time:  t,
36
+	}
37
+}
38
+
39
+// NewInt64Point creates a new Point holding an int64 value.
40
+func NewInt64Point(t time.Time, val int64) Point {
41
+	return Point{
42
+		Value: val,
43
+		Time:  t,
44
+	}
45
+}
46
+
47
+// NewDistributionPoint creates a new Point holding a Distribution value.
48
+func NewDistributionPoint(t time.Time, val *Distribution) Point {
49
+	return Point{
50
+		Value: val,
51
+		Time:  t,
52
+	}
53
+}
54
+
55
+// NewSummaryPoint creates a new Point holding a Summary value.
56
+func NewSummaryPoint(t time.Time, val *Summary) Point {
57
+	return Point{
58
+		Value: val,
59
+		Time:  t,
60
+	}
61
+}
62
+
63
+// ValueVisitor allows reading the value of a point.
64
+type ValueVisitor interface {
65
+	VisitFloat64Value(float64)
66
+	VisitInt64Value(int64)
67
+	VisitDistributionValue(*Distribution)
68
+	VisitSummaryValue(*Summary)
69
+}
70
+
71
+// ReadValue accepts a ValueVisitor and calls the appropriate method with the
72
+// value of this point.
73
+// Consumers of Point should use this in preference to switching on the type
74
+// of the value directly, since new value types may be added.
75
+func (p Point) ReadValue(vv ValueVisitor) {
76
+	switch v := p.Value.(type) {
77
+	case int64:
78
+		vv.VisitInt64Value(v)
79
+	case float64:
80
+		vv.VisitFloat64Value(v)
81
+	case *Distribution:
82
+		vv.VisitDistributionValue(v)
83
+	case *Summary:
84
+		vv.VisitSummaryValue(v)
85
+	default:
86
+		panic("unexpected value type")
87
+	}
88
+}
89
+
90
+// Distribution contains summary statistics for a population of values. It
91
+// optionally contains a histogram representing the distribution of those
92
+// values across a set of buckets.
93
+type Distribution struct {
94
+	// Count is the number of values in the population. Must be non-negative. This value
95
+	// must equal the sum of the values in bucket_counts if a histogram is
96
+	// provided.
97
+	Count int64
98
+	// Sum is the sum of the values in the population. If count is zero then this field
99
+	// must be zero.
100
+	Sum float64
101
+	// SumOfSquaredDeviation is the sum of squared deviations from the mean of the values in the
102
+	// population. For values x_i this is:
103
+	//
104
+	//     Sum[i=1..n]((x_i - mean)^2)
105
+	//
106
+	// Knuth, "The Art of Computer Programming", Vol. 2, page 323, 3rd edition
107
+	// describes Welford's method for accumulating this sum in one pass.
108
+	//
109
+	// If count is zero then this field must be zero.
110
+	SumOfSquaredDeviation float64
111
+	// BucketOptions describes the bounds of the histogram buckets in this
112
+	// distribution.
113
+	//
114
+	// A Distribution may optionally contain a histogram of the values in the
115
+	// population.
116
+	//
117
+	// If nil, there is no associated histogram.
118
+	BucketOptions *BucketOptions
119
+	// Bucket If the distribution does not have a histogram, then omit this field.
120
+	// If there is a histogram, then the sum of the values in the Bucket counts
121
+	// must equal the value in the count field of the distribution.
122
+	Buckets []Bucket
123
+}
124
+
125
+// BucketOptions describes the bounds of the histogram buckets in this
126
+// distribution.
127
+type BucketOptions struct {
128
+	// Bounds specifies a set of bucket upper bounds.
129
+	// This defines len(bounds) + 1 (= N) buckets. The boundaries for bucket
130
+	// index i are:
131
+	//
132
+	// [0, Bounds[i]) for i == 0
133
+	// [Bounds[i-1], Bounds[i]) for 0 < i < N-1
134
+	// [Bounds[i-1], +infinity) for i == N-1
135
+	Bounds []float64
136
+}
137
+
138
+// Bucket represents a single bucket (value range) in a distribution.
139
+type Bucket struct {
140
+	// Count is the number of values in each bucket of the histogram, as described in
141
+	// bucket_bounds.
142
+	Count int64
143
+	// Exemplar associated with this bucket (if any).
144
+	Exemplar *Exemplar
145
+}
146
+
147
+// Summary is a representation of percentiles.
148
+type Summary struct {
149
+	// Count is the cumulative count (if available).
150
+	Count int64
151
+	// Sum is the cumulative sum of values  (if available).
152
+	Sum float64
153
+	// HasCountAndSum is true if Count and Sum are available.
154
+	HasCountAndSum bool
155
+	// Snapshot represents percentiles calculated over an arbitrary time window.
156
+	// The values in this struct can be reset at arbitrary unknown times, with
157
+	// the requirement that all of them are reset at the same time.
158
+	Snapshot Snapshot
159
+}
160
+
161
+// Snapshot represents percentiles over an arbitrary time.
162
+// The values in this struct can be reset at arbitrary unknown times, with
163
+// the requirement that all of them are reset at the same time.
164
+type Snapshot struct {
165
+	// Count is the number of values in the snapshot. Optional since some systems don't
166
+	// expose this. Set to 0 if not available.
167
+	Count int64
168
+	// Sum is the sum of values in the snapshot. Optional since some systems don't
169
+	// expose this. If count is 0 then this field must be zero.
170
+	Sum float64
171
+	// Percentiles is a map from percentile (range (0-100.0]) to the value of
172
+	// the percentile.
173
+	Percentiles map[float64]float64
174
+}
175
+
176
+//go:generate stringer -type Type
177
+
178
+// Type is the overall type of metric, including its value type and whether it
179
+// represents a cumulative total (since the start time) or if it represents a
180
+// gauge value.
181
+type Type int
182
+
183
+// Metric types.
184
+const (
185
+	TypeGaugeInt64 Type = iota
186
+	TypeGaugeFloat64
187
+	TypeGaugeDistribution
188
+	TypeCumulativeInt64
189
+	TypeCumulativeFloat64
190
+	TypeCumulativeDistribution
191
+	TypeSummary
192
+)
0 193
new file mode 100644
... ...
@@ -0,0 +1,16 @@
0
+// Code generated by "stringer -type Type"; DO NOT EDIT.
1
+
2
+package metricdata
3
+
4
+import "strconv"
5
+
6
+const _Type_name = "TypeGaugeInt64TypeGaugeFloat64TypeGaugeDistributionTypeCumulativeInt64TypeCumulativeFloat64TypeCumulativeDistributionTypeSummary"
7
+
8
+var _Type_index = [...]uint8{0, 14, 30, 51, 70, 91, 117, 128}
9
+
10
+func (i Type) String() string {
11
+	if i < 0 || i >= Type(len(_Type_index)-1) {
12
+		return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
13
+	}
14
+	return _Type_name[_Type_index[i]:_Type_index[i+1]]
15
+}
0 16
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package metricdata
15
+
16
+// Unit is a string encoded according to the case-sensitive abbreviations from the
17
+// Unified Code for Units of Measure: http://unitsofmeasure.org/ucum.html
18
+type Unit string
19
+
20
+// Predefined units. To record against a unit not represented here, create your
21
+// own Unit type constant from a string.
22
+const (
23
+	UnitDimensionless Unit = "1"
24
+	UnitBytes         Unit = "By"
25
+	UnitMilliseconds  Unit = "ms"
26
+)
0 27
new file mode 100644
... ...
@@ -0,0 +1,78 @@
0
+// Copyright 2019, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package metricproducer
15
+
16
+import (
17
+	"sync"
18
+)
19
+
20
+// Manager maintains a list of active producers. Producers can register
21
+// with the manager to allow readers to read all metrics provided by them.
22
+// Readers can retrieve all producers registered with the manager,
23
+// read metrics from the producers and export them.
24
+type Manager struct {
25
+	mu        sync.RWMutex
26
+	producers map[Producer]struct{}
27
+}
28
+
29
+var prodMgr *Manager
30
+var once sync.Once
31
+
32
+// GlobalManager is a single instance of producer manager
33
+// that is used by all producers and all readers.
34
+func GlobalManager() *Manager {
35
+	once.Do(func() {
36
+		prodMgr = &Manager{}
37
+		prodMgr.producers = make(map[Producer]struct{})
38
+	})
39
+	return prodMgr
40
+}
41
+
42
+// AddProducer adds the producer to the Manager if it is not already present.
43
+func (pm *Manager) AddProducer(producer Producer) {
44
+	if producer == nil {
45
+		return
46
+	}
47
+	pm.mu.Lock()
48
+	defer pm.mu.Unlock()
49
+	pm.producers[producer] = struct{}{}
50
+}
51
+
52
+// DeleteProducer deletes the producer from the Manager if it is present.
53
+func (pm *Manager) DeleteProducer(producer Producer) {
54
+	if producer == nil {
55
+		return
56
+	}
57
+	pm.mu.Lock()
58
+	defer pm.mu.Unlock()
59
+	delete(pm.producers, producer)
60
+}
61
+
62
+// GetAll returns a slice of all producer currently registered with
63
+// the Manager. For each call it generates a new slice. The slice
64
+// should not be cached as registration may change at any time. It is
65
+// typically called periodically by exporter to read metrics from
66
+// the producers.
67
+func (pm *Manager) GetAll() []Producer {
68
+	pm.mu.Lock()
69
+	defer pm.mu.Unlock()
70
+	producers := make([]Producer, len(pm.producers))
71
+	i := 0
72
+	for producer := range pm.producers {
73
+		producers[i] = producer
74
+		i++
75
+	}
76
+	return producers
77
+}
0 78
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+// Copyright 2019, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package metricproducer
15
+
16
+import (
17
+	"go.opencensus.io/metric/metricdata"
18
+)
19
+
20
+// Producer is a source of metrics.
21
+type Producer interface {
22
+	// Read should return the current values of all metrics supported by this
23
+	// metric provider.
24
+	// The returned metrics should be unique for each combination of name and
25
+	// resource.
26
+	Read() []*metricdata.Metric
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+// Copyright 2017, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+// Package opencensus contains Go support for OpenCensus.
15
+package opencensus // import "go.opencensus.io"
16
+
17
+// Version is the current release version of OpenCensus in use.
18
+func Version() string {
19
+	return "0.23.0"
20
+}
... ...
@@ -15,8 +15,8 @@
15 15
 package ocgrpc
16 16
 
17 17
 import (
18
+	"context"
18 19
 	"go.opencensus.io/trace"
19
-	"golang.org/x/net/context"
20 20
 
21 21
 	"google.golang.org/grpc/stats"
22 22
 )
... ...
@@ -31,6 +31,7 @@ type ClientHandler struct {
31 31
 	StartOptions trace.StartOptions
32 32
 }
33 33
 
34
+// HandleConn exists to satisfy gRPC stats.Handler.
34 35
 func (c *ClientHandler) HandleConn(ctx context.Context, cs stats.ConnStats) {
35 36
 	// no-op
36 37
 }
... ...
@@ -31,9 +31,9 @@ var (
31 31
 	ClientServerLatency          = stats.Float64("grpc.io/client/server_latency", `Propagated from the server and should have the same value as "grpc.io/server/latency".`, stats.UnitMilliseconds)
32 32
 )
33 33
 
34
-// Predefined views may be subscribed to collect data for the above measures.
34
+// Predefined views may be registered to collect data for the above measures.
35 35
 // As always, you may also define your own custom views over measures collected by this
36
-// package. These are declared as a convenience only; none are subscribed by
36
+// package. These are declared as a convenience only; none are registered by
37 37
 // default.
38 38
 var (
39 39
 	ClientSentBytesPerRPCView = &view.View{
... ...
@@ -91,15 +91,6 @@ var (
91 91
 		TagKeys:     []tag.Key{KeyClientMethod},
92 92
 		Aggregation: DefaultMillisecondsDistribution,
93 93
 	}
94
-
95
-	// Deprecated: This view is going to be removed, if you need it please define it
96
-	// yourself.
97
-	ClientRequestCountView = &view.View{
98
-		Name:        "Count of request messages per client RPC",
99
-		TagKeys:     []tag.Key{KeyClientMethod},
100
-		Measure:     ClientRoundtripLatency,
101
-		Aggregation: view.Count(),
102
-	}
103 94
 )
104 95
 
105 96
 // DefaultClientViews are the default client views provided by this package.
... ...
@@ -16,10 +16,10 @@
16 16
 package ocgrpc
17 17
 
18 18
 import (
19
+	"context"
19 20
 	"time"
20 21
 
21 22
 	"go.opencensus.io/tag"
22
-	"golang.org/x/net/context"
23 23
 	"google.golang.org/grpc/grpclog"
24 24
 	"google.golang.org/grpc/stats"
25 25
 )
... ...
@@ -30,7 +30,7 @@ func (h *ClientHandler) statsTagRPC(ctx context.Context, info *stats.RPCTagInfo)
30 30
 	startTime := time.Now()
31 31
 	if info == nil {
32 32
 		if grpclog.V(2) {
33
-			grpclog.Infof("clientHandler.TagRPC called with nil info.", info.FullMethodName)
33
+			grpclog.Info("clientHandler.TagRPC called with nil info.")
34 34
 		}
35 35
 		return ctx
36 36
 	}
... ...
@@ -15,8 +15,8 @@
15 15
 package ocgrpc
16 16
 
17 17
 import (
18
+	"context"
18 19
 	"go.opencensus.io/trace"
19
-	"golang.org/x/net/context"
20 20
 
21 21
 	"google.golang.org/grpc/stats"
22 22
 )
... ...
@@ -34,9 +34,9 @@ var (
34 34
 // mechanism to load these defaults from a common repository/config shared by
35 35
 // all supported languages. Likely a serialized protobuf of these defaults.
36 36
 
37
-// Predefined views may be subscribed to collect data for the above measures.
37
+// Predefined views may be registered to collect data for the above measures.
38 38
 // As always, you may also define your own custom views over measures collected by this
39
-// package. These are declared as a convenience only; none are subscribed by
39
+// package. These are declared as a convenience only; none are registered by
40 40
 // default.
41 41
 var (
42 42
 	ServerReceivedBytesPerRPCView = &view.View{
... ...
@@ -18,7 +18,7 @@ package ocgrpc
18 18
 import (
19 19
 	"time"
20 20
 
21
-	"golang.org/x/net/context"
21
+	"context"
22 22
 
23 23
 	"go.opencensus.io/tag"
24 24
 	"google.golang.org/grpc/grpclog"
... ...
@@ -22,9 +22,11 @@ import (
22 22
 	"sync/atomic"
23 23
 	"time"
24 24
 
25
+	"go.opencensus.io/metric/metricdata"
25 26
 	ocstats "go.opencensus.io/stats"
26 27
 	"go.opencensus.io/stats/view"
27 28
 	"go.opencensus.io/tag"
29
+	"go.opencensus.io/trace"
28 30
 	"google.golang.org/grpc/codes"
29 31
 	"google.golang.org/grpc/grpclog"
30 32
 	"google.golang.org/grpc/stats"
... ...
@@ -51,16 +53,22 @@ type rpcData struct {
51 51
 // The following variables define the default hard-coded auxiliary data used by
52 52
 // both the default GRPC client and GRPC server metrics.
53 53
 var (
54
-	DefaultBytesDistribution        = view.Distribution(0, 1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296)
55
-	DefaultMillisecondsDistribution = view.Distribution(0, 0.01, 0.05, 0.1, 0.3, 0.6, 0.8, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
56
-	DefaultMessageCountDistribution = view.Distribution(0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536)
54
+	DefaultBytesDistribution        = view.Distribution(1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296)
55
+	DefaultMillisecondsDistribution = view.Distribution(0.01, 0.05, 0.1, 0.3, 0.6, 0.8, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
56
+	DefaultMessageCountDistribution = view.Distribution(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536)
57 57
 )
58 58
 
59
+// Server tags are applied to the context used to process each RPC, as well as
60
+// the measures at the end of each RPC.
59 61
 var (
60
-	KeyServerMethod, _ = tag.NewKey("grpc_server_method")
61
-	KeyClientMethod, _ = tag.NewKey("grpc_client_method")
62
-	KeyServerStatus, _ = tag.NewKey("grpc_server_status")
63
-	KeyClientStatus, _ = tag.NewKey("grpc_client_status")
62
+	KeyServerMethod = tag.MustNewKey("grpc_server_method")
63
+	KeyServerStatus = tag.MustNewKey("grpc_server_status")
64
+)
65
+
66
+// Client tags are applied to measures at the end of each RPC.
67
+var (
68
+	KeyClientMethod = tag.MustNewKey("grpc_client_method")
69
+	KeyClientStatus = tag.MustNewKey("grpc_client_status")
64 70
 )
65 71
 
66 72
 var (
... ...
@@ -135,24 +143,31 @@ func handleRPCEnd(ctx context.Context, s *stats.End) {
135 135
 	}
136 136
 
137 137
 	latencyMillis := float64(elapsedTime) / float64(time.Millisecond)
138
+	attachments := getSpanCtxAttachment(ctx)
138 139
 	if s.Client {
139
-		ctx, _ = tag.New(ctx,
140
-			tag.Upsert(KeyClientMethod, methodName(d.method)),
141
-			tag.Upsert(KeyClientStatus, st))
142
-		ocstats.Record(ctx,
143
-			ClientSentBytesPerRPC.M(atomic.LoadInt64(&d.sentBytes)),
144
-			ClientSentMessagesPerRPC.M(atomic.LoadInt64(&d.sentCount)),
145
-			ClientReceivedMessagesPerRPC.M(atomic.LoadInt64(&d.recvCount)),
146
-			ClientReceivedBytesPerRPC.M(atomic.LoadInt64(&d.recvBytes)),
147
-			ClientRoundtripLatency.M(latencyMillis))
140
+		ocstats.RecordWithOptions(ctx,
141
+			ocstats.WithTags(
142
+				tag.Upsert(KeyClientMethod, methodName(d.method)),
143
+				tag.Upsert(KeyClientStatus, st)),
144
+			ocstats.WithAttachments(attachments),
145
+			ocstats.WithMeasurements(
146
+				ClientSentBytesPerRPC.M(atomic.LoadInt64(&d.sentBytes)),
147
+				ClientSentMessagesPerRPC.M(atomic.LoadInt64(&d.sentCount)),
148
+				ClientReceivedMessagesPerRPC.M(atomic.LoadInt64(&d.recvCount)),
149
+				ClientReceivedBytesPerRPC.M(atomic.LoadInt64(&d.recvBytes)),
150
+				ClientRoundtripLatency.M(latencyMillis)))
148 151
 	} else {
149
-		ctx, _ = tag.New(ctx, tag.Upsert(KeyServerStatus, st))
150
-		ocstats.Record(ctx,
151
-			ServerSentBytesPerRPC.M(atomic.LoadInt64(&d.sentBytes)),
152
-			ServerSentMessagesPerRPC.M(atomic.LoadInt64(&d.sentCount)),
153
-			ServerReceivedMessagesPerRPC.M(atomic.LoadInt64(&d.recvCount)),
154
-			ServerReceivedBytesPerRPC.M(atomic.LoadInt64(&d.recvBytes)),
155
-			ServerLatency.M(latencyMillis))
152
+		ocstats.RecordWithOptions(ctx,
153
+			ocstats.WithTags(
154
+				tag.Upsert(KeyServerStatus, st),
155
+			),
156
+			ocstats.WithAttachments(attachments),
157
+			ocstats.WithMeasurements(
158
+				ServerSentBytesPerRPC.M(atomic.LoadInt64(&d.sentBytes)),
159
+				ServerSentMessagesPerRPC.M(atomic.LoadInt64(&d.sentCount)),
160
+				ServerReceivedMessagesPerRPC.M(atomic.LoadInt64(&d.recvCount)),
161
+				ServerReceivedBytesPerRPC.M(atomic.LoadInt64(&d.recvBytes)),
162
+				ServerLatency.M(latencyMillis)))
156 163
 	}
157 164
 }
158 165
 
... ...
@@ -197,3 +212,16 @@ func statusCodeToString(s *status.Status) string {
197 197
 		return "CODE_" + strconv.FormatInt(int64(c), 10)
198 198
 	}
199 199
 }
200
+
201
+func getSpanCtxAttachment(ctx context.Context) metricdata.Attachments {
202
+	attachments := map[string]interface{}{}
203
+	span := trace.FromContext(ctx)
204
+	if span == nil {
205
+		return attachments
206
+	}
207
+	spanCtx := span.SpanContext()
208
+	if spanCtx.IsSampled() {
209
+		attachments[metricdata.AttachmentKeySpanContext] = spanCtx
210
+	}
211
+	return attachments
212
+}
... ...
@@ -19,9 +19,9 @@ import (
19 19
 
20 20
 	"google.golang.org/grpc/codes"
21 21
 
22
+	"context"
22 23
 	"go.opencensus.io/trace"
23 24
 	"go.opencensus.io/trace/propagation"
24
-	"golang.org/x/net/context"
25 25
 	"google.golang.org/grpc/metadata"
26 26
 	"google.golang.org/grpc/stats"
27 27
 	"google.golang.org/grpc/status"
... ...
@@ -16,14 +16,18 @@ package ochttp
16 16
 
17 17
 import (
18 18
 	"net/http"
19
+	"net/http/httptrace"
19 20
 
20 21
 	"go.opencensus.io/trace"
21 22
 	"go.opencensus.io/trace/propagation"
22 23
 )
23 24
 
24 25
 // Transport is an http.RoundTripper that instruments all outgoing requests with
25
-// stats and tracing. The zero value is intended to be a useful default, but for
26
-// now it's recommended that you explicitly set Propagation.
26
+// OpenCensus stats and tracing.
27
+//
28
+// The zero value is intended to be a useful default, but for
29
+// now it's recommended that you explicitly set Propagation, since the default
30
+// for this may change.
27 31
 type Transport struct {
28 32
 	// Base may be set to wrap another http.RoundTripper that does the actual
29 33
 	// requests. By default http.DefaultTransport is used.
... ...
@@ -43,24 +47,53 @@ type Transport struct {
43 43
 	// for spans started by this transport.
44 44
 	StartOptions trace.StartOptions
45 45
 
46
+	// GetStartOptions allows to set start options per request. If set,
47
+	// StartOptions is going to be ignored.
48
+	GetStartOptions func(*http.Request) trace.StartOptions
49
+
50
+	// NameFromRequest holds the function to use for generating the span name
51
+	// from the information found in the outgoing HTTP Request. By default the
52
+	// name equals the URL Path.
53
+	FormatSpanName func(*http.Request) string
54
+
55
+	// NewClientTrace may be set to a function allowing the current *trace.Span
56
+	// to be annotated with HTTP request event information emitted by the
57
+	// httptrace package.
58
+	NewClientTrace func(*http.Request, *trace.Span) *httptrace.ClientTrace
59
+
46 60
 	// TODO: Implement tag propagation for HTTP.
47 61
 }
48 62
 
49 63
 // RoundTrip implements http.RoundTripper, delegating to Base and recording stats and traces for the request.
50 64
 func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
51 65
 	rt := t.base()
66
+	if isHealthEndpoint(req.URL.Path) {
67
+		return rt.RoundTrip(req)
68
+	}
52 69
 	// TODO: remove excessive nesting of http.RoundTrippers here.
53 70
 	format := t.Propagation
54 71
 	if format == nil {
55 72
 		format = defaultFormat
56 73
 	}
74
+	spanNameFormatter := t.FormatSpanName
75
+	if spanNameFormatter == nil {
76
+		spanNameFormatter = spanNameFromURL
77
+	}
78
+
79
+	startOpts := t.StartOptions
80
+	if t.GetStartOptions != nil {
81
+		startOpts = t.GetStartOptions(req)
82
+	}
83
+
57 84
 	rt = &traceTransport{
58 85
 		base:   rt,
59 86
 		format: format,
60 87
 		startOptions: trace.StartOptions{
61
-			Sampler:  t.StartOptions.Sampler,
88
+			Sampler:  startOpts.Sampler,
62 89
 			SpanKind: trace.SpanKindClient,
63 90
 		},
91
+		formatSpanName: spanNameFormatter,
92
+		newClientTrace: t.NewClientTrace,
64 93
 	}
65 94
 	rt = statsTransport{base: rt}
66 95
 	return rt.RoundTrip(req)
... ...
@@ -34,8 +34,11 @@ type statsTransport struct {
34 34
 // RoundTrip implements http.RoundTripper, delegating to Base and recording stats for the request.
35 35
 func (t statsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
36 36
 	ctx, _ := tag.New(req.Context(),
37
-		tag.Upsert(Host, req.URL.Host),
37
+		tag.Upsert(KeyClientHost, req.Host),
38
+		tag.Upsert(Host, req.Host),
39
+		tag.Upsert(KeyClientPath, req.URL.Path),
38 40
 		tag.Upsert(Path, req.URL.Path),
41
+		tag.Upsert(KeyClientMethod, req.Method),
39 42
 		tag.Upsert(Method, req.Method))
40 43
 	req = req.WithContext(ctx)
41 44
 	track := &tracker{
... ...
@@ -58,11 +61,14 @@ func (t statsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
58 58
 		track.end()
59 59
 	} else {
60 60
 		track.statusCode = resp.StatusCode
61
+		if req.Method != "HEAD" {
62
+			track.respContentLength = resp.ContentLength
63
+		}
61 64
 		if resp.Body == nil {
62 65
 			track.end()
63 66
 		} else {
64 67
 			track.body = resp.Body
65
-			resp.Body = track
68
+			resp.Body = wrappedBody(track, resp.Body)
66 69
 		}
67 70
 	}
68 71
 	return resp, err
... ...
@@ -79,36 +85,48 @@ func (t statsTransport) CancelRequest(req *http.Request) {
79 79
 }
80 80
 
81 81
 type tracker struct {
82
-	ctx        context.Context
83
-	respSize   int64
84
-	reqSize    int64
85
-	start      time.Time
86
-	body       io.ReadCloser
87
-	statusCode int
88
-	endOnce    sync.Once
82
+	ctx               context.Context
83
+	respSize          int64
84
+	respContentLength int64
85
+	reqSize           int64
86
+	start             time.Time
87
+	body              io.ReadCloser
88
+	statusCode        int
89
+	endOnce           sync.Once
89 90
 }
90 91
 
91 92
 var _ io.ReadCloser = (*tracker)(nil)
92 93
 
93 94
 func (t *tracker) end() {
94 95
 	t.endOnce.Do(func() {
96
+		latencyMs := float64(time.Since(t.start)) / float64(time.Millisecond)
97
+		respSize := t.respSize
98
+		if t.respSize == 0 && t.respContentLength > 0 {
99
+			respSize = t.respContentLength
100
+		}
95 101
 		m := []stats.Measurement{
96
-			ClientLatency.M(float64(time.Since(t.start)) / float64(time.Millisecond)),
102
+			ClientSentBytes.M(t.reqSize),
103
+			ClientReceivedBytes.M(respSize),
104
+			ClientRoundtripLatency.M(latencyMs),
105
+			ClientLatency.M(latencyMs),
97 106
 			ClientResponseBytes.M(t.respSize),
98 107
 		}
99 108
 		if t.reqSize >= 0 {
100 109
 			m = append(m, ClientRequestBytes.M(t.reqSize))
101 110
 		}
102
-		ctx, _ := tag.New(t.ctx, tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)))
103
-		stats.Record(ctx, m...)
111
+
112
+		stats.RecordWithTags(t.ctx, []tag.Mutator{
113
+			tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)),
114
+			tag.Upsert(KeyClientStatus, strconv.Itoa(t.statusCode)),
115
+		}, m...)
104 116
 	})
105 117
 }
106 118
 
107 119
 func (t *tracker) Read(b []byte) (int, error) {
108 120
 	n, err := t.body.Read(b)
121
+	t.respSize += int64(n)
109 122
 	switch err {
110 123
 	case nil:
111
-		t.respSize += int64(n)
112 124
 		return n, nil
113 125
 	case io.EOF:
114 126
 		t.end()
... ...
@@ -38,7 +38,7 @@ const (
38 38
 // because there are additional fields not represented in the
39 39
 // OpenCensus span context. Spans created from the incoming
40 40
 // header will be the direct children of the client-side span.
41
-// Similarly, reciever of the outgoing spans should use client-side
41
+// Similarly, receiver of the outgoing spans should use client-side
42 42
 // span created by OpenCensus as the parent.
43 43
 type HTTPFormat struct{}
44 44
 
45 45
new file mode 100644
... ...
@@ -0,0 +1,61 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package ochttp
15
+
16
+import (
17
+	"context"
18
+	"net/http"
19
+
20
+	"go.opencensus.io/tag"
21
+)
22
+
23
+// SetRoute sets the http_server_route tag to the given value.
24
+// It's useful when an HTTP framework does not support the http.Handler interface
25
+// and using WithRouteTag is not an option, but provides a way to hook into the request flow.
26
+func SetRoute(ctx context.Context, route string) {
27
+	if a, ok := ctx.Value(addedTagsKey{}).(*addedTags); ok {
28
+		a.t = append(a.t, tag.Upsert(KeyServerRoute, route))
29
+	}
30
+}
31
+
32
+// WithRouteTag returns an http.Handler that records stats with the
33
+// http_server_route tag set to the given value.
34
+func WithRouteTag(handler http.Handler, route string) http.Handler {
35
+	return taggedHandlerFunc(func(w http.ResponseWriter, r *http.Request) []tag.Mutator {
36
+		addRoute := []tag.Mutator{tag.Upsert(KeyServerRoute, route)}
37
+		ctx, _ := tag.New(r.Context(), addRoute...)
38
+		r = r.WithContext(ctx)
39
+		handler.ServeHTTP(w, r)
40
+		return addRoute
41
+	})
42
+}
43
+
44
+// taggedHandlerFunc is a http.Handler that returns tags describing the
45
+// processing of the request. These tags will be recorded along with the
46
+// measures in this package at the end of the request.
47
+type taggedHandlerFunc func(w http.ResponseWriter, r *http.Request) []tag.Mutator
48
+
49
+func (h taggedHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
50
+	tags := h(w, r)
51
+	if a, ok := r.Context().Value(addedTagsKey{}).(*addedTags); ok {
52
+		a.t = append(a.t, tags...)
53
+	}
54
+}
55
+
56
+type addedTagsKey struct{}
57
+
58
+type addedTags struct {
59
+	t []tag.Mutator
60
+}
... ...
@@ -15,10 +15,8 @@
15 15
 package ochttp
16 16
 
17 17
 import (
18
-	"bufio"
19 18
 	"context"
20
-	"errors"
21
-	"net"
19
+	"io"
22 20
 	"net/http"
23 21
 	"strconv"
24 22
 	"sync"
... ...
@@ -30,16 +28,19 @@ import (
30 30
 	"go.opencensus.io/trace/propagation"
31 31
 )
32 32
 
33
-// Handler is a http.Handler that is aware of the incoming request's span.
33
+// Handler is an http.Handler wrapper to instrument your HTTP server with
34
+// OpenCensus. It supports both stats and tracing.
34 35
 //
36
+// Tracing
37
+//
38
+// This handler is aware of the incoming request's span, reading it from request
39
+// headers as configured using the Propagation field.
35 40
 // The extracted span can be accessed from the incoming request's
36 41
 // context.
37 42
 //
38 43
 //    span := trace.FromContext(r.Context())
39 44
 //
40 45
 // The server span will be automatically ended at the end of ServeHTTP.
41
-//
42
-// Incoming propagation mechanism is determined by the given HTTP propagators.
43 46
 type Handler struct {
44 47
 	// Propagation defines how traces are propagated. If unspecified,
45 48
 	// B3 propagation will be used.
... ...
@@ -55,50 +56,86 @@ type Handler struct {
55 55
 	// for spans started by this transport.
56 56
 	StartOptions trace.StartOptions
57 57
 
58
+	// GetStartOptions allows to set start options per request. If set,
59
+	// StartOptions is going to be ignored.
60
+	GetStartOptions func(*http.Request) trace.StartOptions
61
+
58 62
 	// IsPublicEndpoint should be set to true for publicly accessible HTTP(S)
59 63
 	// servers. If true, any trace metadata set on the incoming request will
60 64
 	// be added as a linked trace instead of being added as a parent of the
61 65
 	// current trace.
62 66
 	IsPublicEndpoint bool
67
+
68
+	// FormatSpanName holds the function to use for generating the span name
69
+	// from the information found in the incoming HTTP Request. By default the
70
+	// name equals the URL Path.
71
+	FormatSpanName func(*http.Request) string
72
+
73
+	// IsHealthEndpoint holds the function to use for determining if the
74
+	// incoming HTTP request should be considered a health check. This is in
75
+	// addition to the private isHealthEndpoint func which may also indicate
76
+	// tracing should be skipped.
77
+	IsHealthEndpoint func(*http.Request) bool
63 78
 }
64 79
 
65 80
 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
66
-	var traceEnd, statsEnd func()
67
-	r, traceEnd = h.startTrace(w, r)
81
+	var tags addedTags
82
+	r, traceEnd := h.startTrace(w, r)
68 83
 	defer traceEnd()
69
-	w, statsEnd = h.startStats(w, r)
70
-	defer statsEnd()
84
+	w, statsEnd := h.startStats(w, r)
85
+	defer statsEnd(&tags)
71 86
 	handler := h.Handler
72 87
 	if handler == nil {
73 88
 		handler = http.DefaultServeMux
74 89
 	}
90
+	r = r.WithContext(context.WithValue(r.Context(), addedTagsKey{}, &tags))
75 91
 	handler.ServeHTTP(w, r)
76 92
 }
77 93
 
78 94
 func (h *Handler) startTrace(w http.ResponseWriter, r *http.Request) (*http.Request, func()) {
79
-	name := spanNameFromURL(r.URL)
95
+	if h.IsHealthEndpoint != nil && h.IsHealthEndpoint(r) || isHealthEndpoint(r.URL.Path) {
96
+		return r, func() {}
97
+	}
98
+	var name string
99
+	if h.FormatSpanName == nil {
100
+		name = spanNameFromURL(r)
101
+	} else {
102
+		name = h.FormatSpanName(r)
103
+	}
80 104
 	ctx := r.Context()
105
+
106
+	startOpts := h.StartOptions
107
+	if h.GetStartOptions != nil {
108
+		startOpts = h.GetStartOptions(r)
109
+	}
110
+
81 111
 	var span *trace.Span
82 112
 	sc, ok := h.extractSpanContext(r)
83 113
 	if ok && !h.IsPublicEndpoint {
84 114
 		ctx, span = trace.StartSpanWithRemoteParent(ctx, name, sc,
85
-			trace.WithSampler(h.StartOptions.Sampler),
115
+			trace.WithSampler(startOpts.Sampler),
86 116
 			trace.WithSpanKind(trace.SpanKindServer))
87 117
 	} else {
88 118
 		ctx, span = trace.StartSpan(ctx, name,
89
-			trace.WithSampler(h.StartOptions.Sampler),
119
+			trace.WithSampler(startOpts.Sampler),
90 120
 			trace.WithSpanKind(trace.SpanKindServer),
91 121
 		)
92 122
 		if ok {
93 123
 			span.AddLink(trace.Link{
94 124
 				TraceID:    sc.TraceID,
95 125
 				SpanID:     sc.SpanID,
96
-				Type:       trace.LinkTypeChild,
126
+				Type:       trace.LinkTypeParent,
97 127
 				Attributes: nil,
98 128
 			})
99 129
 		}
100 130
 	}
101 131
 	span.AddAttributes(requestAttrs(r)...)
132
+	if r.Body == nil {
133
+		// TODO: Handle cases where ContentLength is not set.
134
+	} else if r.ContentLength > 0 {
135
+		span.AddMessageReceiveEvent(0, /* TODO: messageID */
136
+			r.ContentLength, -1)
137
+	}
102 138
 	return r.WithContext(ctx), span.End
103 139
 }
104 140
 
... ...
@@ -109,9 +146,9 @@ func (h *Handler) extractSpanContext(r *http.Request) (trace.SpanContext, bool)
109 109
 	return h.Propagation.SpanContextFromRequest(r)
110 110
 }
111 111
 
112
-func (h *Handler) startStats(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, func()) {
112
+func (h *Handler) startStats(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, func(tags *addedTags)) {
113 113
 	ctx, _ := tag.New(r.Context(),
114
-		tag.Upsert(Host, r.URL.Host),
114
+		tag.Upsert(Host, r.Host),
115 115
 		tag.Upsert(Path, r.URL.Path),
116 116
 		tag.Upsert(Method, r.Method))
117 117
 	track := &trackingResponseWriter{
... ...
@@ -126,7 +163,7 @@ func (h *Handler) startStats(w http.ResponseWriter, r *http.Request) (http.Respo
126 126
 		track.reqSize = r.ContentLength
127 127
 	}
128 128
 	stats.Record(ctx, ServerRequestCount.M(1))
129
-	return track, track.end
129
+	return track.wrappedResponseWriter(), track.end
130 130
 }
131 131
 
132 132
 type trackingResponseWriter struct {
... ...
@@ -140,40 +177,10 @@ type trackingResponseWriter struct {
140 140
 	writer     http.ResponseWriter
141 141
 }
142 142
 
143
-// Compile time assertions for widely used net/http interfaces
144
-var _ http.CloseNotifier = (*trackingResponseWriter)(nil)
145
-var _ http.Flusher = (*trackingResponseWriter)(nil)
146
-var _ http.Hijacker = (*trackingResponseWriter)(nil)
147
-var _ http.Pusher = (*trackingResponseWriter)(nil)
143
+// Compile time assertion for ResponseWriter interface
148 144
 var _ http.ResponseWriter = (*trackingResponseWriter)(nil)
149 145
 
150
-var errHijackerUnimplemented = errors.New("ResponseWriter does not implement http.Hijacker")
151
-
152
-func (t *trackingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
153
-	hj, ok := t.writer.(http.Hijacker)
154
-	if !ok {
155
-		return nil, nil, errHijackerUnimplemented
156
-	}
157
-	return hj.Hijack()
158
-}
159
-
160
-func (t *trackingResponseWriter) CloseNotify() <-chan bool {
161
-	cn, ok := t.writer.(http.CloseNotifier)
162
-	if !ok {
163
-		return nil
164
-	}
165
-	return cn.CloseNotify()
166
-}
167
-
168
-func (t *trackingResponseWriter) Push(target string, opts *http.PushOptions) error {
169
-	pusher, ok := t.writer.(http.Pusher)
170
-	if !ok {
171
-		return http.ErrNotSupported
172
-	}
173
-	return pusher.Push(target, opts)
174
-}
175
-
176
-func (t *trackingResponseWriter) end() {
146
+func (t *trackingResponseWriter) end(tags *addedTags) {
177 147
 	t.endOnce.Do(func() {
178 148
 		if t.statusCode == 0 {
179 149
 			t.statusCode = 200
... ...
@@ -181,6 +188,7 @@ func (t *trackingResponseWriter) end() {
181 181
 
182 182
 		span := trace.FromContext(t.ctx)
183 183
 		span.SetStatus(TraceStatus(t.statusCode, t.statusLine))
184
+		span.AddAttributes(trace.Int64Attribute(StatusCodeAttribute, int64(t.statusCode)))
184 185
 
185 186
 		m := []stats.Measurement{
186 187
 			ServerLatency.M(float64(time.Since(t.start)) / float64(time.Millisecond)),
... ...
@@ -189,8 +197,10 @@ func (t *trackingResponseWriter) end() {
189 189
 		if t.reqSize >= 0 {
190 190
 			m = append(m, ServerRequestBytes.M(t.reqSize))
191 191
 		}
192
-		ctx, _ := tag.New(t.ctx, tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)))
193
-		stats.Record(ctx, m...)
192
+		allTags := make([]tag.Mutator, len(tags.t)+1)
193
+		allTags[0] = tag.Upsert(StatusCode, strconv.Itoa(t.statusCode))
194
+		copy(allTags[1:], tags.t)
195
+		stats.RecordWithTags(t.ctx, allTags, m...)
194 196
 	})
195 197
 }
196 198
 
... ...
@@ -201,6 +211,9 @@ func (t *trackingResponseWriter) Header() http.Header {
201 201
 func (t *trackingResponseWriter) Write(data []byte) (int, error) {
202 202
 	n, err := t.writer.Write(data)
203 203
 	t.respSize += int64(n)
204
+	// Add message event for request bytes sent.
205
+	span := trace.FromContext(t.ctx)
206
+	span.AddMessageSendEvent(0 /* TODO: messageID */, int64(n), -1)
204 207
 	return n, err
205 208
 }
206 209
 
... ...
@@ -210,8 +223,231 @@ func (t *trackingResponseWriter) WriteHeader(statusCode int) {
210 210
 	t.statusLine = http.StatusText(t.statusCode)
211 211
 }
212 212
 
213
-func (t *trackingResponseWriter) Flush() {
214
-	if flusher, ok := t.writer.(http.Flusher); ok {
215
-		flusher.Flush()
213
+// wrappedResponseWriter returns a wrapped version of the original
214
+//  ResponseWriter and only implements the same combination of additional
215
+// interfaces as the original.
216
+// This implementation is based on https://github.com/felixge/httpsnoop.
217
+func (t *trackingResponseWriter) wrappedResponseWriter() http.ResponseWriter {
218
+	var (
219
+		hj, i0 = t.writer.(http.Hijacker)
220
+		cn, i1 = t.writer.(http.CloseNotifier)
221
+		pu, i2 = t.writer.(http.Pusher)
222
+		fl, i3 = t.writer.(http.Flusher)
223
+		rf, i4 = t.writer.(io.ReaderFrom)
224
+	)
225
+
226
+	switch {
227
+	case !i0 && !i1 && !i2 && !i3 && !i4:
228
+		return struct {
229
+			http.ResponseWriter
230
+		}{t}
231
+	case !i0 && !i1 && !i2 && !i3 && i4:
232
+		return struct {
233
+			http.ResponseWriter
234
+			io.ReaderFrom
235
+		}{t, rf}
236
+	case !i0 && !i1 && !i2 && i3 && !i4:
237
+		return struct {
238
+			http.ResponseWriter
239
+			http.Flusher
240
+		}{t, fl}
241
+	case !i0 && !i1 && !i2 && i3 && i4:
242
+		return struct {
243
+			http.ResponseWriter
244
+			http.Flusher
245
+			io.ReaderFrom
246
+		}{t, fl, rf}
247
+	case !i0 && !i1 && i2 && !i3 && !i4:
248
+		return struct {
249
+			http.ResponseWriter
250
+			http.Pusher
251
+		}{t, pu}
252
+	case !i0 && !i1 && i2 && !i3 && i4:
253
+		return struct {
254
+			http.ResponseWriter
255
+			http.Pusher
256
+			io.ReaderFrom
257
+		}{t, pu, rf}
258
+	case !i0 && !i1 && i2 && i3 && !i4:
259
+		return struct {
260
+			http.ResponseWriter
261
+			http.Pusher
262
+			http.Flusher
263
+		}{t, pu, fl}
264
+	case !i0 && !i1 && i2 && i3 && i4:
265
+		return struct {
266
+			http.ResponseWriter
267
+			http.Pusher
268
+			http.Flusher
269
+			io.ReaderFrom
270
+		}{t, pu, fl, rf}
271
+	case !i0 && i1 && !i2 && !i3 && !i4:
272
+		return struct {
273
+			http.ResponseWriter
274
+			http.CloseNotifier
275
+		}{t, cn}
276
+	case !i0 && i1 && !i2 && !i3 && i4:
277
+		return struct {
278
+			http.ResponseWriter
279
+			http.CloseNotifier
280
+			io.ReaderFrom
281
+		}{t, cn, rf}
282
+	case !i0 && i1 && !i2 && i3 && !i4:
283
+		return struct {
284
+			http.ResponseWriter
285
+			http.CloseNotifier
286
+			http.Flusher
287
+		}{t, cn, fl}
288
+	case !i0 && i1 && !i2 && i3 && i4:
289
+		return struct {
290
+			http.ResponseWriter
291
+			http.CloseNotifier
292
+			http.Flusher
293
+			io.ReaderFrom
294
+		}{t, cn, fl, rf}
295
+	case !i0 && i1 && i2 && !i3 && !i4:
296
+		return struct {
297
+			http.ResponseWriter
298
+			http.CloseNotifier
299
+			http.Pusher
300
+		}{t, cn, pu}
301
+	case !i0 && i1 && i2 && !i3 && i4:
302
+		return struct {
303
+			http.ResponseWriter
304
+			http.CloseNotifier
305
+			http.Pusher
306
+			io.ReaderFrom
307
+		}{t, cn, pu, rf}
308
+	case !i0 && i1 && i2 && i3 && !i4:
309
+		return struct {
310
+			http.ResponseWriter
311
+			http.CloseNotifier
312
+			http.Pusher
313
+			http.Flusher
314
+		}{t, cn, pu, fl}
315
+	case !i0 && i1 && i2 && i3 && i4:
316
+		return struct {
317
+			http.ResponseWriter
318
+			http.CloseNotifier
319
+			http.Pusher
320
+			http.Flusher
321
+			io.ReaderFrom
322
+		}{t, cn, pu, fl, rf}
323
+	case i0 && !i1 && !i2 && !i3 && !i4:
324
+		return struct {
325
+			http.ResponseWriter
326
+			http.Hijacker
327
+		}{t, hj}
328
+	case i0 && !i1 && !i2 && !i3 && i4:
329
+		return struct {
330
+			http.ResponseWriter
331
+			http.Hijacker
332
+			io.ReaderFrom
333
+		}{t, hj, rf}
334
+	case i0 && !i1 && !i2 && i3 && !i4:
335
+		return struct {
336
+			http.ResponseWriter
337
+			http.Hijacker
338
+			http.Flusher
339
+		}{t, hj, fl}
340
+	case i0 && !i1 && !i2 && i3 && i4:
341
+		return struct {
342
+			http.ResponseWriter
343
+			http.Hijacker
344
+			http.Flusher
345
+			io.ReaderFrom
346
+		}{t, hj, fl, rf}
347
+	case i0 && !i1 && i2 && !i3 && !i4:
348
+		return struct {
349
+			http.ResponseWriter
350
+			http.Hijacker
351
+			http.Pusher
352
+		}{t, hj, pu}
353
+	case i0 && !i1 && i2 && !i3 && i4:
354
+		return struct {
355
+			http.ResponseWriter
356
+			http.Hijacker
357
+			http.Pusher
358
+			io.ReaderFrom
359
+		}{t, hj, pu, rf}
360
+	case i0 && !i1 && i2 && i3 && !i4:
361
+		return struct {
362
+			http.ResponseWriter
363
+			http.Hijacker
364
+			http.Pusher
365
+			http.Flusher
366
+		}{t, hj, pu, fl}
367
+	case i0 && !i1 && i2 && i3 && i4:
368
+		return struct {
369
+			http.ResponseWriter
370
+			http.Hijacker
371
+			http.Pusher
372
+			http.Flusher
373
+			io.ReaderFrom
374
+		}{t, hj, pu, fl, rf}
375
+	case i0 && i1 && !i2 && !i3 && !i4:
376
+		return struct {
377
+			http.ResponseWriter
378
+			http.Hijacker
379
+			http.CloseNotifier
380
+		}{t, hj, cn}
381
+	case i0 && i1 && !i2 && !i3 && i4:
382
+		return struct {
383
+			http.ResponseWriter
384
+			http.Hijacker
385
+			http.CloseNotifier
386
+			io.ReaderFrom
387
+		}{t, hj, cn, rf}
388
+	case i0 && i1 && !i2 && i3 && !i4:
389
+		return struct {
390
+			http.ResponseWriter
391
+			http.Hijacker
392
+			http.CloseNotifier
393
+			http.Flusher
394
+		}{t, hj, cn, fl}
395
+	case i0 && i1 && !i2 && i3 && i4:
396
+		return struct {
397
+			http.ResponseWriter
398
+			http.Hijacker
399
+			http.CloseNotifier
400
+			http.Flusher
401
+			io.ReaderFrom
402
+		}{t, hj, cn, fl, rf}
403
+	case i0 && i1 && i2 && !i3 && !i4:
404
+		return struct {
405
+			http.ResponseWriter
406
+			http.Hijacker
407
+			http.CloseNotifier
408
+			http.Pusher
409
+		}{t, hj, cn, pu}
410
+	case i0 && i1 && i2 && !i3 && i4:
411
+		return struct {
412
+			http.ResponseWriter
413
+			http.Hijacker
414
+			http.CloseNotifier
415
+			http.Pusher
416
+			io.ReaderFrom
417
+		}{t, hj, cn, pu, rf}
418
+	case i0 && i1 && i2 && i3 && !i4:
419
+		return struct {
420
+			http.ResponseWriter
421
+			http.Hijacker
422
+			http.CloseNotifier
423
+			http.Pusher
424
+			http.Flusher
425
+		}{t, hj, cn, pu, fl}
426
+	case i0 && i1 && i2 && i3 && i4:
427
+		return struct {
428
+			http.ResponseWriter
429
+			http.Hijacker
430
+			http.CloseNotifier
431
+			http.Pusher
432
+			http.Flusher
433
+			io.ReaderFrom
434
+		}{t, hj, cn, pu, fl, rf}
435
+	default:
436
+		return struct {
437
+			http.ResponseWriter
438
+		}{t}
216 439
 	}
217 440
 }
218 441
new file mode 100644
... ...
@@ -0,0 +1,169 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package ochttp
15
+
16
+import (
17
+	"crypto/tls"
18
+	"net/http"
19
+	"net/http/httptrace"
20
+	"strings"
21
+
22
+	"go.opencensus.io/trace"
23
+)
24
+
25
+type spanAnnotator struct {
26
+	sp *trace.Span
27
+}
28
+
29
+// TODO: Remove NewSpanAnnotator at the next release.
30
+
31
+// NewSpanAnnotator returns a httptrace.ClientTrace which annotates
32
+// all emitted httptrace events on the provided Span.
33
+// Deprecated: Use NewSpanAnnotatingClientTrace instead
34
+func NewSpanAnnotator(r *http.Request, s *trace.Span) *httptrace.ClientTrace {
35
+	return NewSpanAnnotatingClientTrace(r, s)
36
+}
37
+
38
+// NewSpanAnnotatingClientTrace returns a httptrace.ClientTrace which annotates
39
+// all emitted httptrace events on the provided Span.
40
+func NewSpanAnnotatingClientTrace(_ *http.Request, s *trace.Span) *httptrace.ClientTrace {
41
+	sa := spanAnnotator{sp: s}
42
+
43
+	return &httptrace.ClientTrace{
44
+		GetConn:              sa.getConn,
45
+		GotConn:              sa.gotConn,
46
+		PutIdleConn:          sa.putIdleConn,
47
+		GotFirstResponseByte: sa.gotFirstResponseByte,
48
+		Got100Continue:       sa.got100Continue,
49
+		DNSStart:             sa.dnsStart,
50
+		DNSDone:              sa.dnsDone,
51
+		ConnectStart:         sa.connectStart,
52
+		ConnectDone:          sa.connectDone,
53
+		TLSHandshakeStart:    sa.tlsHandshakeStart,
54
+		TLSHandshakeDone:     sa.tlsHandshakeDone,
55
+		WroteHeaders:         sa.wroteHeaders,
56
+		Wait100Continue:      sa.wait100Continue,
57
+		WroteRequest:         sa.wroteRequest,
58
+	}
59
+}
60
+
61
+func (s spanAnnotator) getConn(hostPort string) {
62
+	attrs := []trace.Attribute{
63
+		trace.StringAttribute("httptrace.get_connection.host_port", hostPort),
64
+	}
65
+	s.sp.Annotate(attrs, "GetConn")
66
+}
67
+
68
+func (s spanAnnotator) gotConn(info httptrace.GotConnInfo) {
69
+	attrs := []trace.Attribute{
70
+		trace.BoolAttribute("httptrace.got_connection.reused", info.Reused),
71
+		trace.BoolAttribute("httptrace.got_connection.was_idle", info.WasIdle),
72
+	}
73
+	if info.WasIdle {
74
+		attrs = append(attrs,
75
+			trace.StringAttribute("httptrace.got_connection.idle_time", info.IdleTime.String()))
76
+	}
77
+	s.sp.Annotate(attrs, "GotConn")
78
+}
79
+
80
+// PutIdleConn implements a httptrace.ClientTrace hook
81
+func (s spanAnnotator) putIdleConn(err error) {
82
+	var attrs []trace.Attribute
83
+	if err != nil {
84
+		attrs = append(attrs,
85
+			trace.StringAttribute("httptrace.put_idle_connection.error", err.Error()))
86
+	}
87
+	s.sp.Annotate(attrs, "PutIdleConn")
88
+}
89
+
90
+func (s spanAnnotator) gotFirstResponseByte() {
91
+	s.sp.Annotate(nil, "GotFirstResponseByte")
92
+}
93
+
94
+func (s spanAnnotator) got100Continue() {
95
+	s.sp.Annotate(nil, "Got100Continue")
96
+}
97
+
98
+func (s spanAnnotator) dnsStart(info httptrace.DNSStartInfo) {
99
+	attrs := []trace.Attribute{
100
+		trace.StringAttribute("httptrace.dns_start.host", info.Host),
101
+	}
102
+	s.sp.Annotate(attrs, "DNSStart")
103
+}
104
+
105
+func (s spanAnnotator) dnsDone(info httptrace.DNSDoneInfo) {
106
+	var addrs []string
107
+	for _, addr := range info.Addrs {
108
+		addrs = append(addrs, addr.String())
109
+	}
110
+	attrs := []trace.Attribute{
111
+		trace.StringAttribute("httptrace.dns_done.addrs", strings.Join(addrs, " , ")),
112
+	}
113
+	if info.Err != nil {
114
+		attrs = append(attrs,
115
+			trace.StringAttribute("httptrace.dns_done.error", info.Err.Error()))
116
+	}
117
+	s.sp.Annotate(attrs, "DNSDone")
118
+}
119
+
120
+func (s spanAnnotator) connectStart(network, addr string) {
121
+	attrs := []trace.Attribute{
122
+		trace.StringAttribute("httptrace.connect_start.network", network),
123
+		trace.StringAttribute("httptrace.connect_start.addr", addr),
124
+	}
125
+	s.sp.Annotate(attrs, "ConnectStart")
126
+}
127
+
128
+func (s spanAnnotator) connectDone(network, addr string, err error) {
129
+	attrs := []trace.Attribute{
130
+		trace.StringAttribute("httptrace.connect_done.network", network),
131
+		trace.StringAttribute("httptrace.connect_done.addr", addr),
132
+	}
133
+	if err != nil {
134
+		attrs = append(attrs,
135
+			trace.StringAttribute("httptrace.connect_done.error", err.Error()))
136
+	}
137
+	s.sp.Annotate(attrs, "ConnectDone")
138
+}
139
+
140
+func (s spanAnnotator) tlsHandshakeStart() {
141
+	s.sp.Annotate(nil, "TLSHandshakeStart")
142
+}
143
+
144
+func (s spanAnnotator) tlsHandshakeDone(_ tls.ConnectionState, err error) {
145
+	var attrs []trace.Attribute
146
+	if err != nil {
147
+		attrs = append(attrs,
148
+			trace.StringAttribute("httptrace.tls_handshake_done.error", err.Error()))
149
+	}
150
+	s.sp.Annotate(attrs, "TLSHandshakeDone")
151
+}
152
+
153
+func (s spanAnnotator) wroteHeaders() {
154
+	s.sp.Annotate(nil, "WroteHeaders")
155
+}
156
+
157
+func (s spanAnnotator) wait100Continue() {
158
+	s.sp.Annotate(nil, "Wait100Continue")
159
+}
160
+
161
+func (s spanAnnotator) wroteRequest(info httptrace.WroteRequestInfo) {
162
+	var attrs []trace.Attribute
163
+	if info.Err != nil {
164
+		attrs = append(attrs,
165
+			trace.StringAttribute("httptrace.wrote_request.error", info.Err.Error()))
166
+	}
167
+	s.sp.Annotate(attrs, "WroteRequest")
168
+}
... ...
@@ -20,20 +20,67 @@ import (
20 20
 	"go.opencensus.io/tag"
21 21
 )
22 22
 
23
+// Deprecated: client HTTP measures.
24
+var (
25
+	// Deprecated: Use a Count aggregation over one of the other client measures to achieve the same effect.
26
+	ClientRequestCount = stats.Int64(
27
+		"opencensus.io/http/client/request_count",
28
+		"Number of HTTP requests started",
29
+		stats.UnitDimensionless)
30
+	// Deprecated: Use ClientSentBytes.
31
+	ClientRequestBytes = stats.Int64(
32
+		"opencensus.io/http/client/request_bytes",
33
+		"HTTP request body size if set as ContentLength (uncompressed)",
34
+		stats.UnitBytes)
35
+	// Deprecated: Use ClientReceivedBytes.
36
+	ClientResponseBytes = stats.Int64(
37
+		"opencensus.io/http/client/response_bytes",
38
+		"HTTP response body size (uncompressed)",
39
+		stats.UnitBytes)
40
+	// Deprecated: Use ClientRoundtripLatency.
41
+	ClientLatency = stats.Float64(
42
+		"opencensus.io/http/client/latency",
43
+		"End-to-end latency",
44
+		stats.UnitMilliseconds)
45
+)
46
+
23 47
 // The following client HTTP measures are supported for use in custom views.
24 48
 var (
25
-	ClientRequestCount  = stats.Int64("opencensus.io/http/client/request_count", "Number of HTTP requests started", stats.UnitDimensionless)
26
-	ClientRequestBytes  = stats.Int64("opencensus.io/http/client/request_bytes", "HTTP request body size if set as ContentLength (uncompressed)", stats.UnitBytes)
27
-	ClientResponseBytes = stats.Int64("opencensus.io/http/client/response_bytes", "HTTP response body size (uncompressed)", stats.UnitBytes)
28
-	ClientLatency       = stats.Float64("opencensus.io/http/client/latency", "End-to-end latency", stats.UnitMilliseconds)
49
+	ClientSentBytes = stats.Int64(
50
+		"opencensus.io/http/client/sent_bytes",
51
+		"Total bytes sent in request body (not including headers)",
52
+		stats.UnitBytes,
53
+	)
54
+	ClientReceivedBytes = stats.Int64(
55
+		"opencensus.io/http/client/received_bytes",
56
+		"Total bytes received in response bodies (not including headers but including error responses with bodies)",
57
+		stats.UnitBytes,
58
+	)
59
+	ClientRoundtripLatency = stats.Float64(
60
+		"opencensus.io/http/client/roundtrip_latency",
61
+		"Time between first byte of request headers sent to last byte of response received, or terminal error",
62
+		stats.UnitMilliseconds,
63
+	)
29 64
 )
30 65
 
31 66
 // The following server HTTP measures are supported for use in custom views:
32 67
 var (
33
-	ServerRequestCount  = stats.Int64("opencensus.io/http/server/request_count", "Number of HTTP requests started", stats.UnitDimensionless)
34
-	ServerRequestBytes  = stats.Int64("opencensus.io/http/server/request_bytes", "HTTP request body size if set as ContentLength (uncompressed)", stats.UnitBytes)
35
-	ServerResponseBytes = stats.Int64("opencensus.io/http/server/response_bytes", "HTTP response body size (uncompressed)", stats.UnitBytes)
36
-	ServerLatency       = stats.Float64("opencensus.io/http/server/latency", "End-to-end latency", stats.UnitMilliseconds)
68
+	ServerRequestCount = stats.Int64(
69
+		"opencensus.io/http/server/request_count",
70
+		"Number of HTTP requests started",
71
+		stats.UnitDimensionless)
72
+	ServerRequestBytes = stats.Int64(
73
+		"opencensus.io/http/server/request_bytes",
74
+		"HTTP request body size if set as ContentLength (uncompressed)",
75
+		stats.UnitBytes)
76
+	ServerResponseBytes = stats.Int64(
77
+		"opencensus.io/http/server/response_bytes",
78
+		"HTTP response body size (uncompressed)",
79
+		stats.UnitBytes)
80
+	ServerLatency = stats.Float64(
81
+		"opencensus.io/http/server/latency",
82
+		"End-to-end latency",
83
+		stats.UnitMilliseconds)
37 84
 )
38 85
 
39 86
 // The following tags are applied to stats recorded by this package. Host, Path
... ...
@@ -41,28 +88,89 @@ var (
41 41
 // ClientRequestCount or ServerRequestCount, since it is recorded before the status is known.
42 42
 var (
43 43
 	// Host is the value of the HTTP Host header.
44
-	Host, _ = tag.NewKey("http.host")
44
+	//
45
+	// The value of this tag can be controlled by the HTTP client, so you need
46
+	// to watch out for potentially generating high-cardinality labels in your
47
+	// metrics backend if you use this tag in views.
48
+	Host = tag.MustNewKey("http.host")
45 49
 
46 50
 	// StatusCode is the numeric HTTP response status code,
47 51
 	// or "error" if a transport error occurred and no status code was read.
48
-	StatusCode, _ = tag.NewKey("http.status")
52
+	StatusCode = tag.MustNewKey("http.status")
49 53
 
50 54
 	// Path is the URL path (not including query string) in the request.
51
-	Path, _ = tag.NewKey("http.path")
55
+	//
56
+	// The value of this tag can be controlled by the HTTP client, so you need
57
+	// to watch out for potentially generating high-cardinality labels in your
58
+	// metrics backend if you use this tag in views.
59
+	Path = tag.MustNewKey("http.path")
52 60
 
53 61
 	// Method is the HTTP method of the request, capitalized (GET, POST, etc.).
54
-	Method, _ = tag.NewKey("http.method")
62
+	Method = tag.MustNewKey("http.method")
63
+
64
+	// KeyServerRoute is a low cardinality string representing the logical
65
+	// handler of the request. This is usually the pattern registered on the a
66
+	// ServeMux (or similar string).
67
+	KeyServerRoute = tag.MustNewKey("http_server_route")
68
+)
69
+
70
+// Client tag keys.
71
+var (
72
+	// KeyClientMethod is the HTTP method, capitalized (i.e. GET, POST, PUT, DELETE, etc.).
73
+	KeyClientMethod = tag.MustNewKey("http_client_method")
74
+	// KeyClientPath is the URL path (not including query string).
75
+	KeyClientPath = tag.MustNewKey("http_client_path")
76
+	// KeyClientStatus is the HTTP status code as an integer (e.g. 200, 404, 500.), or "error" if no response status line was received.
77
+	KeyClientStatus = tag.MustNewKey("http_client_status")
78
+	// KeyClientHost is the value of the request Host header.
79
+	KeyClientHost = tag.MustNewKey("http_client_host")
55 80
 )
56 81
 
57 82
 // Default distributions used by views in this package.
58 83
 var (
59
-	DefaultSizeDistribution    = view.Distribution(0, 1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296)
60
-	DefaultLatencyDistribution = view.Distribution(0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
84
+	DefaultSizeDistribution    = view.Distribution(1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296)
85
+	DefaultLatencyDistribution = view.Distribution(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
86
+)
87
+
88
+// Package ochttp provides some convenience views for client measures.
89
+// You still need to register these views for data to actually be collected.
90
+var (
91
+	ClientSentBytesDistribution = &view.View{
92
+		Name:        "opencensus.io/http/client/sent_bytes",
93
+		Measure:     ClientSentBytes,
94
+		Aggregation: DefaultSizeDistribution,
95
+		Description: "Total bytes sent in request body (not including headers), by HTTP method and response status",
96
+		TagKeys:     []tag.Key{KeyClientMethod, KeyClientStatus},
97
+	}
98
+
99
+	ClientReceivedBytesDistribution = &view.View{
100
+		Name:        "opencensus.io/http/client/received_bytes",
101
+		Measure:     ClientReceivedBytes,
102
+		Aggregation: DefaultSizeDistribution,
103
+		Description: "Total bytes received in response bodies (not including headers but including error responses with bodies), by HTTP method and response status",
104
+		TagKeys:     []tag.Key{KeyClientMethod, KeyClientStatus},
105
+	}
106
+
107
+	ClientRoundtripLatencyDistribution = &view.View{
108
+		Name:        "opencensus.io/http/client/roundtrip_latency",
109
+		Measure:     ClientRoundtripLatency,
110
+		Aggregation: DefaultLatencyDistribution,
111
+		Description: "End-to-end latency, by HTTP method and response status",
112
+		TagKeys:     []tag.Key{KeyClientMethod, KeyClientStatus},
113
+	}
114
+
115
+	ClientCompletedCount = &view.View{
116
+		Name:        "opencensus.io/http/client/completed_count",
117
+		Measure:     ClientRoundtripLatency,
118
+		Aggregation: view.Count(),
119
+		Description: "Count of completed requests, by HTTP method and response status",
120
+		TagKeys:     []tag.Key{KeyClientMethod, KeyClientStatus},
121
+	}
61 122
 )
62 123
 
63
-// Package ochttp provides some convenience views.
64
-// You need to subscribe to the views for data to actually be collected.
124
+// Deprecated: Old client Views.
65 125
 var (
126
+	// Deprecated: No direct replacement, but see ClientCompletedCount.
66 127
 	ClientRequestCountView = &view.View{
67 128
 		Name:        "opencensus.io/http/client/request_count",
68 129
 		Description: "Count of HTTP requests started",
... ...
@@ -70,43 +178,52 @@ var (
70 70
 		Aggregation: view.Count(),
71 71
 	}
72 72
 
73
+	// Deprecated: Use ClientSentBytesDistribution.
73 74
 	ClientRequestBytesView = &view.View{
74 75
 		Name:        "opencensus.io/http/client/request_bytes",
75 76
 		Description: "Size distribution of HTTP request body",
76
-		Measure:     ClientRequestBytes,
77
+		Measure:     ClientSentBytes,
77 78
 		Aggregation: DefaultSizeDistribution,
78 79
 	}
79 80
 
81
+	// Deprecated: Use ClientReceivedBytesDistribution instead.
80 82
 	ClientResponseBytesView = &view.View{
81 83
 		Name:        "opencensus.io/http/client/response_bytes",
82 84
 		Description: "Size distribution of HTTP response body",
83
-		Measure:     ClientResponseBytes,
85
+		Measure:     ClientReceivedBytes,
84 86
 		Aggregation: DefaultSizeDistribution,
85 87
 	}
86 88
 
89
+	// Deprecated: Use ClientRoundtripLatencyDistribution instead.
87 90
 	ClientLatencyView = &view.View{
88 91
 		Name:        "opencensus.io/http/client/latency",
89 92
 		Description: "Latency distribution of HTTP requests",
90
-		Measure:     ClientLatency,
93
+		Measure:     ClientRoundtripLatency,
91 94
 		Aggregation: DefaultLatencyDistribution,
92 95
 	}
93 96
 
97
+	// Deprecated: Use ClientCompletedCount instead.
94 98
 	ClientRequestCountByMethod = &view.View{
95 99
 		Name:        "opencensus.io/http/client/request_count_by_method",
96 100
 		Description: "Client request count by HTTP method",
97 101
 		TagKeys:     []tag.Key{Method},
98
-		Measure:     ClientRequestCount,
102
+		Measure:     ClientSentBytes,
99 103
 		Aggregation: view.Count(),
100 104
 	}
101 105
 
106
+	// Deprecated: Use ClientCompletedCount instead.
102 107
 	ClientResponseCountByStatusCode = &view.View{
103 108
 		Name:        "opencensus.io/http/client/response_count_by_status_code",
104 109
 		Description: "Client response count by status code",
105 110
 		TagKeys:     []tag.Key{StatusCode},
106
-		Measure:     ClientLatency,
111
+		Measure:     ClientRoundtripLatency,
107 112
 		Aggregation: view.Count(),
108 113
 	}
114
+)
109 115
 
116
+// Package ochttp provides some convenience views for server measures.
117
+// You still need to register these views for data to actually be collected.
118
+var (
110 119
 	ServerRequestCountView = &view.View{
111 120
 		Name:        "opencensus.io/http/server/request_count",
112 121
 		Description: "Count of HTTP requests started",
... ...
@@ -153,6 +270,7 @@ var (
153 153
 )
154 154
 
155 155
 // DefaultClientViews are the default client views provided by this package.
156
+// Deprecated: No replacement. Register the views you would like individually.
156 157
 var DefaultClientViews = []*view.View{
157 158
 	ClientRequestCountView,
158 159
 	ClientRequestBytesView,
... ...
@@ -163,6 +281,7 @@ var DefaultClientViews = []*view.View{
163 163
 }
164 164
 
165 165
 // DefaultServerViews are the default server views provided by this package.
166
+// Deprecated: No replacement. Register the views you would like individually.
166 167
 var DefaultServerViews = []*view.View{
167 168
 	ServerRequestCountView,
168 169
 	ServerRequestBytesView,
... ...
@@ -17,7 +17,7 @@ package ochttp
17 17
 import (
18 18
 	"io"
19 19
 	"net/http"
20
-	"net/url"
20
+	"net/http/httptrace"
21 21
 
22 22
 	"go.opencensus.io/plugin/ochttp/propagation/b3"
23 23
 	"go.opencensus.io/trace"
... ...
@@ -34,14 +34,17 @@ const (
34 34
 	HostAttribute       = "http.host"
35 35
 	MethodAttribute     = "http.method"
36 36
 	PathAttribute       = "http.path"
37
+	URLAttribute        = "http.url"
37 38
 	UserAgentAttribute  = "http.user_agent"
38 39
 	StatusCodeAttribute = "http.status_code"
39 40
 )
40 41
 
41 42
 type traceTransport struct {
42
-	base         http.RoundTripper
43
-	startOptions trace.StartOptions
44
-	format       propagation.HTTPFormat
43
+	base           http.RoundTripper
44
+	startOptions   trace.StartOptions
45
+	format         propagation.HTTPFormat
46
+	formatSpanName func(*http.Request) string
47
+	newClientTrace func(*http.Request, *trace.Span) *httptrace.ClientTrace
45 48
 }
46 49
 
47 50
 // TODO(jbd): Add message events for request and response size.
... ...
@@ -50,15 +53,30 @@ type traceTransport struct {
50 50
 // The created span can follow a parent span, if a parent is presented in
51 51
 // the request's context.
52 52
 func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
53
-	name := spanNameFromURL(req.URL)
53
+	name := t.formatSpanName(req)
54 54
 	// TODO(jbd): Discuss whether we want to prefix
55 55
 	// outgoing requests with Sent.
56
-	_, span := trace.StartSpan(req.Context(), name,
56
+	ctx, span := trace.StartSpan(req.Context(), name,
57 57
 		trace.WithSampler(t.startOptions.Sampler),
58 58
 		trace.WithSpanKind(trace.SpanKindClient))
59 59
 
60
-	req = req.WithContext(trace.WithSpan(req.Context(), span))
60
+	if t.newClientTrace != nil {
61
+		req = req.WithContext(httptrace.WithClientTrace(ctx, t.newClientTrace(req, span)))
62
+	} else {
63
+		req = req.WithContext(ctx)
64
+	}
65
+
61 66
 	if t.format != nil {
67
+		// SpanContextToRequest will modify its Request argument, which is
68
+		// contrary to the contract for http.RoundTripper, so we need to
69
+		// pass it a copy of the Request.
70
+		// However, the Request struct itself was already copied by
71
+		// the WithContext calls above and so we just need to copy the header.
72
+		header := make(http.Header)
73
+		for k, v := range req.Header {
74
+			header[k] = v
75
+		}
76
+		req.Header = header
62 77
 		t.format.SpanContextToRequest(span.SpanContext(), req)
63 78
 	}
64 79
 
... ...
@@ -76,7 +94,8 @@ func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
76 76
 	// span.End() will be invoked after
77 77
 	// a read from resp.Body returns io.EOF or when
78 78
 	// resp.Body.Close() is invoked.
79
-	resp.Body = &bodyTracker{rc: resp.Body, span: span}
79
+	bt := &bodyTracker{rc: resp.Body, span: span}
80
+	resp.Body = wrappedBody(bt, resp.Body)
80 81
 	return resp, err
81 82
 }
82 83
 
... ...
@@ -127,17 +146,26 @@ func (t *traceTransport) CancelRequest(req *http.Request) {
127 127
 	}
128 128
 }
129 129
 
130
-func spanNameFromURL(u *url.URL) string {
131
-	return u.Path
130
+func spanNameFromURL(req *http.Request) string {
131
+	return req.URL.Path
132 132
 }
133 133
 
134 134
 func requestAttrs(r *http.Request) []trace.Attribute {
135
-	return []trace.Attribute{
135
+	userAgent := r.UserAgent()
136
+
137
+	attrs := make([]trace.Attribute, 0, 5)
138
+	attrs = append(attrs,
136 139
 		trace.StringAttribute(PathAttribute, r.URL.Path),
137
-		trace.StringAttribute(HostAttribute, r.URL.Host),
140
+		trace.StringAttribute(URLAttribute, r.URL.String()),
141
+		trace.StringAttribute(HostAttribute, r.Host),
138 142
 		trace.StringAttribute(MethodAttribute, r.Method),
139
-		trace.StringAttribute(UserAgentAttribute, r.UserAgent()),
143
+	)
144
+
145
+	if userAgent != "" {
146
+		attrs = append(attrs, trace.StringAttribute(UserAgentAttribute, userAgent))
140 147
 	}
148
+
149
+	return attrs
141 150
 }
142 151
 
143 152
 func responseAttrs(resp *http.Response) []trace.Attribute {
... ...
@@ -146,7 +174,7 @@ func responseAttrs(resp *http.Response) []trace.Attribute {
146 146
 	}
147 147
 }
148 148
 
149
-// HTTPStatusToTraceStatus converts the HTTP status code to a trace.Status that
149
+// TraceStatus is a utility to convert the HTTP status code to a trace.Status that
150 150
 // represents the outcome as closely as possible.
151 151
 func TraceStatus(httpStatusCode int, statusLine string) trace.Status {
152 152
 	var code int32
... ...
@@ -158,6 +186,8 @@ func TraceStatus(httpStatusCode int, statusLine string) trace.Status {
158 158
 		code = trace.StatusCodeCancelled
159 159
 	case http.StatusBadRequest:
160 160
 		code = trace.StatusCodeInvalidArgument
161
+	case http.StatusUnprocessableEntity:
162
+		code = trace.StatusCodeInvalidArgument
161 163
 	case http.StatusGatewayTimeout:
162 164
 		code = trace.StatusCodeDeadlineExceeded
163 165
 	case http.StatusNotFound:
... ...
@@ -174,26 +204,41 @@ func TraceStatus(httpStatusCode int, statusLine string) trace.Status {
174 174
 		code = trace.StatusCodeUnavailable
175 175
 	case http.StatusOK:
176 176
 		code = trace.StatusCodeOK
177
+	case http.StatusConflict:
178
+		code = trace.StatusCodeAlreadyExists
177 179
 	}
180
+
178 181
 	return trace.Status{Code: code, Message: codeToStr[code]}
179 182
 }
180 183
 
181 184
 var codeToStr = map[int32]string{
182
-	trace.StatusCodeOK:                 `"OK"`,
183
-	trace.StatusCodeCancelled:          `"CANCELLED"`,
184
-	trace.StatusCodeUnknown:            `"UNKNOWN"`,
185
-	trace.StatusCodeInvalidArgument:    `"INVALID_ARGUMENT"`,
186
-	trace.StatusCodeDeadlineExceeded:   `"DEADLINE_EXCEEDED"`,
187
-	trace.StatusCodeNotFound:           `"NOT_FOUND"`,
188
-	trace.StatusCodeAlreadyExists:      `"ALREADY_EXISTS"`,
189
-	trace.StatusCodePermissionDenied:   `"PERMISSION_DENIED"`,
190
-	trace.StatusCodeResourceExhausted:  `"RESOURCE_EXHAUSTED"`,
191
-	trace.StatusCodeFailedPrecondition: `"FAILED_PRECONDITION"`,
192
-	trace.StatusCodeAborted:            `"ABORTED"`,
193
-	trace.StatusCodeOutOfRange:         `"OUT_OF_RANGE"`,
194
-	trace.StatusCodeUnimplemented:      `"UNIMPLEMENTED"`,
195
-	trace.StatusCodeInternal:           `"INTERNAL"`,
196
-	trace.StatusCodeUnavailable:        `"UNAVAILABLE"`,
197
-	trace.StatusCodeDataLoss:           `"DATA_LOSS"`,
198
-	trace.StatusCodeUnauthenticated:    `"UNAUTHENTICATED"`,
185
+	trace.StatusCodeOK:                 `OK`,
186
+	trace.StatusCodeCancelled:          `CANCELLED`,
187
+	trace.StatusCodeUnknown:            `UNKNOWN`,
188
+	trace.StatusCodeInvalidArgument:    `INVALID_ARGUMENT`,
189
+	trace.StatusCodeDeadlineExceeded:   `DEADLINE_EXCEEDED`,
190
+	trace.StatusCodeNotFound:           `NOT_FOUND`,
191
+	trace.StatusCodeAlreadyExists:      `ALREADY_EXISTS`,
192
+	trace.StatusCodePermissionDenied:   `PERMISSION_DENIED`,
193
+	trace.StatusCodeResourceExhausted:  `RESOURCE_EXHAUSTED`,
194
+	trace.StatusCodeFailedPrecondition: `FAILED_PRECONDITION`,
195
+	trace.StatusCodeAborted:            `ABORTED`,
196
+	trace.StatusCodeOutOfRange:         `OUT_OF_RANGE`,
197
+	trace.StatusCodeUnimplemented:      `UNIMPLEMENTED`,
198
+	trace.StatusCodeInternal:           `INTERNAL`,
199
+	trace.StatusCodeUnavailable:        `UNAVAILABLE`,
200
+	trace.StatusCodeDataLoss:           `DATA_LOSS`,
201
+	trace.StatusCodeUnauthenticated:    `UNAUTHENTICATED`,
202
+}
203
+
204
+func isHealthEndpoint(path string) bool {
205
+	// Health checking is pretty frequent and
206
+	// traces collected for health endpoints
207
+	// can be extremely noisy and expensive.
208
+	// Disable canonical health checking endpoints
209
+	// like /healthz and /_ah/health for now.
210
+	if path == "/healthz" || path == "/_ah/health" {
211
+		return true
212
+	}
213
+	return false
199 214
 }
200 215
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+// Copyright 2019, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package ochttp
15
+
16
+import (
17
+	"io"
18
+)
19
+
20
+// wrappedBody returns a wrapped version of the original
21
+// Body and only implements the same combination of additional
22
+// interfaces as the original.
23
+func wrappedBody(wrapper io.ReadCloser, body io.ReadCloser) io.ReadCloser {
24
+	var (
25
+		wr, i0 = body.(io.Writer)
26
+	)
27
+	switch {
28
+	case !i0:
29
+		return struct {
30
+			io.ReadCloser
31
+		}{wrapper}
32
+
33
+	case i0:
34
+		return struct {
35
+			io.ReadCloser
36
+			io.Writer
37
+		}{wrapper, wr}
38
+	default:
39
+		return struct {
40
+			io.ReadCloser
41
+		}{wrapper}
42
+	}
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,164 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+// Package resource provides functionality for resource, which capture
15
+// identifying information about the entities for which signals are exported.
16
+package resource
17
+
18
+import (
19
+	"context"
20
+	"fmt"
21
+	"os"
22
+	"regexp"
23
+	"sort"
24
+	"strconv"
25
+	"strings"
26
+)
27
+
28
+// Environment variables used by FromEnv to decode a resource.
29
+const (
30
+	EnvVarType   = "OC_RESOURCE_TYPE"
31
+	EnvVarLabels = "OC_RESOURCE_LABELS"
32
+)
33
+
34
+// Resource describes an entity about which identifying information and metadata is exposed.
35
+// For example, a type "k8s.io/container" may hold labels describing the pod name and namespace.
36
+type Resource struct {
37
+	Type   string
38
+	Labels map[string]string
39
+}
40
+
41
+// EncodeLabels encodes a labels map to a string as provided via the OC_RESOURCE_LABELS environment variable.
42
+func EncodeLabels(labels map[string]string) string {
43
+	sortedKeys := make([]string, 0, len(labels))
44
+	for k := range labels {
45
+		sortedKeys = append(sortedKeys, k)
46
+	}
47
+	sort.Strings(sortedKeys)
48
+
49
+	s := ""
50
+	for i, k := range sortedKeys {
51
+		if i > 0 {
52
+			s += ","
53
+		}
54
+		s += k + "=" + strconv.Quote(labels[k])
55
+	}
56
+	return s
57
+}
58
+
59
+var labelRegex = regexp.MustCompile(`^\s*([[:ascii:]]{1,256}?)=("[[:ascii:]]{0,256}?")\s*,`)
60
+
61
+// DecodeLabels decodes a serialized label map as used in the OC_RESOURCE_LABELS variable.
62
+// A list of labels of the form `<key1>="<value1>",<key2>="<value2>",...` is accepted.
63
+// Domain names and paths are accepted as label keys.
64
+// Most users will want to use FromEnv instead.
65
+func DecodeLabels(s string) (map[string]string, error) {
66
+	m := map[string]string{}
67
+	// Ensure a trailing comma, which allows us to keep the regex simpler
68
+	s = strings.TrimRight(strings.TrimSpace(s), ",") + ","
69
+
70
+	for len(s) > 0 {
71
+		match := labelRegex.FindStringSubmatch(s)
72
+		if len(match) == 0 {
73
+			return nil, fmt.Errorf("invalid label formatting, remainder: %s", s)
74
+		}
75
+		v := match[2]
76
+		if v == "" {
77
+			v = match[3]
78
+		} else {
79
+			var err error
80
+			if v, err = strconv.Unquote(v); err != nil {
81
+				return nil, fmt.Errorf("invalid label formatting, remainder: %s, err: %s", s, err)
82
+			}
83
+		}
84
+		m[match[1]] = v
85
+
86
+		s = s[len(match[0]):]
87
+	}
88
+	return m, nil
89
+}
90
+
91
+// FromEnv is a detector that loads resource information from the OC_RESOURCE_TYPE
92
+// and OC_RESOURCE_labelS environment variables.
93
+func FromEnv(context.Context) (*Resource, error) {
94
+	res := &Resource{
95
+		Type: strings.TrimSpace(os.Getenv(EnvVarType)),
96
+	}
97
+	labels := strings.TrimSpace(os.Getenv(EnvVarLabels))
98
+	if labels == "" {
99
+		return res, nil
100
+	}
101
+	var err error
102
+	if res.Labels, err = DecodeLabels(labels); err != nil {
103
+		return nil, err
104
+	}
105
+	return res, nil
106
+}
107
+
108
+var _ Detector = FromEnv
109
+
110
+// merge resource information from b into a. In case of a collision, a takes precedence.
111
+func merge(a, b *Resource) *Resource {
112
+	if a == nil {
113
+		return b
114
+	}
115
+	if b == nil {
116
+		return a
117
+	}
118
+	res := &Resource{
119
+		Type:   a.Type,
120
+		Labels: map[string]string{},
121
+	}
122
+	if res.Type == "" {
123
+		res.Type = b.Type
124
+	}
125
+	for k, v := range b.Labels {
126
+		res.Labels[k] = v
127
+	}
128
+	// Labels from resource a overwrite labels from resource b.
129
+	for k, v := range a.Labels {
130
+		res.Labels[k] = v
131
+	}
132
+	return res
133
+}
134
+
135
+// Detector attempts to detect resource information.
136
+// If the detector cannot find resource information, the returned resource is nil but no
137
+// error is returned.
138
+// An error is only returned on unexpected failures.
139
+type Detector func(context.Context) (*Resource, error)
140
+
141
+// MultiDetector returns a Detector that calls all input detectors in order and
142
+// merges each result with the previous one. In case a type of label key is already set,
143
+// the first set value is takes precedence.
144
+// It returns on the first error that a sub-detector encounters.
145
+func MultiDetector(detectors ...Detector) Detector {
146
+	return func(ctx context.Context) (*Resource, error) {
147
+		return detectAll(ctx, detectors...)
148
+	}
149
+}
150
+
151
+// detectall calls all input detectors sequentially an merges each result with the previous one.
152
+// It returns on the first error that a sub-detector encounters.
153
+func detectAll(ctx context.Context, detectors ...Detector) (*Resource, error) {
154
+	var res *Resource
155
+	for _, d := range detectors {
156
+		r, err := d(ctx)
157
+		if err != nil {
158
+			return nil, err
159
+		}
160
+		res = merge(res, r)
161
+	}
162
+	return res, nil
163
+}
... ...
@@ -21,35 +21,49 @@ aggregate the collected data, and export the aggregated data.
21 21
 
22 22
 Measures
23 23
 
24
-A measure represents a type of metric to be tracked and recorded.
24
+A measure represents a type of data point to be tracked and recorded.
25 25
 For example, latency, request Mb/s, and response Mb/s are measures
26 26
 to collect from a server.
27 27
 
28
-Each measure needs to be registered before being used. Measure
29
-constructors such as Int64 and Float64 automatically
28
+Measure constructors such as Int64 and Float64 automatically
30 29
 register the measure by the given name. Each registered measure needs
31 30
 to be unique by name. Measures also have a description and a unit.
32 31
 
33
-Libraries can define and export measures for their end users to
34
-create views and collect instrumentation data.
32
+Libraries can define and export measures. Application authors can then
33
+create views and collect and break down measures by the tags they are
34
+interested in.
35 35
 
36 36
 Recording measurements
37 37
 
38 38
 Measurement is a data point to be collected for a measure. For example,
39 39
 for a latency (ms) measure, 100 is a measurement that represents a 100ms
40
-latency event. Users collect data points on the existing measures with
40
+latency event. Measurements are created from measures with
41 41
 the current context. Tags from the current context are recorded with the
42 42
 measurements if they are any.
43 43
 
44
-Recorded measurements are dropped immediately if user is not aggregating
45
-them via views. Users don't necessarily need to conditionally enable/disable
44
+Recorded measurements are dropped immediately if no views are registered for them.
45
+There is usually no need to conditionally enable and disable
46 46
 recording to reduce cost. Recording of measurements is cheap.
47 47
 
48
-Libraries can always record measurements, and end-users can later decide
48
+Libraries can always record measurements, and applications can later decide
49 49
 on which measurements they want to collect by registering views. This allows
50 50
 libraries to turn on the instrumentation by default.
51
+
52
+Exemplars
53
+
54
+For a given recorded measurement, the associated exemplar is a diagnostic map
55
+that gives more information about the measurement.
56
+
57
+When aggregated using a Distribution aggregation, an exemplar is kept for each
58
+bucket in the Distribution. This allows you to easily find an example of a
59
+measurement that fell into each bucket.
60
+
61
+For example, if you also use the OpenCensus trace package and you
62
+record a measurement with a context that contains a sampled trace span,
63
+then the trace span will be added to the exemplar associated with the measurement.
64
+
65
+When exported to a supporting back end, you should be able to easily navigate
66
+to example traces that fell into each bucket in the Distribution.
67
+
51 68
 */
52 69
 package stats // import "go.opencensus.io/stats"
53
-
54
-// TODO(acetechnologist): Add a link to the language independent OpenCensus
55
-// spec when it is available.
... ...
@@ -19,7 +19,7 @@ import (
19 19
 )
20 20
 
21 21
 // DefaultRecorder will be called for each Record call.
22
-var DefaultRecorder func(*tag.Map, interface{})
22
+var DefaultRecorder func(tags *tag.Map, measurement interface{}, attachments map[string]interface{})
23 23
 
24 24
 // SubscriptionReporter reports when a view subscribed with a measure.
25 25
 var SubscriptionReporter func(measure string)
26 26
deleted file mode 100644
... ...
@@ -1,28 +0,0 @@
1
-// Copyright 2018, 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
-package internal // import "go.opencensus.io/stats/internal"
16
-
17
-const (
18
-	MaxNameLength = 255
19
-)
20
-
21
-func IsPrintable(str string) bool {
22
-	for _, r := range str {
23
-		if !(r >= ' ' && r <= '~') {
24
-			return false
25
-		}
26
-	}
27
-	return true
28
-}
... ...
@@ -20,19 +20,31 @@ import (
20 20
 	"sync/atomic"
21 21
 )
22 22
 
23
-// Measure represents a type of metric to be tracked and recorded.
24
-// For example, latency, request Mb/s, and response Mb/s are measures
23
+// Measure represents a single numeric value to be tracked and recorded.
24
+// For example, latency, request bytes, and response bytes could be measures
25 25
 // to collect from a server.
26 26
 //
27
-// Each measure needs to be registered before being used.
28
-// Measure constructors such as Int64 and
29
-// Float64 automatically registers the measure
30
-// by the given name.
31
-// Each registered measure needs to be unique by name.
32
-// Measures also have a description and a unit.
27
+// Measures by themselves have no outside effects. In order to be exported,
28
+// the measure needs to be used in a View. If no Views are defined over a
29
+// measure, there is very little cost in recording it.
33 30
 type Measure interface {
31
+	// Name returns the name of this measure.
32
+	//
33
+	// Measure names are globally unique (among all libraries linked into your program).
34
+	// We recommend prefixing the measure name with a domain name relevant to your
35
+	// project or application.
36
+	//
37
+	// Measure names are never sent over the wire or exported to backends.
38
+	// They are only used to create Views.
34 39
 	Name() string
40
+
41
+	// Description returns the human-readable description of this measure.
35 42
 	Description() string
43
+
44
+	// Unit returns the units for the values this measure takes on.
45
+	//
46
+	// Units are encoded according to the case-sensitive abbreviations from the
47
+	// Unified Code for Units of Measure: http://unitsofmeasure.org/ucum.html
36 48
 	Unit() string
37 49
 }
38 50
 
... ...
@@ -81,8 +93,9 @@ func registerMeasureHandle(name, desc, unit string) *measureDescriptor {
81 81
 // provides methods to create measurements of their kind. For example, Int64Measure
82 82
 // provides M to convert an int64 into a measurement.
83 83
 type Measurement struct {
84
-	v float64
85
-	m Measure
84
+	v    float64
85
+	m    Measure
86
+	desc *measureDescriptor
86 87
 }
87 88
 
88 89
 // Value returns the value of the Measurement as a float64.
... ...
@@ -15,38 +15,41 @@
15 15
 
16 16
 package stats
17 17
 
18
-// Float64Measure is a measure of type float64.
18
+// Float64Measure is a measure for float64 values.
19 19
 type Float64Measure struct {
20
-	md *measureDescriptor
20
+	desc *measureDescriptor
21
+}
22
+
23
+// M creates a new float64 measurement.
24
+// Use Record to record measurements.
25
+func (m *Float64Measure) M(v float64) Measurement {
26
+	return Measurement{
27
+		m:    m,
28
+		desc: m.desc,
29
+		v:    v,
30
+	}
31
+}
32
+
33
+// Float64 creates a new measure for float64 values.
34
+//
35
+// See the documentation for interface Measure for more guidance on the
36
+// parameters of this function.
37
+func Float64(name, description, unit string) *Float64Measure {
38
+	mi := registerMeasureHandle(name, description, unit)
39
+	return &Float64Measure{mi}
21 40
 }
22 41
 
23 42
 // Name returns the name of the measure.
24 43
 func (m *Float64Measure) Name() string {
25
-	return m.md.name
44
+	return m.desc.name
26 45
 }
27 46
 
28 47
 // Description returns the description of the measure.
29 48
 func (m *Float64Measure) Description() string {
30
-	return m.md.description
49
+	return m.desc.description
31 50
 }
32 51
 
33 52
 // Unit returns the unit of the measure.
34 53
 func (m *Float64Measure) Unit() string {
35
-	return m.md.unit
36
-}
37
-
38
-// M creates a new float64 measurement.
39
-// Use Record to record measurements.
40
-func (m *Float64Measure) M(v float64) Measurement {
41
-	if !m.md.subscribed() {
42
-		return Measurement{}
43
-	}
44
-	return Measurement{m: m, v: v}
45
-}
46
-
47
-// Float64 creates a new measure of type Float64Measure.
48
-// It never returns an error.
49
-func Float64(name, description, unit string) *Float64Measure {
50
-	mi := registerMeasureHandle(name, description, unit)
51
-	return &Float64Measure{mi}
54
+	return m.desc.unit
52 55
 }
... ...
@@ -15,38 +15,41 @@
15 15
 
16 16
 package stats
17 17
 
18
-// Int64Measure is a measure of type int64.
18
+// Int64Measure is a measure for int64 values.
19 19
 type Int64Measure struct {
20
-	md *measureDescriptor
20
+	desc *measureDescriptor
21
+}
22
+
23
+// M creates a new int64 measurement.
24
+// Use Record to record measurements.
25
+func (m *Int64Measure) M(v int64) Measurement {
26
+	return Measurement{
27
+		m:    m,
28
+		desc: m.desc,
29
+		v:    float64(v),
30
+	}
31
+}
32
+
33
+// Int64 creates a new measure for int64 values.
34
+//
35
+// See the documentation for interface Measure for more guidance on the
36
+// parameters of this function.
37
+func Int64(name, description, unit string) *Int64Measure {
38
+	mi := registerMeasureHandle(name, description, unit)
39
+	return &Int64Measure{mi}
21 40
 }
22 41
 
23 42
 // Name returns the name of the measure.
24 43
 func (m *Int64Measure) Name() string {
25
-	return m.md.name
44
+	return m.desc.name
26 45
 }
27 46
 
28 47
 // Description returns the description of the measure.
29 48
 func (m *Int64Measure) Description() string {
30
-	return m.md.description
49
+	return m.desc.description
31 50
 }
32 51
 
33 52
 // Unit returns the unit of the measure.
34 53
 func (m *Int64Measure) Unit() string {
35
-	return m.md.unit
36
-}
37
-
38
-// M creates a new int64 measurement.
39
-// Use Record to record measurements.
40
-func (m *Int64Measure) M(v int64) Measurement {
41
-	if !m.md.subscribed() {
42
-		return Measurement{}
43
-	}
44
-	return Measurement{m: m, v: float64(v)}
45
-}
46
-
47
-// Int64 creates a new measure of type Int64Measure.
48
-// It never returns an error.
49
-func Int64(name, description, unit string) *Int64Measure {
50
-	mi := registerMeasureHandle(name, description, unit)
51
-	return &Int64Measure{mi}
54
+	return m.desc.unit
52 55
 }
... ...
@@ -18,6 +18,7 @@ package stats
18 18
 import (
19 19
 	"context"
20 20
 
21
+	"go.opencensus.io/metric/metricdata"
21 22
 	"go.opencensus.io/stats/internal"
22 23
 	"go.opencensus.io/tag"
23 24
 )
... ...
@@ -30,23 +31,87 @@ func init() {
30 30
 	}
31 31
 }
32 32
 
33
-// Record records one or multiple measurements with the same tags at once.
33
+type recordOptions struct {
34
+	attachments  metricdata.Attachments
35
+	mutators     []tag.Mutator
36
+	measurements []Measurement
37
+}
38
+
39
+// WithAttachments applies provided exemplar attachments.
40
+func WithAttachments(attachments metricdata.Attachments) Options {
41
+	return func(ro *recordOptions) {
42
+		ro.attachments = attachments
43
+	}
44
+}
45
+
46
+// WithTags applies provided tag mutators.
47
+func WithTags(mutators ...tag.Mutator) Options {
48
+	return func(ro *recordOptions) {
49
+		ro.mutators = mutators
50
+	}
51
+}
52
+
53
+// WithMeasurements applies provided measurements.
54
+func WithMeasurements(measurements ...Measurement) Options {
55
+	return func(ro *recordOptions) {
56
+		ro.measurements = measurements
57
+	}
58
+}
59
+
60
+// Options apply changes to recordOptions.
61
+type Options func(*recordOptions)
62
+
63
+func createRecordOption(ros ...Options) *recordOptions {
64
+	o := &recordOptions{}
65
+	for _, ro := range ros {
66
+		ro(o)
67
+	}
68
+	return o
69
+}
70
+
71
+// Record records one or multiple measurements with the same context at once.
34 72
 // If there are any tags in the context, measurements will be tagged with them.
35 73
 func Record(ctx context.Context, ms ...Measurement) {
36
-	if len(ms) == 0 {
37
-		return
74
+	RecordWithOptions(ctx, WithMeasurements(ms...))
75
+}
76
+
77
+// RecordWithTags records one or multiple measurements at once.
78
+//
79
+// Measurements will be tagged with the tags in the context mutated by the mutators.
80
+// RecordWithTags is useful if you want to record with tag mutations but don't want
81
+// to propagate the mutations in the context.
82
+func RecordWithTags(ctx context.Context, mutators []tag.Mutator, ms ...Measurement) error {
83
+	return RecordWithOptions(ctx, WithTags(mutators...), WithMeasurements(ms...))
84
+}
85
+
86
+// RecordWithOptions records measurements from the given options (if any) against context
87
+// and tags and attachments in the options (if any).
88
+// If there are any tags in the context, measurements will be tagged with them.
89
+func RecordWithOptions(ctx context.Context, ros ...Options) error {
90
+	o := createRecordOption(ros...)
91
+	if len(o.measurements) == 0 {
92
+		return nil
38 93
 	}
39
-	var record bool
40
-	for _, m := range ms {
41
-		if (m != Measurement{}) {
94
+	recorder := internal.DefaultRecorder
95
+	if recorder == nil {
96
+		return nil
97
+	}
98
+	record := false
99
+	for _, m := range o.measurements {
100
+		if m.desc.subscribed() {
42 101
 			record = true
43 102
 			break
44 103
 		}
45 104
 	}
46 105
 	if !record {
47
-		return
106
+		return nil
48 107
 	}
49
-	if internal.DefaultRecorder != nil {
50
-		internal.DefaultRecorder(tag.FromContext(ctx), ms)
108
+	if len(o.mutators) > 0 {
109
+		var err error
110
+		if ctx, err = tag.New(ctx, o.mutators...); err != nil {
111
+			return err
112
+		}
51 113
 	}
114
+	recorder(tag.FromContext(ctx), o.measurements, o.attachments)
115
+	return nil
52 116
 }
... ...
@@ -22,4 +22,5 @@ const (
22 22
 	UnitDimensionless = "1"
23 23
 	UnitBytes         = "By"
24 24
 	UnitMilliseconds  = "ms"
25
+	UnitSeconds       = "s"
25 26
 )
... ...
@@ -82,7 +82,7 @@ func Sum() *Aggregation {
82 82
 // Distribution indicates that the desired aggregation is
83 83
 // a histogram distribution.
84 84
 //
85
-// An distribution aggregation may contain a histogram of the values in the
85
+// A distribution aggregation may contain a histogram of the values in the
86 86
 // population. The bucket boundaries for that histogram are described
87 87
 // by the bounds. This defines len(bounds)+1 buckets.
88 88
 //
... ...
@@ -99,13 +99,14 @@ func Sum() *Aggregation {
99 99
 // If len(bounds) is 1 then there is no finite buckets, and that single
100 100
 // element is the common boundary of the overflow and underflow buckets.
101 101
 func Distribution(bounds ...float64) *Aggregation {
102
-	return &Aggregation{
102
+	agg := &Aggregation{
103 103
 		Type:    AggTypeDistribution,
104 104
 		Buckets: bounds,
105
-		newData: func() AggregationData {
106
-			return newDistributionData(bounds)
107
-		},
108 105
 	}
106
+	agg.newData = func() AggregationData {
107
+		return newDistributionData(agg)
108
+	}
109
+	return agg
109 110
 }
110 111
 
111 112
 // LastValue only reports the last value recorded using this
... ...
@@ -17,6 +17,9 @@ package view
17 17
 
18 18
 import (
19 19
 	"math"
20
+	"time"
21
+
22
+	"go.opencensus.io/metric/metricdata"
20 23
 )
21 24
 
22 25
 // AggregationData represents an aggregated value from a collection.
... ...
@@ -24,9 +27,10 @@ import (
24 24
 // Mosts users won't directly access aggregration data.
25 25
 type AggregationData interface {
26 26
 	isAggregationData() bool
27
-	addSample(v float64)
27
+	addSample(v float64, attachments map[string]interface{}, t time.Time)
28 28
 	clone() AggregationData
29 29
 	equal(other AggregationData) bool
30
+	toPoint(t metricdata.Type, time time.Time) metricdata.Point
30 31
 }
31 32
 
32 33
 const epsilon = 1e-9
... ...
@@ -41,7 +45,7 @@ type CountData struct {
41 41
 
42 42
 func (a *CountData) isAggregationData() bool { return true }
43 43
 
44
-func (a *CountData) addSample(v float64) {
44
+func (a *CountData) addSample(_ float64, _ map[string]interface{}, _ time.Time) {
45 45
 	a.Value = a.Value + 1
46 46
 }
47 47
 
... ...
@@ -58,6 +62,15 @@ func (a *CountData) equal(other AggregationData) bool {
58 58
 	return a.Value == a2.Value
59 59
 }
60 60
 
61
+func (a *CountData) toPoint(metricType metricdata.Type, t time.Time) metricdata.Point {
62
+	switch metricType {
63
+	case metricdata.TypeCumulativeInt64:
64
+		return metricdata.NewInt64Point(t, a.Value)
65
+	default:
66
+		panic("unsupported metricdata.Type")
67
+	}
68
+}
69
+
61 70
 // SumData is the aggregated data for the Sum aggregation.
62 71
 // A sum aggregation processes data and sums up the recordings.
63 72
 //
... ...
@@ -68,8 +81,8 @@ type SumData struct {
68 68
 
69 69
 func (a *SumData) isAggregationData() bool { return true }
70 70
 
71
-func (a *SumData) addSample(f float64) {
72
-	a.Value += f
71
+func (a *SumData) addSample(v float64, _ map[string]interface{}, _ time.Time) {
72
+	a.Value += v
73 73
 }
74 74
 
75 75
 func (a *SumData) clone() AggregationData {
... ...
@@ -84,26 +97,45 @@ func (a *SumData) equal(other AggregationData) bool {
84 84
 	return math.Pow(a.Value-a2.Value, 2) < epsilon
85 85
 }
86 86
 
87
+func (a *SumData) toPoint(metricType metricdata.Type, t time.Time) metricdata.Point {
88
+	switch metricType {
89
+	case metricdata.TypeCumulativeInt64:
90
+		return metricdata.NewInt64Point(t, int64(a.Value))
91
+	case metricdata.TypeCumulativeFloat64:
92
+		return metricdata.NewFloat64Point(t, a.Value)
93
+	default:
94
+		panic("unsupported metricdata.Type")
95
+	}
96
+}
97
+
87 98
 // DistributionData is the aggregated data for the
88 99
 // Distribution aggregation.
89 100
 //
90 101
 // Most users won't directly access distribution data.
102
+//
103
+// For a distribution with N bounds, the associated DistributionData will have
104
+// N+1 buckets.
91 105
 type DistributionData struct {
92
-	Count           int64     // number of data points aggregated
93
-	Min             float64   // minimum value in the distribution
94
-	Max             float64   // max value in the distribution
95
-	Mean            float64   // mean of the distribution
96
-	SumOfSquaredDev float64   // sum of the squared deviation from the mean
97
-	CountPerBucket  []int64   // number of occurrences per bucket
98
-	bounds          []float64 // histogram distribution of the values
106
+	Count           int64   // number of data points aggregated
107
+	Min             float64 // minimum value in the distribution
108
+	Max             float64 // max value in the distribution
109
+	Mean            float64 // mean of the distribution
110
+	SumOfSquaredDev float64 // sum of the squared deviation from the mean
111
+	CountPerBucket  []int64 // number of occurrences per bucket
112
+	// ExemplarsPerBucket is slice the same length as CountPerBucket containing
113
+	// an exemplar for the associated bucket, or nil.
114
+	ExemplarsPerBucket []*metricdata.Exemplar
115
+	bounds             []float64 // histogram distribution of the values
99 116
 }
100 117
 
101
-func newDistributionData(bounds []float64) *DistributionData {
118
+func newDistributionData(agg *Aggregation) *DistributionData {
119
+	bucketCount := len(agg.Buckets) + 1
102 120
 	return &DistributionData{
103
-		CountPerBucket: make([]int64, len(bounds)+1),
104
-		bounds:         bounds,
105
-		Min:            math.MaxFloat64,
106
-		Max:            math.SmallestNonzeroFloat64,
121
+		CountPerBucket:     make([]int64, bucketCount),
122
+		ExemplarsPerBucket: make([]*metricdata.Exemplar, bucketCount),
123
+		bounds:             agg.Buckets,
124
+		Min:                math.MaxFloat64,
125
+		Max:                math.SmallestNonzeroFloat64,
107 126
 	}
108 127
 }
109 128
 
... ...
@@ -119,46 +151,62 @@ func (a *DistributionData) variance() float64 {
119 119
 
120 120
 func (a *DistributionData) isAggregationData() bool { return true }
121 121
 
122
-func (a *DistributionData) addSample(f float64) {
123
-	if f < a.Min {
124
-		a.Min = f
122
+// TODO(songy23): support exemplar attachments.
123
+func (a *DistributionData) addSample(v float64, attachments map[string]interface{}, t time.Time) {
124
+	if v < a.Min {
125
+		a.Min = v
125 126
 	}
126
-	if f > a.Max {
127
-		a.Max = f
127
+	if v > a.Max {
128
+		a.Max = v
128 129
 	}
129 130
 	a.Count++
130
-	a.incrementBucketCount(f)
131
+	a.addToBucket(v, attachments, t)
131 132
 
132 133
 	if a.Count == 1 {
133
-		a.Mean = f
134
+		a.Mean = v
134 135
 		return
135 136
 	}
136 137
 
137 138
 	oldMean := a.Mean
138
-	a.Mean = a.Mean + (f-a.Mean)/float64(a.Count)
139
-	a.SumOfSquaredDev = a.SumOfSquaredDev + (f-oldMean)*(f-a.Mean)
139
+	a.Mean = a.Mean + (v-a.Mean)/float64(a.Count)
140
+	a.SumOfSquaredDev = a.SumOfSquaredDev + (v-oldMean)*(v-a.Mean)
140 141
 }
141 142
 
142
-func (a *DistributionData) incrementBucketCount(f float64) {
143
-	if len(a.bounds) == 0 {
144
-		a.CountPerBucket[0]++
145
-		return
143
+func (a *DistributionData) addToBucket(v float64, attachments map[string]interface{}, t time.Time) {
144
+	var count *int64
145
+	var i int
146
+	var b float64
147
+	for i, b = range a.bounds {
148
+		if v < b {
149
+			count = &a.CountPerBucket[i]
150
+			break
151
+		}
152
+	}
153
+	if count == nil { // Last bucket.
154
+		i = len(a.bounds)
155
+		count = &a.CountPerBucket[i]
156
+	}
157
+	*count++
158
+	if exemplar := getExemplar(v, attachments, t); exemplar != nil {
159
+		a.ExemplarsPerBucket[i] = exemplar
146 160
 	}
161
+}
147 162
 
148
-	for i, b := range a.bounds {
149
-		if f < b {
150
-			a.CountPerBucket[i]++
151
-			return
152
-		}
163
+func getExemplar(v float64, attachments map[string]interface{}, t time.Time) *metricdata.Exemplar {
164
+	if len(attachments) == 0 {
165
+		return nil
166
+	}
167
+	return &metricdata.Exemplar{
168
+		Value:       v,
169
+		Timestamp:   t,
170
+		Attachments: attachments,
153 171
 	}
154
-	a.CountPerBucket[len(a.bounds)]++
155 172
 }
156 173
 
157 174
 func (a *DistributionData) clone() AggregationData {
158
-	counts := make([]int64, len(a.CountPerBucket))
159
-	copy(counts, a.CountPerBucket)
160 175
 	c := *a
161
-	c.CountPerBucket = counts
176
+	c.CountPerBucket = append([]int64(nil), a.CountPerBucket...)
177
+	c.ExemplarsPerBucket = append([]*metricdata.Exemplar(nil), a.ExemplarsPerBucket...)
162 178
 	return &c
163 179
 }
164 180
 
... ...
@@ -181,6 +229,33 @@ func (a *DistributionData) equal(other AggregationData) bool {
181 181
 	return a.Count == a2.Count && a.Min == a2.Min && a.Max == a2.Max && math.Pow(a.Mean-a2.Mean, 2) < epsilon && math.Pow(a.variance()-a2.variance(), 2) < epsilon
182 182
 }
183 183
 
184
+func (a *DistributionData) toPoint(metricType metricdata.Type, t time.Time) metricdata.Point {
185
+	switch metricType {
186
+	case metricdata.TypeCumulativeDistribution:
187
+		buckets := []metricdata.Bucket{}
188
+		for i := 0; i < len(a.CountPerBucket); i++ {
189
+			buckets = append(buckets, metricdata.Bucket{
190
+				Count:    a.CountPerBucket[i],
191
+				Exemplar: a.ExemplarsPerBucket[i],
192
+			})
193
+		}
194
+		bucketOptions := &metricdata.BucketOptions{Bounds: a.bounds}
195
+
196
+		val := &metricdata.Distribution{
197
+			Count:                 a.Count,
198
+			Sum:                   a.Sum(),
199
+			SumOfSquaredDeviation: a.SumOfSquaredDev,
200
+			BucketOptions:         bucketOptions,
201
+			Buckets:               buckets,
202
+		}
203
+		return metricdata.NewDistributionPoint(t, val)
204
+
205
+	default:
206
+		// TODO: [rghetia] when we have a use case for TypeGaugeDistribution.
207
+		panic("unsupported metricdata.Type")
208
+	}
209
+}
210
+
184 211
 // LastValueData returns the last value recorded for LastValue aggregation.
185 212
 type LastValueData struct {
186 213
 	Value float64
... ...
@@ -190,7 +265,7 @@ func (l *LastValueData) isAggregationData() bool {
190 190
 	return true
191 191
 }
192 192
 
193
-func (l *LastValueData) addSample(v float64) {
193
+func (l *LastValueData) addSample(v float64, _ map[string]interface{}, _ time.Time) {
194 194
 	l.Value = v
195 195
 }
196 196
 
... ...
@@ -205,3 +280,14 @@ func (l *LastValueData) equal(other AggregationData) bool {
205 205
 	}
206 206
 	return l.Value == a2.Value
207 207
 }
208
+
209
+func (l *LastValueData) toPoint(metricType metricdata.Type, t time.Time) metricdata.Point {
210
+	switch metricType {
211
+	case metricdata.TypeGaugeInt64:
212
+		return metricdata.NewInt64Point(t, int64(l.Value))
213
+	case metricdata.TypeGaugeFloat64:
214
+		return metricdata.NewFloat64Point(t, l.Value)
215
+	default:
216
+		panic("unsupported metricdata.Type")
217
+	}
218
+}
... ...
@@ -17,6 +17,7 @@ package view
17 17
 
18 18
 import (
19 19
 	"sort"
20
+	"time"
20 21
 
21 22
 	"go.opencensus.io/internal/tagencoding"
22 23
 	"go.opencensus.io/tag"
... ...
@@ -31,20 +32,21 @@ type collector struct {
31 31
 	a *Aggregation
32 32
 }
33 33
 
34
-func (c *collector) addSample(s string, v float64) {
34
+func (c *collector) addSample(s string, v float64, attachments map[string]interface{}, t time.Time) {
35 35
 	aggregator, ok := c.signatures[s]
36 36
 	if !ok {
37 37
 		aggregator = c.a.newData()
38 38
 		c.signatures[s] = aggregator
39 39
 	}
40
-	aggregator.addSample(v)
40
+	aggregator.addSample(v, attachments, t)
41 41
 }
42 42
 
43
+// collectRows returns a snapshot of the collected Row values.
43 44
 func (c *collector) collectedRows(keys []tag.Key) []*Row {
44
-	var rows []*Row
45
+	rows := make([]*Row, 0, len(c.signatures))
45 46
 	for sig, aggregator := range c.signatures {
46 47
 		tags := decodeTags([]byte(sig), keys)
47
-		row := &Row{tags, aggregator}
48
+		row := &Row{Tags: tags, Data: aggregator.clone()}
48 49
 		rows = append(rows, row)
49 50
 	}
50 51
 	return rows
... ...
@@ -13,33 +13,34 @@
13 13
 // limitations under the License.
14 14
 //
15 15
 
16
-/*
17
-Package view contains support for collecting and exposing aggregates over stats.
18
-
19
-In order to collect measurements, views need to be defined and registered.
20
-A view allows recorded measurements to be filtered and aggregated over a time window.
21
-
22
-All recorded measurements can be filtered by a list of tags.
23
-
24
-OpenCensus provides several aggregation methods: count, distribution and sum.
25
-Count aggregation only counts the number of measurement points. Distribution
26
-aggregation provides statistical summary of the aggregated data. Sum distribution
27
-sums up the measurement points. Aggregations are cumulative.
28
-
29
-Users can dynamically create and delete views.
30
-
31
-Libraries can export their own views and claim the view names
32
-by registering them themselves.
33
-
34
-Exporting
35
-
36
-Collected and aggregated data can be exported to a metric collection
37
-backend by registering its exporter.
38
-
39
-Multiple exporters can be registered to upload the data to various
40
-different backends. Users need to unregister the exporters once they
41
-no longer are needed.
42
-*/
16
+// Package view contains support for collecting and exposing aggregates over stats.
17
+//
18
+// In order to collect measurements, views need to be defined and registered.
19
+// A view allows recorded measurements to be filtered and aggregated.
20
+//
21
+// All recorded measurements can be grouped by a list of tags.
22
+//
23
+// OpenCensus provides several aggregation methods: Count, Distribution and Sum.
24
+//
25
+// Count only counts the number of measurement points recorded.
26
+// Distribution provides statistical summary of the aggregated data by counting
27
+// how many recorded measurements fall into each bucket.
28
+// Sum adds up the measurement values.
29
+// LastValue just keeps track of the most recently recorded measurement value.
30
+// All aggregations are cumulative.
31
+//
32
+// Views can be registered and unregistered at any time during program execution.
33
+//
34
+// Libraries can define views but it is recommended that in most cases registering
35
+// views be left up to applications.
36
+//
37
+// Exporting
38
+//
39
+// Collected and aggregated data can be exported to a metric collection
40
+// backend by registering its exporter.
41
+//
42
+// Multiple exporters can be registered to upload the data to various
43
+// different back ends.
43 44
 package view // import "go.opencensus.io/stats/view"
44 45
 
45 46
 // TODO(acetechnologist): Add a link to the language independent OpenCensus
... ...
@@ -27,6 +27,9 @@ var (
27 27
 // Exporter takes a significant amount of time to
28 28
 // process a Data, that work should be done on another goroutine.
29 29
 //
30
+// It is safe to assume that ExportView will not be called concurrently from
31
+// multiple goroutines.
32
+//
30 33
 // The Data should not be modified.
31 34
 type Exporter interface {
32 35
 	ExportView(viewData *Data)
... ...
@@ -17,19 +17,20 @@ package view
17 17
 
18 18
 import (
19 19
 	"bytes"
20
+	"errors"
20 21
 	"fmt"
21 22
 	"reflect"
22 23
 	"sort"
23 24
 	"sync/atomic"
24 25
 	"time"
25 26
 
27
+	"go.opencensus.io/metric/metricdata"
26 28
 	"go.opencensus.io/stats"
27
-	"go.opencensus.io/stats/internal"
28 29
 	"go.opencensus.io/tag"
29 30
 )
30 31
 
31 32
 // View allows users to aggregate the recorded stats.Measurements.
32
-// Views need to be passed to the Subscribe function to be before data will be
33
+// Views need to be passed to the Register function before data will be
33 34
 // collected and sent to Exporters.
34 35
 type View struct {
35 36
 	Name        string // Name of View. Must be unique. If unset, will default to the name of the Measure.
... ...
@@ -42,7 +43,7 @@ type View struct {
42 42
 	// Measure is a stats.Measure to aggregate in this view.
43 43
 	Measure stats.Measure
44 44
 
45
-	// Aggregation is the aggregation function tp apply to the set of Measurements.
45
+	// Aggregation is the aggregation function to apply to the set of Measurements.
46 46
 	Aggregation *Aggregation
47 47
 }
48 48
 
... ...
@@ -67,14 +68,19 @@ func (v *View) same(other *View) bool {
67 67
 		v.Measure.Name() == other.Measure.Name()
68 68
 }
69 69
 
70
+// ErrNegativeBucketBounds error returned if histogram contains negative bounds.
71
+//
72
+// Deprecated: this should not be public.
73
+var ErrNegativeBucketBounds = errors.New("negative bucket bounds not supported")
74
+
70 75
 // canonicalize canonicalizes v by setting explicit
71 76
 // defaults for Name and Description and sorting the TagKeys
72 77
 func (v *View) canonicalize() error {
73 78
 	if v.Measure == nil {
74
-		return fmt.Errorf("cannot subscribe view %q: measure not set", v.Name)
79
+		return fmt.Errorf("cannot register view %q: measure not set", v.Name)
75 80
 	}
76 81
 	if v.Aggregation == nil {
77
-		return fmt.Errorf("cannot subscribe view %q: aggregation not set", v.Name)
82
+		return fmt.Errorf("cannot register view %q: aggregation not set", v.Name)
78 83
 	}
79 84
 	if v.Name == "" {
80 85
 		v.Name = v.Measure.Name()
... ...
@@ -88,20 +94,40 @@ func (v *View) canonicalize() error {
88 88
 	sort.Slice(v.TagKeys, func(i, j int) bool {
89 89
 		return v.TagKeys[i].Name() < v.TagKeys[j].Name()
90 90
 	})
91
+	sort.Float64s(v.Aggregation.Buckets)
92
+	for _, b := range v.Aggregation.Buckets {
93
+		if b < 0 {
94
+			return ErrNegativeBucketBounds
95
+		}
96
+	}
97
+	// drop 0 bucket silently.
98
+	v.Aggregation.Buckets = dropZeroBounds(v.Aggregation.Buckets...)
99
+
91 100
 	return nil
92 101
 }
93 102
 
103
+func dropZeroBounds(bounds ...float64) []float64 {
104
+	for i, bound := range bounds {
105
+		if bound > 0 {
106
+			return bounds[i:]
107
+		}
108
+	}
109
+	return []float64{}
110
+}
111
+
94 112
 // viewInternal is the internal representation of a View.
95 113
 type viewInternal struct {
96
-	view       *View  // view is the canonicalized View definition associated with this view.
97
-	subscribed uint32 // 1 if someone is subscribed and data need to be exported, use atomic to access
98
-	collector  *collector
114
+	view             *View  // view is the canonicalized View definition associated with this view.
115
+	subscribed       uint32 // 1 if someone is subscribed and data need to be exported, use atomic to access
116
+	collector        *collector
117
+	metricDescriptor *metricdata.Descriptor
99 118
 }
100 119
 
101 120
 func newViewInternal(v *View) (*viewInternal, error) {
102 121
 	return &viewInternal{
103
-		view:      v,
104
-		collector: &collector{make(map[string]AggregationData), v.Aggregation},
122
+		view:             v,
123
+		collector:        &collector{make(map[string]AggregationData), v.Aggregation},
124
+		metricDescriptor: viewToMetricDescriptor(v),
105 125
 	}, nil
106 126
 }
107 127
 
... ...
@@ -127,12 +153,12 @@ func (v *viewInternal) collectedRows() []*Row {
127 127
 	return v.collector.collectedRows(v.view.TagKeys)
128 128
 }
129 129
 
130
-func (v *viewInternal) addSample(m *tag.Map, val float64) {
130
+func (v *viewInternal) addSample(m *tag.Map, val float64, attachments map[string]interface{}, t time.Time) {
131 131
 	if !v.isSubscribed() {
132 132
 		return
133 133
 	}
134 134
 	sig := string(encodeWithKeys(m, v.view.TagKeys))
135
-	v.collector.addSample(sig, val)
135
+	v.collector.addSample(sig, val, attachments, t)
136 136
 }
137 137
 
138 138
 // A Data is a set of rows about usage of the single measure associated
... ...
@@ -163,7 +189,7 @@ func (r *Row) String() string {
163 163
 }
164 164
 
165 165
 // Equal returns true if both rows are equal. Tags are expected to be ordered
166
-// by the key name. Even both rows have the same tags but the tags appear in
166
+// by the key name. Even if both rows have the same tags but the tags appear in
167 167
 // different orders it will return false.
168 168
 func (r *Row) Equal(other *Row) bool {
169 169
 	if r == other {
... ...
@@ -172,11 +198,23 @@ func (r *Row) Equal(other *Row) bool {
172 172
 	return reflect.DeepEqual(r.Tags, other.Tags) && r.Data.equal(other.Data)
173 173
 }
174 174
 
175
+const maxNameLength = 255
176
+
177
+// Returns true if the given string contains only printable characters.
178
+func isPrintable(str string) bool {
179
+	for _, r := range str {
180
+		if !(r >= ' ' && r <= '~') {
181
+			return false
182
+		}
183
+	}
184
+	return true
185
+}
186
+
175 187
 func checkViewName(name string) error {
176
-	if len(name) > internal.MaxNameLength {
177
-		return fmt.Errorf("view name cannot be larger than %v", internal.MaxNameLength)
188
+	if len(name) > maxNameLength {
189
+		return fmt.Errorf("view name cannot be larger than %v", maxNameLength)
178 190
 	}
179
-	if !internal.IsPrintable(name) {
191
+	if !isPrintable(name) {
180 192
 		return fmt.Errorf("view name needs to be an ASCII string")
181 193
 	}
182 194
 	return nil
183 195
new file mode 100644
... ...
@@ -0,0 +1,149 @@
0
+// Copyright 2019, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+//
14
+
15
+package view
16
+
17
+import (
18
+	"time"
19
+
20
+	"go.opencensus.io/metric/metricdata"
21
+	"go.opencensus.io/stats"
22
+)
23
+
24
+func getUnit(unit string) metricdata.Unit {
25
+	switch unit {
26
+	case "1":
27
+		return metricdata.UnitDimensionless
28
+	case "ms":
29
+		return metricdata.UnitMilliseconds
30
+	case "By":
31
+		return metricdata.UnitBytes
32
+	}
33
+	return metricdata.UnitDimensionless
34
+}
35
+
36
+func getType(v *View) metricdata.Type {
37
+	m := v.Measure
38
+	agg := v.Aggregation
39
+
40
+	switch agg.Type {
41
+	case AggTypeSum:
42
+		switch m.(type) {
43
+		case *stats.Int64Measure:
44
+			return metricdata.TypeCumulativeInt64
45
+		case *stats.Float64Measure:
46
+			return metricdata.TypeCumulativeFloat64
47
+		default:
48
+			panic("unexpected measure type")
49
+		}
50
+	case AggTypeDistribution:
51
+		return metricdata.TypeCumulativeDistribution
52
+	case AggTypeLastValue:
53
+		switch m.(type) {
54
+		case *stats.Int64Measure:
55
+			return metricdata.TypeGaugeInt64
56
+		case *stats.Float64Measure:
57
+			return metricdata.TypeGaugeFloat64
58
+		default:
59
+			panic("unexpected measure type")
60
+		}
61
+	case AggTypeCount:
62
+		switch m.(type) {
63
+		case *stats.Int64Measure:
64
+			return metricdata.TypeCumulativeInt64
65
+		case *stats.Float64Measure:
66
+			return metricdata.TypeCumulativeInt64
67
+		default:
68
+			panic("unexpected measure type")
69
+		}
70
+	default:
71
+		panic("unexpected aggregation type")
72
+	}
73
+}
74
+
75
+func getLabelKeys(v *View) []metricdata.LabelKey {
76
+	labelKeys := []metricdata.LabelKey{}
77
+	for _, k := range v.TagKeys {
78
+		labelKeys = append(labelKeys, metricdata.LabelKey{Key: k.Name()})
79
+	}
80
+	return labelKeys
81
+}
82
+
83
+func viewToMetricDescriptor(v *View) *metricdata.Descriptor {
84
+	return &metricdata.Descriptor{
85
+		Name:        v.Name,
86
+		Description: v.Description,
87
+		Unit:        convertUnit(v),
88
+		Type:        getType(v),
89
+		LabelKeys:   getLabelKeys(v),
90
+	}
91
+}
92
+
93
+func convertUnit(v *View) metricdata.Unit {
94
+	switch v.Aggregation.Type {
95
+	case AggTypeCount:
96
+		return metricdata.UnitDimensionless
97
+	default:
98
+		return getUnit(v.Measure.Unit())
99
+	}
100
+}
101
+
102
+func toLabelValues(row *Row, expectedKeys []metricdata.LabelKey) []metricdata.LabelValue {
103
+	labelValues := []metricdata.LabelValue{}
104
+	tagMap := make(map[string]string)
105
+	for _, tag := range row.Tags {
106
+		tagMap[tag.Key.Name()] = tag.Value
107
+	}
108
+
109
+	for _, key := range expectedKeys {
110
+		if val, ok := tagMap[key.Key]; ok {
111
+			labelValues = append(labelValues, metricdata.NewLabelValue(val))
112
+		} else {
113
+			labelValues = append(labelValues, metricdata.LabelValue{})
114
+		}
115
+	}
116
+	return labelValues
117
+}
118
+
119
+func rowToTimeseries(v *viewInternal, row *Row, now time.Time, startTime time.Time) *metricdata.TimeSeries {
120
+	return &metricdata.TimeSeries{
121
+		Points:      []metricdata.Point{row.Data.toPoint(v.metricDescriptor.Type, now)},
122
+		LabelValues: toLabelValues(row, v.metricDescriptor.LabelKeys),
123
+		StartTime:   startTime,
124
+	}
125
+}
126
+
127
+func viewToMetric(v *viewInternal, now time.Time, startTime time.Time) *metricdata.Metric {
128
+	if v.metricDescriptor.Type == metricdata.TypeGaugeInt64 ||
129
+		v.metricDescriptor.Type == metricdata.TypeGaugeFloat64 {
130
+		startTime = time.Time{}
131
+	}
132
+
133
+	rows := v.collectedRows()
134
+	if len(rows) == 0 {
135
+		return nil
136
+	}
137
+
138
+	ts := []*metricdata.TimeSeries{}
139
+	for _, row := range rows {
140
+		ts = append(ts, rowToTimeseries(v, row, now, startTime))
141
+	}
142
+
143
+	m := &metricdata.Metric{
144
+		Descriptor: *v.metricDescriptor,
145
+		TimeSeries: ts,
146
+	}
147
+	return m
148
+}
... ...
@@ -17,8 +17,11 @@ package view
17 17
 
18 18
 import (
19 19
 	"fmt"
20
+	"sync"
20 21
 	"time"
21 22
 
23
+	"go.opencensus.io/metric/metricdata"
24
+	"go.opencensus.io/metric/metricproducer"
22 25
 	"go.opencensus.io/stats"
23 26
 	"go.opencensus.io/stats/internal"
24 27
 	"go.opencensus.io/tag"
... ...
@@ -43,14 +46,15 @@ type worker struct {
43 43
 	timer      *time.Ticker
44 44
 	c          chan command
45 45
 	quit, done chan bool
46
+	mu         sync.RWMutex
46 47
 }
47 48
 
48 49
 var defaultWorker *worker
49 50
 
50 51
 var defaultReportingDuration = 10 * time.Second
51 52
 
52
-// Find returns a subscribed view associated with this name.
53
-// If no subscribed view is found, nil is returned.
53
+// Find returns a registered view associated with this name.
54
+// If no registered view is found, nil is returned.
54 55
 func Find(name string) (v *View) {
55 56
 	req := &getViewByNameReq{
56 57
 		name: name,
... ...
@@ -62,13 +66,8 @@ func Find(name string) (v *View) {
62 62
 }
63 63
 
64 64
 // Register begins collecting data for the given views.
65
-// Once a view is subscribed, it reports data to the registered exporters.
65
+// Once a view is registered, it reports data to the registered exporters.
66 66
 func Register(views ...*View) error {
67
-	for _, v := range views {
68
-		if err := v.canonicalize(); err != nil {
69
-			return err
70
-		}
71
-	}
72 67
 	req := &registerViewReq{
73 68
 		views: views,
74 69
 		err:   make(chan error),
... ...
@@ -94,6 +93,8 @@ func Unregister(views ...*View) {
94 94
 	<-req.done
95 95
 }
96 96
 
97
+// RetrieveData gets a snapshot of the data collected for the the view registered
98
+// with the given name. It is intended for testing only.
97 99
 func RetrieveData(viewName string) ([]*Row, error) {
98 100
 	req := &retrieveDataReq{
99 101
 		now: time.Now(),
... ...
@@ -105,17 +106,23 @@ func RetrieveData(viewName string) ([]*Row, error) {
105 105
 	return resp.rows, resp.err
106 106
 }
107 107
 
108
-func record(tags *tag.Map, ms interface{}) {
108
+func record(tags *tag.Map, ms interface{}, attachments map[string]interface{}) {
109 109
 	req := &recordReq{
110
-		tm: tags,
111
-		ms: ms.([]stats.Measurement),
110
+		tm:          tags,
111
+		ms:          ms.([]stats.Measurement),
112
+		attachments: attachments,
113
+		t:           time.Now(),
112 114
 	}
113 115
 	defaultWorker.c <- req
114 116
 }
115 117
 
116 118
 // SetReportingPeriod sets the interval between reporting aggregated views in
117
-// the program. If duration is less than or
118
-// equal to zero, it enables the default behavior.
119
+// the program. If duration is less than or equal to zero, it enables the
120
+// default behavior.
121
+//
122
+// Note: each exporter makes different promises about what the lowest supported
123
+// duration is. For example, the Stackdriver exporter recommends a value no
124
+// lower than 1 minute. Consult each exporter per your needs.
119 125
 func SetReportingPeriod(d time.Duration) {
120 126
 	// TODO(acetechnologist): ensure that the duration d is more than a certain
121 127
 	// value. e.g. 1s
... ...
@@ -140,6 +147,9 @@ func newWorker() *worker {
140 140
 }
141 141
 
142 142
 func (w *worker) start() {
143
+	prodMgr := metricproducer.GlobalManager()
144
+	prodMgr.AddProducer(w)
145
+
143 146
 	for {
144 147
 		select {
145 148
 		case cmd := <-w.c:
... ...
@@ -156,6 +166,9 @@ func (w *worker) start() {
156 156
 }
157 157
 
158 158
 func (w *worker) stop() {
159
+	prodMgr := metricproducer.GlobalManager()
160
+	prodMgr.DeleteProducer(w)
161
+
159 162
 	w.quit <- true
160 163
 	<-w.done
161 164
 }
... ...
@@ -173,13 +186,15 @@ func (w *worker) getMeasureRef(name string) *measureRef {
173 173
 }
174 174
 
175 175
 func (w *worker) tryRegisterView(v *View) (*viewInternal, error) {
176
+	w.mu.Lock()
177
+	defer w.mu.Unlock()
176 178
 	vi, err := newViewInternal(v)
177 179
 	if err != nil {
178 180
 		return nil, err
179 181
 	}
180 182
 	if x, ok := w.views[vi.view.Name]; ok {
181 183
 		if !x.view.same(vi.view) {
182
-			return nil, fmt.Errorf("cannot subscribe view %q; a different view with the same name is already subscribed", v.Name)
184
+			return nil, fmt.Errorf("cannot register view %q; a different view with the same name is already registered", v.Name)
183 185
 		}
184 186
 
185 187
 		// the view is already registered so there is nothing to do and the
... ...
@@ -192,40 +207,75 @@ func (w *worker) tryRegisterView(v *View) (*viewInternal, error) {
192 192
 	return vi, nil
193 193
 }
194 194
 
195
+func (w *worker) unregisterView(viewName string) {
196
+	w.mu.Lock()
197
+	defer w.mu.Unlock()
198
+	delete(w.views, viewName)
199
+}
200
+
201
+func (w *worker) reportView(v *viewInternal, now time.Time) {
202
+	if !v.isSubscribed() {
203
+		return
204
+	}
205
+	rows := v.collectedRows()
206
+	_, ok := w.startTimes[v]
207
+	if !ok {
208
+		w.startTimes[v] = now
209
+	}
210
+	viewData := &Data{
211
+		View:  v.view,
212
+		Start: w.startTimes[v],
213
+		End:   time.Now(),
214
+		Rows:  rows,
215
+	}
216
+	exportersMu.Lock()
217
+	for e := range exporters {
218
+		e.ExportView(viewData)
219
+	}
220
+	exportersMu.Unlock()
221
+}
222
+
195 223
 func (w *worker) reportUsage(now time.Time) {
224
+	w.mu.Lock()
225
+	defer w.mu.Unlock()
196 226
 	for _, v := range w.views {
197
-		if !v.isSubscribed() {
198
-			continue
199
-		}
200
-		rows := v.collectedRows()
201
-		_, ok := w.startTimes[v]
202
-		if !ok {
203
-			w.startTimes[v] = now
204
-		}
205
-		// Make sure collector is never going
206
-		// to mutate the exported data.
207
-		rows = deepCopyRowData(rows)
208
-		viewData := &Data{
209
-			View:  v.view,
210
-			Start: w.startTimes[v],
211
-			End:   time.Now(),
212
-			Rows:  rows,
213
-		}
214
-		exportersMu.Lock()
215
-		for e := range exporters {
216
-			e.ExportView(viewData)
217
-		}
218
-		exportersMu.Unlock()
227
+		w.reportView(v, now)
219 228
 	}
220 229
 }
221 230
 
222
-func deepCopyRowData(rows []*Row) []*Row {
223
-	newRows := make([]*Row, 0, len(rows))
224
-	for _, r := range rows {
225
-		newRows = append(newRows, &Row{
226
-			Data: r.Data.clone(),
227
-			Tags: r.Tags,
228
-		})
231
+func (w *worker) toMetric(v *viewInternal, now time.Time) *metricdata.Metric {
232
+	if !v.isSubscribed() {
233
+		return nil
234
+	}
235
+
236
+	_, ok := w.startTimes[v]
237
+	if !ok {
238
+		w.startTimes[v] = now
239
+	}
240
+
241
+	var startTime time.Time
242
+	if v.metricDescriptor.Type == metricdata.TypeGaugeInt64 ||
243
+		v.metricDescriptor.Type == metricdata.TypeGaugeFloat64 {
244
+		startTime = time.Time{}
245
+	} else {
246
+		startTime = w.startTimes[v]
247
+	}
248
+
249
+	return viewToMetric(v, now, startTime)
250
+}
251
+
252
+// Read reads all view data and returns them as metrics.
253
+// It is typically invoked by metric reader to export stats in metric format.
254
+func (w *worker) Read() []*metricdata.Metric {
255
+	w.mu.Lock()
256
+	defer w.mu.Unlock()
257
+	now := time.Now()
258
+	metrics := make([]*metricdata.Metric, 0, len(w.views))
259
+	for _, v := range w.views {
260
+		metric := w.toMetric(v, now)
261
+		if metric != nil {
262
+			metrics = append(metrics, metric)
263
+		}
229 264
 	}
230
-	return newRows
265
+	return metrics
231 266
 }
... ...
@@ -56,6 +56,12 @@ type registerViewReq struct {
56 56
 }
57 57
 
58 58
 func (cmd *registerViewReq) handleCommand(w *worker) {
59
+	for _, v := range cmd.views {
60
+		if err := v.canonicalize(); err != nil {
61
+			cmd.err <- err
62
+			return
63
+		}
64
+	}
59 65
 	var errstr []string
60 66
 	for _, view := range cmd.views {
61 67
 		vi, err := w.tryRegisterView(view)
... ...
@@ -73,7 +79,7 @@ func (cmd *registerViewReq) handleCommand(w *worker) {
73 73
 	}
74 74
 }
75 75
 
76
-// unregisterFromViewReq is the command to unsubscribe to a view. Has no
76
+// unregisterFromViewReq is the command to unregister to a view. Has no
77 77
 // impact on the data collection for client that are pulling data from the
78 78
 // library.
79 79
 type unregisterFromViewReq struct {
... ...
@@ -88,13 +94,16 @@ func (cmd *unregisterFromViewReq) handleCommand(w *worker) {
88 88
 			continue
89 89
 		}
90 90
 
91
+		// Report pending data for this view before removing it.
92
+		w.reportView(vi, time.Now())
93
+
91 94
 		vi.unsubscribe()
92 95
 		if !vi.isSubscribed() {
93 96
 			// this was the last subscription and view is not collecting anymore.
94 97
 			// The collected data can be cleared.
95 98
 			vi.clearRows()
96 99
 		}
97
-		delete(w.views, name)
100
+		w.unregisterView(name)
98 101
 	}
99 102
 	cmd.done <- struct{}{}
100 103
 }
... ...
@@ -112,6 +121,8 @@ type retrieveDataResp struct {
112 112
 }
113 113
 
114 114
 func (cmd *retrieveDataReq) handleCommand(w *worker) {
115
+	w.mu.Lock()
116
+	defer w.mu.Unlock()
115 117
 	vi, ok := w.views[cmd.v]
116 118
 	if !ok {
117 119
 		cmd.c <- &retrieveDataResp{
... ...
@@ -137,24 +148,28 @@ func (cmd *retrieveDataReq) handleCommand(w *worker) {
137 137
 // recordReq is the command to record data related to multiple measures
138 138
 // at once.
139 139
 type recordReq struct {
140
-	tm *tag.Map
141
-	ms []stats.Measurement
140
+	tm          *tag.Map
141
+	ms          []stats.Measurement
142
+	attachments map[string]interface{}
143
+	t           time.Time
142 144
 }
143 145
 
144 146
 func (cmd *recordReq) handleCommand(w *worker) {
147
+	w.mu.Lock()
148
+	defer w.mu.Unlock()
145 149
 	for _, m := range cmd.ms {
146
-		if (m == stats.Measurement{}) { // not subscribed
150
+		if (m == stats.Measurement{}) { // not registered
147 151
 			continue
148 152
 		}
149 153
 		ref := w.getMeasureRef(m.Measure().Name())
150 154
 		for v := range ref.views {
151
-			v.addSample(cmd.tm, m.Value())
155
+			v.addSample(cmd.tm, m.Value(), cmd.attachments, time.Now())
152 156
 		}
153 157
 	}
154 158
 }
155 159
 
156 160
 // setReportingPeriodReq is the command to modify the duration between
157
-// reporting the collected data to the subscribed clients.
161
+// reporting the collected data to the registered clients.
158 162
 type setReportingPeriodReq struct {
159 163
 	d time.Duration
160 164
 	c chan bool
... ...
@@ -15,7 +15,9 @@
15 15
 
16 16
 package tag
17 17
 
18
-import "context"
18
+import (
19
+	"context"
20
+)
19 21
 
20 22
 // FromContext returns the tag map stored in the context.
21 23
 func FromContext(ctx context.Context) *Map {
... ...
@@ -21,7 +21,7 @@ type Key struct {
21 21
 }
22 22
 
23 23
 // NewKey creates or retrieves a string key identified by name.
24
-// Calling NewKey consequently with the same name returns the same key.
24
+// Calling NewKey more than once with the same name returns the same key.
25 25
 func NewKey(name string) (Key, error) {
26 26
 	if !checkKeyName(name) {
27 27
 		return Key{}, errInvalidKeyName
... ...
@@ -29,6 +29,15 @@ func NewKey(name string) (Key, error) {
29 29
 	return Key{name: name}, nil
30 30
 }
31 31
 
32
+// MustNewKey returns a key with the given name, and panics if name is an invalid key name.
33
+func MustNewKey(name string) Key {
34
+	k, err := NewKey(name)
35
+	if err != nil {
36
+		panic(err)
37
+	}
38
+	return k
39
+}
40
+
32 41
 // Name returns the name of the key.
33 42
 func (k Key) Name() string {
34 43
 	return k.name
... ...
@@ -28,10 +28,15 @@ type Tag struct {
28 28
 	Value string
29 29
 }
30 30
 
31
+type tagContent struct {
32
+	value string
33
+	m     metadatas
34
+}
35
+
31 36
 // Map is a map of tags. Use New to create a context containing
32 37
 // a new Map.
33 38
 type Map struct {
34
-	m map[Key]string
39
+	m map[Key]tagContent
35 40
 }
36 41
 
37 42
 // Value returns the value for the key if a value for the key exists.
... ...
@@ -40,7 +45,7 @@ func (m *Map) Value(k Key) (string, bool) {
40 40
 		return "", false
41 41
 	}
42 42
 	v, ok := m.m[k]
43
-	return v, ok
43
+	return v.value, ok
44 44
 }
45 45
 
46 46
 func (m *Map) String() string {
... ...
@@ -62,21 +67,21 @@ func (m *Map) String() string {
62 62
 	return buffer.String()
63 63
 }
64 64
 
65
-func (m *Map) insert(k Key, v string) {
65
+func (m *Map) insert(k Key, v string, md metadatas) {
66 66
 	if _, ok := m.m[k]; ok {
67 67
 		return
68 68
 	}
69
-	m.m[k] = v
69
+	m.m[k] = tagContent{value: v, m: md}
70 70
 }
71 71
 
72
-func (m *Map) update(k Key, v string) {
72
+func (m *Map) update(k Key, v string, md metadatas) {
73 73
 	if _, ok := m.m[k]; ok {
74
-		m.m[k] = v
74
+		m.m[k] = tagContent{value: v, m: md}
75 75
 	}
76 76
 }
77 77
 
78
-func (m *Map) upsert(k Key, v string) {
79
-	m.m[k] = v
78
+func (m *Map) upsert(k Key, v string, md metadatas) {
79
+	m.m[k] = tagContent{value: v, m: md}
80 80
 }
81 81
 
82 82
 func (m *Map) delete(k Key) {
... ...
@@ -84,7 +89,7 @@ func (m *Map) delete(k Key) {
84 84
 }
85 85
 
86 86
 func newMap() *Map {
87
-	return &Map{m: make(map[Key]string)}
87
+	return &Map{m: make(map[Key]tagContent)}
88 88
 }
89 89
 
90 90
 // Mutator modifies a tag map.
... ...
@@ -95,13 +100,17 @@ type Mutator interface {
95 95
 // Insert returns a mutator that inserts a
96 96
 // value associated with k. If k already exists in the tag map,
97 97
 // mutator doesn't update the value.
98
-func Insert(k Key, v string) Mutator {
98
+// Metadata applies metadata to the tag. It is optional.
99
+// Metadatas are applied in the order in which it is provided.
100
+// If more than one metadata updates the same attribute then
101
+// the update from the last metadata prevails.
102
+func Insert(k Key, v string, mds ...Metadata) Mutator {
99 103
 	return &mutator{
100 104
 		fn: func(m *Map) (*Map, error) {
101 105
 			if !checkValue(v) {
102 106
 				return nil, errInvalidValue
103 107
 			}
104
-			m.insert(k, v)
108
+			m.insert(k, v, createMetadatas(mds...))
105 109
 			return m, nil
106 110
 		},
107 111
 	}
... ...
@@ -110,13 +119,17 @@ func Insert(k Key, v string) Mutator {
110 110
 // Update returns a mutator that updates the
111 111
 // value of the tag associated with k with v. If k doesn't
112 112
 // exists in the tag map, the mutator doesn't insert the value.
113
-func Update(k Key, v string) Mutator {
113
+// Metadata applies metadata to the tag. It is optional.
114
+// Metadatas are applied in the order in which it is provided.
115
+// If more than one metadata updates the same attribute then
116
+// the update from the last metadata prevails.
117
+func Update(k Key, v string, mds ...Metadata) Mutator {
114 118
 	return &mutator{
115 119
 		fn: func(m *Map) (*Map, error) {
116 120
 			if !checkValue(v) {
117 121
 				return nil, errInvalidValue
118 122
 			}
119
-			m.update(k, v)
123
+			m.update(k, v, createMetadatas(mds...))
120 124
 			return m, nil
121 125
 		},
122 126
 	}
... ...
@@ -126,18 +139,37 @@ func Update(k Key, v string) Mutator {
126 126
 // value of the tag associated with k with v. It inserts the
127 127
 // value if k doesn't exist already. It mutates the value
128 128
 // if k already exists.
129
-func Upsert(k Key, v string) Mutator {
129
+// Metadata applies metadata to the tag. It is optional.
130
+// Metadatas are applied in the order in which it is provided.
131
+// If more than one metadata updates the same attribute then
132
+// the update from the last metadata prevails.
133
+func Upsert(k Key, v string, mds ...Metadata) Mutator {
130 134
 	return &mutator{
131 135
 		fn: func(m *Map) (*Map, error) {
132 136
 			if !checkValue(v) {
133 137
 				return nil, errInvalidValue
134 138
 			}
135
-			m.upsert(k, v)
139
+			m.upsert(k, v, createMetadatas(mds...))
136 140
 			return m, nil
137 141
 		},
138 142
 	}
139 143
 }
140 144
 
145
+func createMetadatas(mds ...Metadata) metadatas {
146
+	var metas metadatas
147
+	if len(mds) > 0 {
148
+		for _, md := range mds {
149
+			if md != nil {
150
+				md(&metas)
151
+			}
152
+		}
153
+	} else {
154
+		WithTTL(TTLUnlimitedPropagation)(&metas)
155
+	}
156
+	return metas
157
+
158
+}
159
+
141 160
 // Delete returns a mutator that deletes
142 161
 // the value associated with k.
143 162
 func Delete(k Key) Mutator {
... ...
@@ -160,10 +192,10 @@ func New(ctx context.Context, mutator ...Mutator) (context.Context, error) {
160 160
 			if !checkKeyName(k.Name()) {
161 161
 				return ctx, fmt.Errorf("key:%q: %v", k, errInvalidKeyName)
162 162
 			}
163
-			if !checkValue(v) {
163
+			if !checkValue(v.value) {
164 164
 				return ctx, fmt.Errorf("key:%q value:%q: %v", k.Name(), v, errInvalidValue)
165 165
 			}
166
-			m.insert(k, v)
166
+			m.insert(k, v.value, v.m)
167 167
 		}
168 168
 	}
169 169
 	var err error
... ...
@@ -162,14 +162,19 @@ func (eg *encoderGRPC) bytes() []byte {
162 162
 // Encode encodes the tag map into a []byte. It is useful to propagate
163 163
 // the tag maps on wire in binary format.
164 164
 func Encode(m *Map) []byte {
165
+	if m == nil {
166
+		return nil
167
+	}
165 168
 	eg := &encoderGRPC{
166 169
 		buf: make([]byte, len(m.m)),
167 170
 	}
168
-	eg.writeByte(byte(tagsVersionID))
171
+	eg.writeByte(tagsVersionID)
169 172
 	for k, v := range m.m {
170
-		eg.writeByte(byte(keyTypeString))
171
-		eg.writeStringWithVarintLen(k.name)
172
-		eg.writeBytesWithVarintLen([]byte(v))
173
+		if v.m.ttl.ttl == valueTTLUnlimitedPropagation {
174
+			eg.writeByte(byte(keyTypeString))
175
+			eg.writeStringWithVarintLen(k.name)
176
+			eg.writeBytesWithVarintLen([]byte(v.value))
177
+		}
173 178
 	}
174 179
 	return eg.bytes()
175 180
 }
... ...
@@ -177,45 +182,58 @@ func Encode(m *Map) []byte {
177 177
 // Decode decodes the given []byte into a tag map.
178 178
 func Decode(bytes []byte) (*Map, error) {
179 179
 	ts := newMap()
180
+	err := DecodeEach(bytes, ts.upsert)
181
+	if err != nil {
182
+		// no partial failures
183
+		return nil, err
184
+	}
185
+	return ts, nil
186
+}
180 187
 
188
+// DecodeEach decodes the given serialized tag map, calling handler for each
189
+// tag key and value decoded.
190
+func DecodeEach(bytes []byte, fn func(key Key, val string, md metadatas)) error {
181 191
 	eg := &encoderGRPC{
182 192
 		buf: bytes,
183 193
 	}
184 194
 	if len(eg.buf) == 0 {
185
-		return ts, nil
195
+		return nil
186 196
 	}
187 197
 
188 198
 	version := eg.readByte()
189 199
 	if version > tagsVersionID {
190
-		return nil, fmt.Errorf("cannot decode: unsupported version: %q; supports only up to: %q", version, tagsVersionID)
200
+		return fmt.Errorf("cannot decode: unsupported version: %q; supports only up to: %q", version, tagsVersionID)
191 201
 	}
192 202
 
193 203
 	for !eg.readEnded() {
194 204
 		typ := keyType(eg.readByte())
195 205
 
196 206
 		if typ != keyTypeString {
197
-			return nil, fmt.Errorf("cannot decode: invalid key type: %q", typ)
207
+			return fmt.Errorf("cannot decode: invalid key type: %q", typ)
198 208
 		}
199 209
 
200 210
 		k, err := eg.readBytesWithVarintLen()
201 211
 		if err != nil {
202
-			return nil, err
212
+			return err
203 213
 		}
204 214
 
205 215
 		v, err := eg.readBytesWithVarintLen()
206 216
 		if err != nil {
207
-			return nil, err
217
+			return err
208 218
 		}
209 219
 
210 220
 		key, err := NewKey(string(k))
211 221
 		if err != nil {
212
-			return nil, err // no partial failures
222
+			return err
213 223
 		}
214 224
 		val := string(v)
215 225
 		if !checkValue(val) {
216
-			return nil, errInvalidValue // no partial failures
226
+			return errInvalidValue
227
+		}
228
+		fn(key, val, createMetadatas(WithTTL(TTLUnlimitedPropagation)))
229
+		if err != nil {
230
+			return err
217 231
 		}
218
-		ts.upsert(key, val)
219 232
 	}
220
-	return ts, nil
233
+	return nil
221 234
 }
222 235
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+// Copyright 2019, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+//
14
+
15
+package tag
16
+
17
+const (
18
+	// valueTTLNoPropagation prevents tag from propagating.
19
+	valueTTLNoPropagation = 0
20
+
21
+	// valueTTLUnlimitedPropagation allows tag to propagate without any limits on number of hops.
22
+	valueTTLUnlimitedPropagation = -1
23
+)
24
+
25
+// TTL is metadata that specifies number of hops a tag can propagate.
26
+// Details about TTL metadata is specified at https://github.com/census-instrumentation/opencensus-specs/blob/master/tags/TagMap.md#tagmetadata
27
+type TTL struct {
28
+	ttl int
29
+}
30
+
31
+var (
32
+	// TTLUnlimitedPropagation is TTL metadata that allows tag to propagate without any limits on number of hops.
33
+	TTLUnlimitedPropagation = TTL{ttl: valueTTLUnlimitedPropagation}
34
+
35
+	// TTLNoPropagation is TTL metadata that prevents tag from propagating.
36
+	TTLNoPropagation = TTL{ttl: valueTTLNoPropagation}
37
+)
38
+
39
+type metadatas struct {
40
+	ttl TTL
41
+}
42
+
43
+// Metadata applies metadatas specified by the function.
44
+type Metadata func(*metadatas)
45
+
46
+// WithTTL applies metadata with provided ttl.
47
+func WithTTL(ttl TTL) Metadata {
48
+	return func(m *metadatas) {
49
+		m.ttl = ttl
50
+	}
51
+}
... ...
@@ -25,7 +25,7 @@ func do(ctx context.Context, f func(ctx context.Context)) {
25 25
 	m := FromContext(ctx)
26 26
 	keyvals := make([]string, 0, 2*len(m.m))
27 27
 	for k, v := range m.m {
28
-		keyvals = append(keyvals, k.Name(), v)
28
+		keyvals = append(keyvals, k.Name(), v.value)
29 29
 	}
30 30
 	pprof.Do(ctx, pprof.Labels(keyvals...), f)
31 31
 }
... ...
@@ -59,6 +59,11 @@ func Int64Attribute(key string, value int64) Attribute {
59 59
 	return Attribute{key: key, value: value}
60 60
 }
61 61
 
62
+// Float64Attribute returns a float64-valued attribute.
63
+func Float64Attribute(key string, value float64) Attribute {
64
+	return Attribute{key: key, value: value}
65
+}
66
+
62 67
 // StringAttribute returns a string-valued attribute.
63 68
 func StringAttribute(key string, value string) Attribute {
64 69
 	return Attribute{key: key, value: value}
... ...
@@ -71,8 +76,8 @@ type LinkType int32
71 71
 // LinkType values.
72 72
 const (
73 73
 	LinkTypeUnspecified LinkType = iota // The relationship of the two spans is unknown.
74
-	LinkTypeChild                       // The current span is a child of the linked span.
75
-	LinkTypeParent                      // The current span is the parent of the linked span.
74
+	LinkTypeChild                       // The linked span is a child of the current span.
75
+	LinkTypeParent                      // The linked span is the parent of the current span.
76 76
 )
77 77
 
78 78
 // Link represents a reference from one span to another span.
... ...
@@ -14,7 +14,11 @@
14 14
 
15 15
 package trace
16 16
 
17
-import "go.opencensus.io/trace/internal"
17
+import (
18
+	"sync"
19
+
20
+	"go.opencensus.io/trace/internal"
21
+)
18 22
 
19 23
 // Config represents the global tracing configuration.
20 24
 type Config struct {
... ...
@@ -23,12 +27,42 @@ type Config struct {
23 23
 
24 24
 	// IDGenerator is for internal use only.
25 25
 	IDGenerator internal.IDGenerator
26
+
27
+	// MaxAnnotationEventsPerSpan is max number of annotation events per span
28
+	MaxAnnotationEventsPerSpan int
29
+
30
+	// MaxMessageEventsPerSpan is max number of message events per span
31
+	MaxMessageEventsPerSpan int
32
+
33
+	// MaxAnnotationEventsPerSpan is max number of attributes per span
34
+	MaxAttributesPerSpan int
35
+
36
+	// MaxLinksPerSpan is max number of links per span
37
+	MaxLinksPerSpan int
26 38
 }
27 39
 
40
+var configWriteMu sync.Mutex
41
+
42
+const (
43
+	// DefaultMaxAnnotationEventsPerSpan is default max number of annotation events per span
44
+	DefaultMaxAnnotationEventsPerSpan = 32
45
+
46
+	// DefaultMaxMessageEventsPerSpan is default max number of message events per span
47
+	DefaultMaxMessageEventsPerSpan = 128
48
+
49
+	// DefaultMaxAttributesPerSpan is default max number of attributes per span
50
+	DefaultMaxAttributesPerSpan = 32
51
+
52
+	// DefaultMaxLinksPerSpan is default max number of links per span
53
+	DefaultMaxLinksPerSpan = 32
54
+)
55
+
28 56
 // ApplyConfig applies changes to the global tracing configuration.
29 57
 //
30 58
 // Fields not provided in the given config are going to be preserved.
31 59
 func ApplyConfig(cfg Config) {
60
+	configWriteMu.Lock()
61
+	defer configWriteMu.Unlock()
32 62
 	c := *config.Load().(*Config)
33 63
 	if cfg.DefaultSampler != nil {
34 64
 		c.DefaultSampler = cfg.DefaultSampler
... ...
@@ -36,5 +70,17 @@ func ApplyConfig(cfg Config) {
36 36
 	if cfg.IDGenerator != nil {
37 37
 		c.IDGenerator = cfg.IDGenerator
38 38
 	}
39
+	if cfg.MaxAnnotationEventsPerSpan > 0 {
40
+		c.MaxAnnotationEventsPerSpan = cfg.MaxAnnotationEventsPerSpan
41
+	}
42
+	if cfg.MaxMessageEventsPerSpan > 0 {
43
+		c.MaxMessageEventsPerSpan = cfg.MaxMessageEventsPerSpan
44
+	}
45
+	if cfg.MaxAttributesPerSpan > 0 {
46
+		c.MaxAttributesPerSpan = cfg.MaxAttributesPerSpan
47
+	}
48
+	if cfg.MaxLinksPerSpan > 0 {
49
+		c.MaxLinksPerSpan = cfg.MaxLinksPerSpan
50
+	}
39 51
 	config.Store(&c)
40 52
 }
... ...
@@ -32,6 +32,8 @@ to sample a subset of traces, or use AlwaysSample to collect a trace on every ru
32 32
 
33 33
     trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
34 34
 
35
+Be careful about using trace.AlwaysSample in a production application with
36
+significant traffic: a new trace will be started and exported for every request.
35 37
 
36 38
 Adding Spans to a Trace
37 39
 
... ...
@@ -42,7 +44,7 @@ It is common to want to capture all the activity of a function call in a span. F
42 42
 this to work, the function must take a context.Context as a parameter. Add these two
43 43
 lines to the top of the function:
44 44
 
45
-    ctx, span := trace.StartSpan(ctx, "my.org/Run")
45
+    ctx, span := trace.StartSpan(ctx, "example.com/Run")
46 46
     defer span.End()
47 47
 
48 48
 StartSpan will create a new top-level span if the context
49 49
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+// Copyright 2019, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package trace
15
+
16
+type evictedQueue struct {
17
+	queue        []interface{}
18
+	capacity     int
19
+	droppedCount int
20
+}
21
+
22
+func newEvictedQueue(capacity int) *evictedQueue {
23
+	eq := &evictedQueue{
24
+		capacity: capacity,
25
+		queue:    make([]interface{}, 0),
26
+	}
27
+
28
+	return eq
29
+}
30
+
31
+func (eq *evictedQueue) add(value interface{}) {
32
+	if len(eq.queue) == eq.capacity {
33
+		eq.queue = eq.queue[1:]
34
+		eq.droppedCount++
35
+	}
36
+	eq.queue = append(eq.queue, value)
37
+}
... ...
@@ -16,6 +16,7 @@ package trace
16 16
 
17 17
 import (
18 18
 	"sync"
19
+	"sync/atomic"
19 20
 	"time"
20 21
 )
21 22
 
... ...
@@ -30,9 +31,11 @@ type Exporter interface {
30 30
 	ExportSpan(s *SpanData)
31 31
 }
32 32
 
33
+type exportersMap map[Exporter]struct{}
34
+
33 35
 var (
34
-	exportersMu sync.Mutex
35
-	exporters   map[Exporter]struct{}
36
+	exporterMu sync.Mutex
37
+	exporters  atomic.Value
36 38
 )
37 39
 
38 40
 // RegisterExporter adds to the list of Exporters that will receive sampled
... ...
@@ -40,20 +43,31 @@ var (
40 40
 //
41 41
 // Binaries can register exporters, libraries shouldn't register exporters.
42 42
 func RegisterExporter(e Exporter) {
43
-	exportersMu.Lock()
44
-	if exporters == nil {
45
-		exporters = make(map[Exporter]struct{})
43
+	exporterMu.Lock()
44
+	new := make(exportersMap)
45
+	if old, ok := exporters.Load().(exportersMap); ok {
46
+		for k, v := range old {
47
+			new[k] = v
48
+		}
46 49
 	}
47
-	exporters[e] = struct{}{}
48
-	exportersMu.Unlock()
50
+	new[e] = struct{}{}
51
+	exporters.Store(new)
52
+	exporterMu.Unlock()
49 53
 }
50 54
 
51 55
 // UnregisterExporter removes from the list of Exporters the Exporter that was
52 56
 // registered with the given name.
53 57
 func UnregisterExporter(e Exporter) {
54
-	exportersMu.Lock()
55
-	delete(exporters, e)
56
-	exportersMu.Unlock()
58
+	exporterMu.Lock()
59
+	new := make(exportersMap)
60
+	if old, ok := exporters.Load().(exportersMap); ok {
61
+		for k, v := range old {
62
+			new[k] = v
63
+		}
64
+	}
65
+	delete(new, e)
66
+	exporters.Store(new)
67
+	exporterMu.Unlock()
57 68
 }
58 69
 
59 70
 // SpanData contains all the information collected by a Span.
... ...
@@ -71,6 +85,13 @@ type SpanData struct {
71 71
 	Annotations   []Annotation
72 72
 	MessageEvents []MessageEvent
73 73
 	Status
74
-	Links           []Link
75
-	HasRemoteParent bool
74
+	Links                    []Link
75
+	HasRemoteParent          bool
76
+	DroppedAttributeCount    int
77
+	DroppedAnnotationCount   int
78
+	DroppedMessageEventCount int
79
+	DroppedLinkCount         int
80
+
81
+	// ChildSpanCount holds the number of child span created for this span.
82
+	ChildSpanCount int
76 83
 }
... ...
@@ -15,6 +15,7 @@
15 15
 // Package internal provides trace internals.
16 16
 package internal
17 17
 
18
+// IDGenerator allows custom generators for TraceId and SpanId.
18 19
 type IDGenerator interface {
19 20
 	NewTraceID() [16]byte
20 21
 	NewSpanID() [8]byte
21 22
new file mode 100644
... ...
@@ -0,0 +1,61 @@
0
+// Copyright 2019, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+package trace
15
+
16
+import (
17
+	"github.com/golang/groupcache/lru"
18
+)
19
+
20
+// A simple lru.Cache wrapper that tracks the keys of the current contents and
21
+// the cumulative number of evicted items.
22
+type lruMap struct {
23
+	cacheKeys    map[lru.Key]bool
24
+	cache        *lru.Cache
25
+	droppedCount int
26
+}
27
+
28
+func newLruMap(size int) *lruMap {
29
+	lm := &lruMap{
30
+		cacheKeys:    make(map[lru.Key]bool),
31
+		cache:        lru.New(size),
32
+		droppedCount: 0,
33
+	}
34
+	lm.cache.OnEvicted = func(key lru.Key, value interface{}) {
35
+		delete(lm.cacheKeys, key)
36
+		lm.droppedCount++
37
+	}
38
+	return lm
39
+}
40
+
41
+func (lm lruMap) len() int {
42
+	return lm.cache.Len()
43
+}
44
+
45
+func (lm lruMap) keys() []interface{} {
46
+	keys := []interface{}{}
47
+	for k := range lm.cacheKeys {
48
+		keys = append(keys, k)
49
+	}
50
+	return keys
51
+}
52
+
53
+func (lm *lruMap) add(key, value interface{}) {
54
+	lm.cacheKeys[lru.Key(key)] = true
55
+	lm.cache.Add(lru.Key(key), value)
56
+}
57
+
58
+func (lm *lruMap) get(key interface{}) (interface{}, bool) {
59
+	return lm.cache.Get(key)
60
+}
... ...
@@ -20,10 +20,6 @@ import (
20 20
 
21 21
 const defaultSamplingProbability = 1e-4
22 22
 
23
-func newDefaultSampler() Sampler {
24
-	return ProbabilitySampler(defaultSamplingProbability)
25
-}
26
-
27 23
 // Sampler decides whether a trace should be sampled and exported.
28 24
 type Sampler func(SamplingParameters) SamplingDecision
29 25
 
... ...
@@ -62,6 +58,9 @@ func ProbabilitySampler(fraction float64) Sampler {
62 62
 }
63 63
 
64 64
 // AlwaysSample returns a Sampler that samples every trace.
65
+// Be careful about using this sampler in a production application with
66
+// significant traffic: a new trace will be started and exported for every
67
+// request.
65 68
 func AlwaysSample() Sampler {
66 69
 	return func(p SamplingParameters) SamplingDecision {
67 70
 		return SamplingDecision{Sample: true}
... ...
@@ -25,6 +25,7 @@ import (
25 25
 	"time"
26 26
 
27 27
 	"go.opencensus.io/internal"
28
+	"go.opencensus.io/trace/tracestate"
28 29
 )
29 30
 
30 31
 // Span represents a span of a trace.  It has an associated SpanContext, and
... ...
@@ -41,6 +42,20 @@ type Span struct {
41 41
 	data        *SpanData
42 42
 	mu          sync.Mutex // protects the contents of *data (but not the pointer value.)
43 43
 	spanContext SpanContext
44
+
45
+	// lruAttributes are capped at configured limit. When the capacity is reached an oldest entry
46
+	// is removed to create room for a new entry.
47
+	lruAttributes *lruMap
48
+
49
+	// annotations are stored in FIFO queue capped by configured limit.
50
+	annotations *evictedQueue
51
+
52
+	// messageEvents are stored in FIFO queue capped by configured limit.
53
+	messageEvents *evictedQueue
54
+
55
+	// links are stored in FIFO queue capped by configured limit.
56
+	links *evictedQueue
57
+
44 58
 	// spanStore is the spanStore this span belongs to, if any, otherwise it is nil.
45 59
 	*spanStore
46 60
 	endOnce sync.Once
... ...
@@ -88,6 +103,7 @@ type SpanContext struct {
88 88
 	TraceID      TraceID
89 89
 	SpanID       SpanID
90 90
 	TraceOptions TraceOptions
91
+	Tracestate   *tracestate.Tracestate
91 92
 }
92 93
 
93 94
 type contextKey struct{}
... ...
@@ -98,13 +114,6 @@ func FromContext(ctx context.Context) *Span {
98 98
 	return s
99 99
 }
100 100
 
101
-// WithSpan returns a new context with the given Span attached.
102
-//
103
-// Deprecated: Use NewContext.
104
-func WithSpan(parent context.Context, s *Span) context.Context {
105
-	return NewContext(parent, s)
106
-}
107
-
108 101
 // NewContext returns a new context with the given Span attached.
109 102
 func NewContext(parent context.Context, s *Span) context.Context {
110 103
 	return context.WithValue(parent, contextKey{}, s)
... ...
@@ -154,10 +163,14 @@ func WithSampler(sampler Sampler) StartOption {
154 154
 
155 155
 // StartSpan starts a new child span of the current span in the context. If
156 156
 // there is no span in the context, creates a new trace and span.
157
+//
158
+// Returned context contains the newly created span. You can use it to
159
+// propagate the returned span in process.
157 160
 func StartSpan(ctx context.Context, name string, o ...StartOption) (context.Context, *Span) {
158 161
 	var opts StartOptions
159 162
 	var parent SpanContext
160 163
 	if p := FromContext(ctx); p != nil {
164
+		p.addChild()
161 165
 		parent = p.spanContext
162 166
 	}
163 167
 	for _, op := range o {
... ...
@@ -174,6 +187,9 @@ func StartSpan(ctx context.Context, name string, o ...StartOption) (context.Cont
174 174
 //
175 175
 // If the incoming context contains a parent, it ignores. StartSpanWithRemoteParent is
176 176
 // preferred for cases where the parent is propagated via an incoming request.
177
+//
178
+// Returned context contains the newly created span. You can use it to
179
+// propagate the returned span in process.
177 180
 func StartSpanWithRemoteParent(ctx context.Context, name string, parent SpanContext, o ...StartOption) (context.Context, *Span) {
178 181
 	var opts StartOptions
179 182
 	for _, op := range o {
... ...
@@ -185,26 +201,6 @@ func StartSpanWithRemoteParent(ctx context.Context, name string, parent SpanCont
185 185
 	return NewContext(ctx, span), span
186 186
 }
187 187
 
188
-// NewSpan returns a new span.
189
-//
190
-// If parent is not nil, created span will be a child of the parent.
191
-//
192
-// Deprecated: Use StartSpan.
193
-func NewSpan(name string, parent *Span, o StartOptions) *Span {
194
-	var parentSpanContext SpanContext
195
-	if parent != nil {
196
-		parentSpanContext = parent.SpanContext()
197
-	}
198
-	return startSpanInternal(name, parent != nil, parentSpanContext, false, o)
199
-}
200
-
201
-// NewSpanWithRemoteParent returns a new span with the given parent SpanContext.
202
-//
203
-// Deprecated: Use StartSpanWithRemoteParent.
204
-func NewSpanWithRemoteParent(name string, parent SpanContext, o StartOptions) *Span {
205
-	return startSpanInternal(name, true, parent, true, o)
206
-}
207
-
208 188
 func startSpanInternal(name string, hasParent bool, parent SpanContext, remoteParent bool, o StartOptions) *Span {
209 189
 	span := &Span{}
210 190
 	span.spanContext = parent
... ...
@@ -245,6 +241,11 @@ func startSpanInternal(name string, hasParent bool, parent SpanContext, remotePa
245 245
 		Name:            name,
246 246
 		HasRemoteParent: remoteParent,
247 247
 	}
248
+	span.lruAttributes = newLruMap(cfg.MaxAttributesPerSpan)
249
+	span.annotations = newEvictedQueue(cfg.MaxAnnotationEventsPerSpan)
250
+	span.messageEvents = newEvictedQueue(cfg.MaxMessageEventsPerSpan)
251
+	span.links = newEvictedQueue(cfg.MaxLinksPerSpan)
252
+
248 253
 	if hasParent {
249 254
 		span.data.ParentSpanID = parent.SpanID
250 255
 	}
... ...
@@ -262,26 +263,29 @@ func startSpanInternal(name string, hasParent bool, parent SpanContext, remotePa
262 262
 
263 263
 // End ends the span.
264 264
 func (s *Span) End() {
265
+	if s == nil {
266
+		return
267
+	}
268
+	if s.executionTracerTaskEnd != nil {
269
+		s.executionTracerTaskEnd()
270
+	}
265 271
 	if !s.IsRecordingEvents() {
266 272
 		return
267 273
 	}
268 274
 	s.endOnce.Do(func() {
269
-		if s.executionTracerTaskEnd != nil {
270
-			s.executionTracerTaskEnd()
271
-		}
272
-		// TODO: optimize to avoid this call if sd won't be used.
273
-		sd := s.makeSpanData()
274
-		sd.EndTime = internal.MonotonicEndTime(sd.StartTime)
275
-		if s.spanStore != nil {
276
-			s.spanStore.finished(s, sd)
277
-		}
278
-		if s.spanContext.IsSampled() {
279
-			// TODO: consider holding exportersMu for less time.
280
-			exportersMu.Lock()
281
-			for e := range exporters {
282
-				e.ExportSpan(sd)
275
+		exp, _ := exporters.Load().(exportersMap)
276
+		mustExport := s.spanContext.IsSampled() && len(exp) > 0
277
+		if s.spanStore != nil || mustExport {
278
+			sd := s.makeSpanData()
279
+			sd.EndTime = internal.MonotonicEndTime(sd.StartTime)
280
+			if s.spanStore != nil {
281
+				s.spanStore.finished(s, sd)
282
+			}
283
+			if mustExport {
284
+				for e := range exp {
285
+					e.ExportSpan(sd)
286
+				}
283 287
 			}
284
-			exportersMu.Unlock()
285 288
 		}
286 289
 	})
287 290
 }
... ...
@@ -292,11 +296,21 @@ func (s *Span) makeSpanData() *SpanData {
292 292
 	var sd SpanData
293 293
 	s.mu.Lock()
294 294
 	sd = *s.data
295
-	if s.data.Attributes != nil {
296
-		sd.Attributes = make(map[string]interface{})
297
-		for k, v := range s.data.Attributes {
298
-			sd.Attributes[k] = v
299
-		}
295
+	if s.lruAttributes.len() > 0 {
296
+		sd.Attributes = s.lruAttributesToAttributeMap()
297
+		sd.DroppedAttributeCount = s.lruAttributes.droppedCount
298
+	}
299
+	if len(s.annotations.queue) > 0 {
300
+		sd.Annotations = s.interfaceArrayToAnnotationArray()
301
+		sd.DroppedAnnotationCount = s.annotations.droppedCount
302
+	}
303
+	if len(s.messageEvents.queue) > 0 {
304
+		sd.MessageEvents = s.interfaceArrayToMessageEventArray()
305
+		sd.DroppedMessageEventCount = s.messageEvents.droppedCount
306
+	}
307
+	if len(s.links.queue) > 0 {
308
+		sd.Links = s.interfaceArrayToLinksArray()
309
+		sd.DroppedLinkCount = s.links.droppedCount
300 310
 	}
301 311
 	s.mu.Unlock()
302 312
 	return &sd
... ...
@@ -310,6 +324,16 @@ func (s *Span) SpanContext() SpanContext {
310 310
 	return s.spanContext
311 311
 }
312 312
 
313
+// SetName sets the name of the span, if it is recording events.
314
+func (s *Span) SetName(name string) {
315
+	if !s.IsRecordingEvents() {
316
+		return
317
+	}
318
+	s.mu.Lock()
319
+	s.data.Name = name
320
+	s.mu.Unlock()
321
+}
322
+
313 323
 // SetStatus sets the status of the span, if it is recording events.
314 324
 func (s *Span) SetStatus(status Status) {
315 325
 	if !s.IsRecordingEvents() {
... ...
@@ -320,6 +344,57 @@ func (s *Span) SetStatus(status Status) {
320 320
 	s.mu.Unlock()
321 321
 }
322 322
 
323
+func (s *Span) interfaceArrayToLinksArray() []Link {
324
+	linksArr := make([]Link, 0)
325
+	for _, value := range s.links.queue {
326
+		linksArr = append(linksArr, value.(Link))
327
+	}
328
+	return linksArr
329
+}
330
+
331
+func (s *Span) interfaceArrayToMessageEventArray() []MessageEvent {
332
+	messageEventArr := make([]MessageEvent, 0)
333
+	for _, value := range s.messageEvents.queue {
334
+		messageEventArr = append(messageEventArr, value.(MessageEvent))
335
+	}
336
+	return messageEventArr
337
+}
338
+
339
+func (s *Span) interfaceArrayToAnnotationArray() []Annotation {
340
+	annotationArr := make([]Annotation, 0)
341
+	for _, value := range s.annotations.queue {
342
+		annotationArr = append(annotationArr, value.(Annotation))
343
+	}
344
+	return annotationArr
345
+}
346
+
347
+func (s *Span) lruAttributesToAttributeMap() map[string]interface{} {
348
+	attributes := make(map[string]interface{})
349
+	for _, key := range s.lruAttributes.keys() {
350
+		value, ok := s.lruAttributes.get(key)
351
+		if ok {
352
+			keyStr := key.(string)
353
+			attributes[keyStr] = value
354
+		}
355
+	}
356
+	return attributes
357
+}
358
+
359
+func (s *Span) copyToCappedAttributes(attributes []Attribute) {
360
+	for _, a := range attributes {
361
+		s.lruAttributes.add(a.key, a.value)
362
+	}
363
+}
364
+
365
+func (s *Span) addChild() {
366
+	if !s.IsRecordingEvents() {
367
+		return
368
+	}
369
+	s.mu.Lock()
370
+	s.data.ChildSpanCount++
371
+	s.mu.Unlock()
372
+}
373
+
323 374
 // AddAttributes sets attributes in the span.
324 375
 //
325 376
 // Existing attributes whose keys appear in the attributes parameter are overwritten.
... ...
@@ -328,10 +403,7 @@ func (s *Span) AddAttributes(attributes ...Attribute) {
328 328
 		return
329 329
 	}
330 330
 	s.mu.Lock()
331
-	if s.data.Attributes == nil {
332
-		s.data.Attributes = make(map[string]interface{})
333
-	}
334
-	copyAttributes(s.data.Attributes, attributes)
331
+	s.copyToCappedAttributes(attributes)
335 332
 	s.mu.Unlock()
336 333
 }
337 334
 
... ...
@@ -351,7 +423,7 @@ func (s *Span) lazyPrintfInternal(attributes []Attribute, format string, a ...in
351 351
 		m = make(map[string]interface{})
352 352
 		copyAttributes(m, attributes)
353 353
 	}
354
-	s.data.Annotations = append(s.data.Annotations, Annotation{
354
+	s.annotations.add(Annotation{
355 355
 		Time:       now,
356 356
 		Message:    msg,
357 357
 		Attributes: m,
... ...
@@ -367,7 +439,7 @@ func (s *Span) printStringInternal(attributes []Attribute, str string) {
367 367
 		a = make(map[string]interface{})
368 368
 		copyAttributes(a, attributes)
369 369
 	}
370
-	s.data.Annotations = append(s.data.Annotations, Annotation{
370
+	s.annotations.add(Annotation{
371 371
 		Time:       now,
372 372
 		Message:    str,
373 373
 		Attributes: a,
... ...
@@ -404,7 +476,7 @@ func (s *Span) AddMessageSendEvent(messageID, uncompressedByteSize, compressedBy
404 404
 	}
405 405
 	now := time.Now()
406 406
 	s.mu.Lock()
407
-	s.data.MessageEvents = append(s.data.MessageEvents, MessageEvent{
407
+	s.messageEvents.add(MessageEvent{
408 408
 		Time:                 now,
409 409
 		EventType:            MessageEventTypeSent,
410 410
 		MessageID:            messageID,
... ...
@@ -426,7 +498,7 @@ func (s *Span) AddMessageReceiveEvent(messageID, uncompressedByteSize, compresse
426 426
 	}
427 427
 	now := time.Now()
428 428
 	s.mu.Lock()
429
-	s.data.MessageEvents = append(s.data.MessageEvents, MessageEvent{
429
+	s.messageEvents.add(MessageEvent{
430 430
 		Time:                 now,
431 431
 		EventType:            MessageEventTypeRecv,
432 432
 		MessageID:            messageID,
... ...
@@ -442,7 +514,7 @@ func (s *Span) AddLink(l Link) {
442 442
 		return
443 443
 	}
444 444
 	s.mu.Lock()
445
-	s.data.Links = append(s.data.Links, l)
445
+	s.links.add(l)
446 446
 	s.mu.Unlock()
447 447
 }
448 448
 
... ...
@@ -474,29 +546,39 @@ func init() {
474 474
 	gen.spanIDInc |= 1
475 475
 
476 476
 	config.Store(&Config{
477
-		DefaultSampler: ProbabilitySampler(defaultSamplingProbability),
478
-		IDGenerator:    gen,
477
+		DefaultSampler:             ProbabilitySampler(defaultSamplingProbability),
478
+		IDGenerator:                gen,
479
+		MaxAttributesPerSpan:       DefaultMaxAttributesPerSpan,
480
+		MaxAnnotationEventsPerSpan: DefaultMaxAnnotationEventsPerSpan,
481
+		MaxMessageEventsPerSpan:    DefaultMaxMessageEventsPerSpan,
482
+		MaxLinksPerSpan:            DefaultMaxLinksPerSpan,
479 483
 	})
480 484
 }
481 485
 
482 486
 type defaultIDGenerator struct {
483 487
 	sync.Mutex
484
-	traceIDRand *rand.Rand
488
+
489
+	// Please keep these as the first fields
490
+	// so that these 8 byte fields will be aligned on addresses
491
+	// divisible by 8, on both 32-bit and 64-bit machines when
492
+	// performing atomic increments and accesses.
493
+	// See:
494
+	// * https://github.com/census-instrumentation/opencensus-go/issues/587
495
+	// * https://github.com/census-instrumentation/opencensus-go/issues/865
496
+	// * https://golang.org/pkg/sync/atomic/#pkg-note-BUG
497
+	nextSpanID uint64
498
+	spanIDInc  uint64
499
+
485 500
 	traceIDAdd  [2]uint64
486
-	nextSpanID  uint64
487
-	spanIDInc   uint64
501
+	traceIDRand *rand.Rand
488 502
 }
489 503
 
490 504
 // NewSpanID returns a non-zero span ID from a randomly-chosen sequence.
491
-// mu should be held while this function is called.
492 505
 func (gen *defaultIDGenerator) NewSpanID() [8]byte {
493
-	gen.Lock()
494
-	id := gen.nextSpanID
495
-	gen.nextSpanID += gen.spanIDInc
496
-	if gen.nextSpanID == 0 {
497
-		gen.nextSpanID += gen.spanIDInc
506
+	var id uint64
507
+	for id == 0 {
508
+		id = atomic.AddUint64(&gen.nextSpanID, gen.spanIDInc)
498 509
 	}
499
-	gen.Unlock()
500 510
 	var sid [8]byte
501 511
 	binary.LittleEndian.PutUint64(sid[:], id)
502 512
 	return sid
503 513
new file mode 100644
... ...
@@ -0,0 +1,147 @@
0
+// Copyright 2018, OpenCensus Authors
1
+//
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+//     http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
14
+// Package tracestate implements support for the Tracestate header of the
15
+// W3C TraceContext propagation format.
16
+package tracestate
17
+
18
+import (
19
+	"fmt"
20
+	"regexp"
21
+)
22
+
23
+const (
24
+	keyMaxSize       = 256
25
+	valueMaxSize     = 256
26
+	maxKeyValuePairs = 32
27
+)
28
+
29
+const (
30
+	keyWithoutVendorFormat = `[a-z][_0-9a-z\-\*\/]{0,255}`
31
+	keyWithVendorFormat    = `[a-z][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}`
32
+	keyFormat              = `(` + keyWithoutVendorFormat + `)|(` + keyWithVendorFormat + `)`
33
+	valueFormat            = `[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]`
34
+)
35
+
36
+var keyValidationRegExp = regexp.MustCompile(`^(` + keyFormat + `)$`)
37
+var valueValidationRegExp = regexp.MustCompile(`^(` + valueFormat + `)$`)
38
+
39
+// Tracestate represents tracing-system specific context in a list of key-value pairs. Tracestate allows different
40
+// vendors propagate additional information and inter-operate with their legacy Id formats.
41
+type Tracestate struct {
42
+	entries []Entry
43
+}
44
+
45
+// Entry represents one key-value pair in a list of key-value pair of Tracestate.
46
+type Entry struct {
47
+	// Key is an opaque string up to 256 characters printable. It MUST begin with a lowercase letter,
48
+	// and can only contain lowercase letters a-z, digits 0-9, underscores _, dashes -, asterisks *, and
49
+	// forward slashes /.
50
+	Key string
51
+
52
+	// Value is an opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the
53
+	// range 0x20 to 0x7E) except comma , and =.
54
+	Value string
55
+}
56
+
57
+// Entries returns a slice of Entry.
58
+func (ts *Tracestate) Entries() []Entry {
59
+	if ts == nil {
60
+		return nil
61
+	}
62
+	return ts.entries
63
+}
64
+
65
+func (ts *Tracestate) remove(key string) *Entry {
66
+	for index, entry := range ts.entries {
67
+		if entry.Key == key {
68
+			ts.entries = append(ts.entries[:index], ts.entries[index+1:]...)
69
+			return &entry
70
+		}
71
+	}
72
+	return nil
73
+}
74
+
75
+func (ts *Tracestate) add(entries []Entry) error {
76
+	for _, entry := range entries {
77
+		ts.remove(entry.Key)
78
+	}
79
+	if len(ts.entries)+len(entries) > maxKeyValuePairs {
80
+		return fmt.Errorf("adding %d key-value pairs to current %d pairs exceeds the limit of %d",
81
+			len(entries), len(ts.entries), maxKeyValuePairs)
82
+	}
83
+	ts.entries = append(entries, ts.entries...)
84
+	return nil
85
+}
86
+
87
+func isValid(entry Entry) bool {
88
+	return keyValidationRegExp.MatchString(entry.Key) &&
89
+		valueValidationRegExp.MatchString(entry.Value)
90
+}
91
+
92
+func containsDuplicateKey(entries ...Entry) (string, bool) {
93
+	keyMap := make(map[string]int)
94
+	for _, entry := range entries {
95
+		if _, ok := keyMap[entry.Key]; ok {
96
+			return entry.Key, true
97
+		}
98
+		keyMap[entry.Key] = 1
99
+	}
100
+	return "", false
101
+}
102
+
103
+func areEntriesValid(entries ...Entry) (*Entry, bool) {
104
+	for _, entry := range entries {
105
+		if !isValid(entry) {
106
+			return &entry, false
107
+		}
108
+	}
109
+	return nil, true
110
+}
111
+
112
+// New creates a Tracestate object from a parent and/or entries (key-value pair).
113
+// Entries from the parent are copied if present. The entries passed to this function
114
+// are inserted in front of those copied from the parent. If an entry copied from the
115
+// parent contains the same key as one of the entry in entries then the entry copied
116
+// from the parent is removed. See add func.
117
+//
118
+// An error is returned with nil Tracestate if
119
+//  1. one or more entry in entries is invalid.
120
+//  2. two or more entries in the input entries have the same key.
121
+//  3. the number of entries combined from the parent and the input entries exceeds maxKeyValuePairs.
122
+//     (duplicate entry is counted only once).
123
+func New(parent *Tracestate, entries ...Entry) (*Tracestate, error) {
124
+	if parent == nil && len(entries) == 0 {
125
+		return nil, nil
126
+	}
127
+	if entry, ok := areEntriesValid(entries...); !ok {
128
+		return nil, fmt.Errorf("key-value pair {%s, %s} is invalid", entry.Key, entry.Value)
129
+	}
130
+
131
+	if key, duplicate := containsDuplicateKey(entries...); duplicate {
132
+		return nil, fmt.Errorf("contains duplicate keys (%s)", key)
133
+	}
134
+
135
+	tracestate := Tracestate{}
136
+
137
+	if parent != nil && len(parent.entries) > 0 {
138
+		tracestate.entries = append([]Entry{}, parent.entries...)
139
+	}
140
+
141
+	err := tracestate.add(entries)
142
+	if err != nil {
143
+		return nil, err
144
+	}
145
+	return &tracestate, nil
146
+}