Repository: census-instrumentation/opencensus-go
Pull request #1069 (2019-03-18 12:48:16)

ochttp plugin: makes Body a transparent wrapper
With Go 1.12 the body of http.Response can implement `io.Writer`.
https://tip.golang.org/pkg/net/http/#Response.Body

Upgrading to websocket for example will result with a body implementing `io.Writer`.
https://golang.org/src/net/http/httputil/reverseproxy.go#L521

I followed the logic of wrappedResponseWriter, it might be overkill as Response.Body has only one optional interface for now.

Both `ochttp.tracker` and `ochttp.bodyTracker` are affected.


Patch:
diff --git a/exemplar/exemplar.go b/exemplar/exemplar.go
deleted file mode 100644
index acc225a..0000000
--- a/exemplar/exemplar.go
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2018, OpenCensus Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package exemplar implements support for exemplars. Exemplars are additional
-// data associated with each measurement.
-//
-// Their purpose it to provide an example of the kind of thing
-// (request, RPC, trace span, etc.) that resulted in that measurement.
-package exemplar
-
-import (
-	"context"
-	"time"
-)
-
-// Exemplars keys.
-const (
-	KeyTraceID   = "trace_id"
-	KeySpanID    = "span_id"
-	KeyPrefixTag = "tag:"
-)
-
-// Exemplar is an example data point associated with each bucket of a
-// distribution type aggregation.
-type Exemplar struct {
-	Value       float64     // the value that was recorded
-	Timestamp   time.Time   // the time the value was recorded
-	Attachments Attachments // attachments (if any)
-}
-
-// Attachments is a map of extra values associated with a recorded data point.
-// The map should only be mutated from AttachmentExtractor functions.
-type Attachments map[string]string
-
-// AttachmentExtractor is a function capable of extracting exemplar attachments
-// from the context used to record measurements.
-// The map passed to the function should be mutated and returned. It will
-// initially be nil: the first AttachmentExtractor that would like to add keys to the
-// map is responsible for initializing it.
-type AttachmentExtractor func(ctx context.Context, a Attachments) Attachments
-
-var extractors []AttachmentExtractor
-
-// RegisterAttachmentExtractor registers the given extractor associated with the exemplar
-// type name.
-//
-// Extractors will be used to attempt to extract exemplars from the context
-// associated with each recorded measurement.
-//
-// Packages that support exemplars should register their extractor functions on
-// initialization.
-//
-// RegisterAttachmentExtractor should not be called after any measurements have
-// been recorded.
-func RegisterAttachmentExtractor(e AttachmentExtractor) {
-	extractors = append(extractors, e)
-}
-
-// AttachmentsFromContext extracts exemplars from the given context.
-// Each registered AttachmentExtractor (see RegisterAttachmentExtractor) is called in an
-// unspecified order to add attachments to the exemplar.
-func AttachmentsFromContext(ctx context.Context) Attachments {
-	var a Attachments
-	for _, extractor := range extractors {
-		a = extractor(ctx, a)
-	}
-	return a
-}
diff --git a/trace/exemplar.go b/metric/metricdata/exemplar.go
similarity index 51%
rename from trace/exemplar.go
rename to metric/metricdata/exemplar.go
index 416d805..f51e33f 100644
--- a/trace/exemplar.go
+++ b/metric/metricdata/exemplar.go
@@ -12,32 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package trace
+package metricdata
 
 import (
-	"context"
-	"encoding/hex"
-
-	"go.opencensus.io/exemplar"
+	"time"
 )
 
-func init() {
-	exemplar.RegisterAttachmentExtractor(attachSpanContext)
+// Exemplar is an example data point associated with each bucket of a
+// distribution type aggregation.
+//
+// Their purpose is to provide an example of the kind of thing
+// (request, RPC, trace span, etc.) that resulted in that measurement.
+type Exemplar struct {
+	Value       float64     // the value that was recorded
+	Timestamp   time.Time   // the time the value was recorded
+	Attachments Attachments // attachments (if any)
 }
 
-func attachSpanContext(ctx context.Context, a exemplar.Attachments) exemplar.Attachments {
-	span := FromContext(ctx)
-	if span == nil {
-		return a
-	}
-	sc := span.SpanContext()
-	if !sc.IsSampled() {
-		return a
-	}
-	if a == nil {
-		a = make(exemplar.Attachments)
-	}
-	a[exemplar.KeyTraceID] = hex.EncodeToString(sc.TraceID[:])
-	a[exemplar.KeySpanID] = hex.EncodeToString(sc.SpanID[:])
-	return a
-}
+// Attachments is a map of extra values associated with a recorded data point.
+type Attachments map[string]string
diff --git a/metric/metricdata/point.go b/metric/metricdata/point.go
index 2c2a871..7fe057b 100644
--- a/metric/metricdata/point.go
+++ b/metric/metricdata/point.go
@@ -16,8 +16,6 @@ package metricdata
 
 import (
 	"time"
-
-	"go.opencensus.io/exemplar"
 )
 
 // Point is a single data point of a time series.
@@ -144,7 +142,7 @@ type Bucket struct {
 	// bucket_bounds.
 	Count int64
 	// Exemplar associated with this bucket (if any).
-	Exemplar *exemplar.Exemplar
+	Exemplar *Exemplar
 }
 
 // Summary is a representation of percentiles.
diff --git a/plugin/ochttp/client_stats.go b/plugin/ochttp/client_stats.go
index e258bcc..17142aa 100644
--- a/plugin/ochttp/client_stats.go
+++ b/plugin/ochttp/client_stats.go
@@ -68,7 +68,7 @@ func (t statsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
 			track.end()
 		} else {
 			track.body = resp.Body
-			resp.Body = track
+			resp.Body = wrappedBody(track, resp.Body)
 		}
 	}
 	return resp, err
diff --git a/plugin/ochttp/trace.go b/plugin/ochttp/trace.go
index ca312fc..fdf65fc 100644
--- a/plugin/ochttp/trace.go
+++ b/plugin/ochttp/trace.go
@@ -93,7 +93,8 @@ func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
 	// span.End() will be invoked after
 	// a read from resp.Body returns io.EOF or when
 	// resp.Body.Close() is invoked.
-	resp.Body = &bodyTracker{rc: resp.Body, span: span}
+	bt := &bodyTracker{rc: resp.Body, span: span}
+	resp.Body = wrappedBody(bt, resp.Body)
 	return resp, err
 }
 
diff --git a/plugin/ochttp/wrapped_body.go b/plugin/ochttp/wrapped_body.go
new file mode 100644
index 0000000..7d75cae
--- /dev/null
+++ b/plugin/ochttp/wrapped_body.go
@@ -0,0 +1,44 @@
+// Copyright 2019, OpenCensus Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ochttp
+
+import (
+	"io"
+)
+
+// wrappedBody returns a wrapped version of the original
+// Body and only implements the same combination of additional
+// interfaces as the original.
+func wrappedBody(wrapper io.ReadCloser, body io.ReadCloser) io.ReadCloser {
+	var (
+		wr, i0 = body.(io.Writer)
+	)
+	switch {
+	case !i0:
+		return struct {
+			io.ReadCloser
+		}{wrapper}
+
+	case i0:
+		return struct {
+			io.ReadCloser
+			io.Writer
+		}{wrapper, wr}
+	default:
+		return struct {
+			io.ReadCloser
+		}{wrapper}
+	}
+}
diff --git a/stats/record.go b/stats/record.go
index 86f491e..a0f0ec4 100644
--- a/stats/record.go
+++ b/stats/record.go
@@ -18,7 +18,6 @@ package stats
 import (
 	"context"
 
-	"go.opencensus.io/exemplar"
 	"go.opencensus.io/stats/internal"
 	"go.opencensus.io/tag"
 )
@@ -51,7 +50,8 @@ func Record(ctx context.Context, ms ...Measurement) {
 	if !record {
 		return
 	}
-	recorder(tag.FromContext(ctx), ms, exemplar.AttachmentsFromContext(ctx))
+	// TODO(songy23): fix attachments.
+	recorder(tag.FromContext(ctx), ms, map[string]string{})
 }
 
 // RecordWithTags records one or multiple measurements at once.
diff --git a/stats/view/aggregation_data.go b/stats/view/aggregation_data.go
index 960b946..bf8015f 100644
--- a/stats/view/aggregation_data.go
+++ b/stats/view/aggregation_data.go
@@ -18,7 +18,7 @@ package view
 import (
 	"math"
 
-	"go.opencensus.io/exemplar"
+	"go.opencensus.io/metric/metricdata"
 )
 
 // AggregationData represents an aggregated value from a collection.
@@ -26,7 +26,7 @@ import (
 // Mosts users won't directly access aggregration data.
 type AggregationData interface {
 	isAggregationData() bool
-	addSample(e *exemplar.Exemplar)
+	addSample(v float64)
 	clone() AggregationData
 	equal(other AggregationData) bool
 }
@@ -43,7 +43,7 @@ type CountData struct {
 
 func (a *CountData) isAggregationData() bool { return true }
 
-func (a *CountData) addSample(_ *exemplar.Exemplar) {
+func (a *CountData) addSample(_ float64) {
 	a.Value = a.Value + 1
 }
 
@@ -70,8 +70,8 @@ type SumData struct {
 
 func (a *SumData) isAggregationData() bool { return true }
 
-func (a *SumData) addSample(e *exemplar.Exemplar) {
-	a.Value += e.Value
+func (a *SumData) addSample(v float64) {
+	a.Value += v
 }
 
 func (a *SumData) clone() AggregationData {
@@ -101,8 +101,8 @@ type DistributionData struct {
 	SumOfSquaredDev float64 // sum of the squared deviation from the mean
 	CountPerBucket  []int64 // number of occurrences per bucket
 	// ExemplarsPerBucket is slice the same length as CountPerBucket containing
-	// an exemplar for the associated bucket, or nil.
-	ExemplarsPerBucket []*exemplar.Exemplar
+	// an metricdata for the associated bucket, or nil.
+	ExemplarsPerBucket []*metricdata.Exemplar
 	bounds             []float64 // histogram distribution of the values
 }
 
@@ -110,7 +110,7 @@ func newDistributionData(bounds []float64) *DistributionData {
 	bucketCount := len(bounds) + 1
 	return &DistributionData{
 		CountPerBucket:     make([]int64, bucketCount),
-		ExemplarsPerBucket: make([]*exemplar.Exemplar, bucketCount),
+		ExemplarsPerBucket: make([]*metricdata.Exemplar, bucketCount),
 		bounds:             bounds,
 		Min:                math.MaxFloat64,
 		Max:                math.SmallestNonzeroFloat64,
@@ -129,64 +129,45 @@ func (a *DistributionData) variance() float64 {
 
 func (a *DistributionData) isAggregationData() bool { return true }
 
-func (a *DistributionData) addSample(e *exemplar.Exemplar) {
-	f := e.Value
-	if f < a.Min {
-		a.Min = f
+// TODO(songy23): support exemplar attachments.
+func (a *DistributionData) addSample(v float64) {
+	if v < a.Min {
+		a.Min = v
 	}
-	if f > a.Max {
-		a.Max = f
+	if v > a.Max {
+		a.Max = v
 	}
 	a.Count++
-	a.addToBucket(e)
+	a.addToBucket(v)
 
 	if a.Count == 1 {
-		a.Mean = f
+		a.Mean = v
 		return
 	}
 
 	oldMean := a.Mean
-	a.Mean = a.Mean + (f-a.Mean)/float64(a.Count)
-	a.SumOfSquaredDev = a.SumOfSquaredDev + (f-oldMean)*(f-a.Mean)
+	a.Mean = a.Mean + (v-a.Mean)/float64(a.Count)
+	a.SumOfSquaredDev = a.SumOfSquaredDev + (v-oldMean)*(v-a.Mean)
 }
 
-func (a *DistributionData) addToBucket(e *exemplar.Exemplar) {
+func (a *DistributionData) addToBucket(v float64) {
 	var count *int64
-	var ex **exemplar.Exemplar
 	for i, b := range a.bounds {
-		if e.Value < b {
+		if v < b {
 			count = &a.CountPerBucket[i]
-			ex = &a.ExemplarsPerBucket[i]
 			break
 		}
 	}
-	if count == nil {
+	if count == nil { // Last bucket.
 		count = &a.CountPerBucket[len(a.bounds)]
-		ex = &a.ExemplarsPerBucket[len(a.bounds)]
 	}
 	*count++
-	*ex = maybeRetainExemplar(*ex, e)
-}
-
-func maybeRetainExemplar(old, cur *exemplar.Exemplar) *exemplar.Exemplar {
-	if old == nil {
-		return cur
-	}
-
-	// Heuristic to pick the "better" exemplar: first keep the one with a
-	// sampled trace attachment, if neither have a trace attachment, pick the
-	// one with more attachments.
-	_, haveTraceID := cur.Attachments[exemplar.KeyTraceID]
-	if haveTraceID || len(cur.Attachments) >= len(old.Attachments) {
-		return cur
-	}
-	return old
 }
 
 func (a *DistributionData) clone() AggregationData {
 	c := *a
 	c.CountPerBucket = append([]int64(nil), a.CountPerBucket...)
-	c.ExemplarsPerBucket = append([]*exemplar.Exemplar(nil), a.ExemplarsPerBucket...)
+	c.ExemplarsPerBucket = append([]*metricdata.Exemplar(nil), a.ExemplarsPerBucket...)
 	return &c
 }
 
@@ -218,8 +199,8 @@ func (l *LastValueData) isAggregationData() bool {
 	return true
 }
 
-func (l *LastValueData) addSample(e *exemplar.Exemplar) {
-	l.Value = e.Value
+func (l *LastValueData) addSample(v float64) {
+	l.Value = v
 }
 
 func (l *LastValueData) clone() AggregationData {
diff --git a/stats/view/collector.go b/stats/view/collector.go
index 32415d4..250395d 100644
--- a/stats/view/collector.go
+++ b/stats/view/collector.go
@@ -18,8 +18,6 @@ package view
 import (
 	"sort"
 
-	"go.opencensus.io/exemplar"
-
 	"go.opencensus.io/internal/tagencoding"
 	"go.opencensus.io/tag"
 )
@@ -33,13 +31,13 @@ type collector struct {
 	a *Aggregation
 }
 
-func (c *collector) addSample(s string, e *exemplar.Exemplar) {
+func (c *collector) addSample(s string, v float64) {
 	aggregator, ok := c.signatures[s]
 	if !ok {
 		aggregator = c.a.newData()
 		c.signatures[s] = aggregator
 	}
-	aggregator.addSample(e)
+	aggregator.addSample(v)
 }
 
 // collectRows returns a snapshot of the collected Row values.
diff --git a/stats/view/view.go b/stats/view/view.go
index 7372f99..633d346 100644
--- a/stats/view/view.go
+++ b/stats/view/view.go
@@ -24,8 +24,6 @@ import (
 	"sync/atomic"
 	"time"
 
-	"go.opencensus.io/exemplar"
-
 	"go.opencensus.io/stats"
 	"go.opencensus.io/tag"
 )
@@ -152,12 +150,12 @@ func (v *viewInternal) collectedRows() []*Row {
 	return v.collector.collectedRows(v.view.TagKeys)
 }
 
-func (v *viewInternal) addSample(m *tag.Map, e *exemplar.Exemplar) {
+func (v *viewInternal) addSample(m *tag.Map, val float64) {
 	if !v.isSubscribed() {
 		return
 	}
 	sig := string(encodeWithKeys(m, v.view.TagKeys))
-	v.collector.addSample(sig, e)
+	v.collector.addSample(sig, val)
 }
 
 // A Data is a set of rows about usage of the single measure associated
diff --git a/stats/view/worker_commands.go b/stats/view/worker_commands.go
index f71ec1e..33f2316 100644
--- a/stats/view/worker_commands.go
+++ b/stats/view/worker_commands.go
@@ -21,8 +21,6 @@ import (
 	"strings"
 	"time"
 
-	"go.opencensus.io/exemplar"
-
 	"go.opencensus.io/stats"
 	"go.opencensus.io/stats/internal"
 	"go.opencensus.io/tag"
@@ -161,12 +159,7 @@ func (cmd *recordReq) handleCommand(w *worker) {
 		}
 		ref := w.getMeasureRef(m.Measure().Name())
 		for v := range ref.views {
-			e := &exemplar.Exemplar{
-				Value:       m.Value(),
-				Timestamp:   cmd.t,
-				Attachments: cmd.attachments,
-			}
-			v.addSample(cmd.tm, e)
+			v.addSample(cmd.tm, m.Value())
 		}
 	}
 }
diff --git a/tag/context.go b/tag/context.go
index dcc13f4..b27d1b2 100644
--- a/tag/context.go
+++ b/tag/context.go
@@ -17,8 +17,6 @@ package tag
 
 import (
 	"context"
-
-	"go.opencensus.io/exemplar"
 )
 
 // FromContext returns the tag map stored in the context.
@@ -43,25 +41,3 @@ func NewContext(ctx context.Context, m *Map) context.Context {
 type ctxKey struct{}
 
 var mapCtxKey = ctxKey{}
-
-func init() {
-	exemplar.RegisterAttachmentExtractor(extractTagsAttachments)
-}
-
-func extractTagsAttachments(ctx context.Context, a exemplar.Attachments) exemplar.Attachments {
-	m := FromContext(ctx)
-	if m == nil {
-		return a
-	}
-	if len(m.m) == 0 {
-		return a
-	}
-	if a == nil {
-		a = make(map[string]string)
-	}
-
-	for k, v := range m.m {
-		a[exemplar.KeyPrefixTag+k.Name()] = v
-	}
-	return a
-}


Test patch:
diff --git a/stats/view/aggregation_data_test.go b/stats/view/aggregation_data_test.go
index dc209cb..caa624c 100644
--- a/stats/view/aggregation_data_test.go
+++ b/stats/view/aggregation_data_test.go
@@ -18,11 +18,10 @@ package view
 import (
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
-	"go.opencensus.io/exemplar"
+	"go.opencensus.io/metric/metricdata"
 )
 
 func TestDataClone(t *testing.T) {
@@ -67,21 +66,12 @@ func TestDataClone(t *testing.T) {
 
 func TestDistributionData_addSample(t *testing.T) {
 	dd := newDistributionData([]float64{1, 2})
-	t1, _ := time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Jan 2 15:04:05 -0700 MST 2006")
-	e1 := &exemplar.Exemplar{
-		Attachments: exemplar.Attachments{
-			"tag:X": "Y",
-			"tag:A": "B",
-		},
-		Timestamp: t1,
-		Value:     0.5,
-	}
-	dd.addSample(e1)
+	dd.addSample(0.5)
 
 	want := &DistributionData{
 		Count:              1,
 		CountPerBucket:     []int64{1, 0, 0},
-		ExemplarsPerBucket: []*exemplar.Exemplar{e1, nil, nil},
+		ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil, nil},
 		Max:                0.5,
 		Min:                0.5,
 		Mean:               0.5,
@@ -91,21 +81,13 @@ func TestDistributionData_addSample(t *testing.T) {
 		t.Fatalf("Unexpected DistributionData -got +want: %s", diff)
 	}
 
-	t2 := t1.Add(time.Microsecond)
-	e2 := &exemplar.Exemplar{
-		Attachments: exemplar.Attachments{
-			"tag:X": "Y",
-		},
-		Timestamp: t2,
-		Value:     0.7,
-	}
-	dd.addSample(e2)
+	dd.addSample(0.7)
 
 	// Previous exemplar should be preserved, since it has more annotations.
 	want = &DistributionData{
 		Count:              2,
 		CountPerBucket:     []int64{2, 0, 0},
-		ExemplarsPerBucket: []*exemplar.Exemplar{e1, nil, nil},
+		ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil, nil},
 		Max:                0.7,
 		Min:                0.5,
 		Mean:               0.6,
@@ -115,21 +97,13 @@ func TestDistributionData_addSample(t *testing.T) {
 		t.Fatalf("Unexpected DistributionData -got +want: %s", diff)
 	}
 
-	t3 := t2.Add(time.Microsecond)
-	e3 := &exemplar.Exemplar{
-		Attachments: exemplar.Attachments{
-			exemplar.KeyTraceID: "abcd",
-		},
-		Timestamp: t3,
-		Value:     0.2,
-	}
-	dd.addSample(e3)
+	dd.addSample(0.2)
 
 	// Exemplar should be replaced since it has a trace_id.
 	want = &DistributionData{
 		Count:              3,
 		CountPerBucket:     []int64{3, 0, 0},
-		ExemplarsPerBucket: []*exemplar.Exemplar{e3, nil, nil},
+		ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil, nil},
 		Max:                0.7,
 		Min:                0.2,
 		Mean:               0.4666666666666667,
diff --git a/stats/view/view_test.go b/stats/view/view_test.go
index 5501108..aef2352 100644
--- a/stats/view/view_test.go
+++ b/stats/view/view_test.go
@@ -21,7 +21,7 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 
-	"go.opencensus.io/exemplar"
+	"go.opencensus.io/metric/metricdata"
 
 	"go.opencensus.io/stats"
 	"go.opencensus.io/tag"
@@ -69,7 +69,7 @@ func Test_View_MeasureFloat64_AggregationDistribution(t *testing.T) {
 				{
 					[]tag.Tag{{Key: k1, Value: "v1"}},
 					&DistributionData{
-						Count: 2, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 8, CountPerBucket: []int64{1, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 2, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 8, CountPerBucket: []int64{1, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 			},
@@ -84,13 +84,13 @@ func Test_View_MeasureFloat64_AggregationDistribution(t *testing.T) {
 				{
 					[]tag.Tag{{Key: k1, Value: "v1"}},
 					&DistributionData{
-						Count: 1, Min: 1, Max: 1, Mean: 1, CountPerBucket: []int64{1, 0}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 1, Min: 1, Max: 1, Mean: 1, CountPerBucket: []int64{1, 0}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 				{
 					[]tag.Tag{{Key: k2, Value: "v2"}},
 					&DistributionData{
-						Count: 1, Min: 5, Max: 5, Mean: 5, CountPerBucket: []int64{0, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 1, Min: 5, Max: 5, Mean: 5, CountPerBucket: []int64{0, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 			},
@@ -108,25 +108,25 @@ func Test_View_MeasureFloat64_AggregationDistribution(t *testing.T) {
 				{
 					[]tag.Tag{{Key: k1, Value: "v1"}},
 					&DistributionData{
-						Count: 2, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 8, CountPerBucket: []int64{1, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 2, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 8, CountPerBucket: []int64{1, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 				{
 					[]tag.Tag{{Key: k1, Value: "v1 other"}},
 					&DistributionData{
-						Count: 1, Min: 1, Max: 1, Mean: 1, CountPerBucket: []int64{1, 0}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 1, Min: 1, Max: 1, Mean: 1, CountPerBucket: []int64{1, 0}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 				{
 					[]tag.Tag{{Key: k2, Value: "v2"}},
 					&DistributionData{
-						Count: 1, Min: 5, Max: 5, Mean: 5, CountPerBucket: []int64{0, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 1, Min: 5, Max: 5, Mean: 5, CountPerBucket: []int64{0, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 				{
 					[]tag.Tag{{Key: k1, Value: "v1"}, {Key: k2, Value: "v2"}},
 					&DistributionData{
-						Count: 1, Min: 5, Max: 5, Mean: 5, CountPerBucket: []int64{0, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 1, Min: 5, Max: 5, Mean: 5, CountPerBucket: []int64{0, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 			},
@@ -146,19 +146,19 @@ func Test_View_MeasureFloat64_AggregationDistribution(t *testing.T) {
 				{
 					[]tag.Tag{{Key: k1, Value: "v1 is a very long value key"}},
 					&DistributionData{
-						Count: 2, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 8, CountPerBucket: []int64{1, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 2, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 8, CountPerBucket: []int64{1, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 				{
 					[]tag.Tag{{Key: k1, Value: "v1 is another very long value key"}},
 					&DistributionData{
-						Count: 1, Min: 1, Max: 1, Mean: 1, CountPerBucket: []int64{1, 0}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 1, Min: 1, Max: 1, Mean: 1, CountPerBucket: []int64{1, 0}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 				{
 					[]tag.Tag{{Key: k1, Value: "v1 is a very long value key"}, {Key: k2, Value: "v2 is a very long value key"}},
 					&DistributionData{
-						Count: 4, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 2.66666666666667 * 3, CountPerBucket: []int64{1, 3}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil},
+						Count: 4, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 2.66666666666667 * 3, CountPerBucket: []int64{1, 3}, bounds: []float64{2}, ExemplarsPerBucket: []*metricdata.Exemplar{nil, nil},
 					},
 				},
 			},
@@ -177,11 +177,7 @@ func Test_View_MeasureFloat64_AggregationDistribution(t *testing.T) {
 			if err != nil {
 				t.Errorf("%v: New = %v", tc.label, err)
 			}
-			e := &exemplar.Exemplar{
-				Value:       r.f,
-				Attachments: exemplar.AttachmentsFromContext(ctx),
-			}
-			view.addSample(tag.FromContext(ctx), e)
+			view.addSample(tag.FromContext(ctx), r.f)
 		}
 
 		gotRows := view.collectedRows()
@@ -297,10 +293,7 @@ func Test_View_MeasureFloat64_AggregationSum(t *testing.T) {
 			if err != nil {
 				t.Errorf("%v: New = %v", tt.label, err)
 			}
-			e := &exemplar.Exemplar{
-				Value: r.f,
-			}
-			view.addSample(tag.FromContext(ctx), e)
+			view.addSample(tag.FromContext(ctx), r.f)
 		}
 
 		gotRows := view.collectedRows()
diff --git a/tag/context_test.go b/tag/context_test.go
deleted file mode 100644
index e85b1c4..0000000
--- a/tag/context_test.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018, OpenCensus Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package tag
-
-import (
-	"context"
-	"testing"
-)
-
-func TestExtractTagsAttachment(t *testing.T) {
-	// We can't depend on the stats of view package without creating a
-	// dependency cycle.
-
-	var m map[string]string
-	ctx := context.Background()
-
-	res := extractTagsAttachments(ctx, m)
-	if res != nil {
-		t.Fatalf("res = %v; want nil", res)
-	}
-
-	k, _ := NewKey("test")
-	ctx, _ = New(ctx, Insert(k, "test123"))
-	res = extractTagsAttachments(ctx, m)
-	if res == nil {
-		t.Fatal("res = nil")
-	}
-	if got, want := res["tag:test"], "test123"; got != want {
-		t.Fatalf("res[Tags:test] = %v; want %v", got, want)
-	}
-}
diff --git a/trace/exemplar_test.go b/trace/exemplar_test.go
deleted file mode 100644
index 5d6f62d..0000000
--- a/trace/exemplar_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2018, OpenCensus Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package trace_test
-
-import (
-	"context"
-	"testing"
-
-	"go.opencensus.io/stats"
-	"go.opencensus.io/stats/view"
-	"go.opencensus.io/trace"
-)
-
-func TestTraceExemplar(t *testing.T) {
-	m := stats.Float64("measure."+t.Name(), "", stats.UnitDimensionless)
-	v := &view.View{
-		Measure:     m,
-		Aggregation: view.Distribution(1, 2, 3),
-	}
-	view.Register(v)
-	ctx := context.Background()
-	ctx, span := trace.StartSpan(ctx, t.Name(), trace.WithSampler(trace.AlwaysSample()))
-	stats.Record(ctx, m.M(1.5))
-	span.End()
-
-	rows, err := view.RetrieveData(v.Name)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if len(rows) == 0 {
-		t.Fatal("len(rows) = 0; want > 0")
-	}
-	dd := rows[0].Data.(*view.DistributionData)
-	if got := len(dd.ExemplarsPerBucket); got < 3 {
-		t.Fatalf("len(dd.ExemplarsPerBucket) = %d; want >= 2", got)
-	}
-	exemplar := dd.ExemplarsPerBucket[1]
-	if exemplar == nil {
-		t.Fatal("Expected exemplar")
-	}
-	if got, want := exemplar.Value, 1.5; got != want {
-		t.Fatalf("exemplar.Value = %v; got %v", got, want)
-	}
-	if _, ok := exemplar.Attachments["trace_id"]; !ok {
-		t.Fatalf("exemplar.Attachments = %v; want trace_id key", exemplar.Attachments)
-	}
-	if _, ok := exemplar.Attachments["span_id"]; !ok {
-		t.Fatalf("exemplar.Attachments = %v; want span_id key", exemplar.Attachments)
-	}
-}
-
-func TestTraceExemplar_notSampled(t *testing.T) {
-	m := stats.Float64("measure."+t.Name(), "", stats.UnitDimensionless)
-	v := &view.View{
-		Measure:     m,
-		Aggregation: view.Distribution(1, 2, 3),
-	}
-	view.Register(v)
-	ctx := context.Background()
-	ctx, span := trace.StartSpan(ctx, t.Name(), trace.WithSampler(trace.NeverSample()))
-	stats.Record(ctx, m.M(1.5))
-	span.End()
-
-	rows, err := view.RetrieveData(v.Name)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if len(rows) == 0 {
-		t.Fatal("len(rows) = 0; want > 0")
-	}
-	dd := rows[0].Data.(*view.DistributionData)
-	if got := len(dd.ExemplarsPerBucket); got < 3 {
-		t.Fatalf("len(buckets) = %d; want >= 2", got)
-	}
-	exemplar := dd.ExemplarsPerBucket[1]
-	if exemplar == nil {
-		t.Fatal("Expected exemplar")
-	}
-	if got, want := exemplar.Value, 1.5; got != want {
-		t.Fatalf("exemplar.Value = %v; got %v", got, want)
-	}
-	if _, ok := exemplar.Attachments["trace_id"]; ok {
-		t.Fatalf("exemplar.Attachments = %v; want no trace_id", exemplar.Attachments)
-	}
-	if _, ok := exemplar.Attachments["span_id"]; ok {
-		t.Fatalf("exemplar.Attachments = %v; want span_id key", exemplar.Attachments)
-	}
-}