Add metric package containing Gauge support
diff --git a/metric/doc.go b/metric/doc.go
new file mode 100644
index 0000000..485ee8f
--- /dev/null
+++ b/metric/doc.go
@@ -0,0 +1,19 @@
+// 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 metric support for gauge metrics.
+//
+// This is an EXPERIMENTAL package, and may change in arbitrary ways without
+// notice.
+package metric // import "go.opencensus.io/metric"
diff --git a/metric/examples_test.go b/metric/examples_test.go
new file mode 100644
index 0000000..b510ce8
--- /dev/null
+++ b/metric/examples_test.go
@@ -0,0 +1,36 @@
+// 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 metric_test
+
+import (
+	"net/http"
+
+	"go.opencensus.io/metric"
+	"go.opencensus.io/metric/metricdata"
+)
+
+func ExampleRegistry_AddInt64Gauge() {
+	r := metric.NewRegistry()
+	// TODO: allow exporting from a registry
+
+	g := r.AddInt64Gauge("active_request", "Number of active requests, per method.", metricdata.UnitDimensionless, "method")
+
+	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
+		e := g.GetEntry(metricdata.NewLabelValue(request.Method))
+		e.Add(1)
+		defer e.Add(-1)
+		// process request ...
+	})
+}
diff --git a/metric/gauge.go b/metric/gauge.go
new file mode 100644
index 0000000..c64a9f0
--- /dev/null
+++ b/metric/gauge.go
@@ -0,0 +1,196 @@
+// 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 metric
+
+import (
+	"math"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"go.opencensus.io/internal/tagencoding"
+	"go.opencensus.io/metric/metricdata"
+)
+
+// gauge represents a quantity that can go up an down, for example queue depth
+// or number of outstanding requests.
+//
+// gauge maintains a value for each combination of of label values passed to
+// the Set or Add methods.
+//
+// gauge should not be used directly, use Float64Gauge or Int64Gauge.
+type gauge struct {
+	vals    sync.Map
+	desc    metricdata.Descriptor
+	start   time.Time
+	keys    []string
+	isFloat bool
+}
+
+type gaugeEntry interface {
+	read(t time.Time) metricdata.Point
+}
+
+// Read returns the current values of the gauge as a metric for export.
+func (g *gauge) read() *metricdata.Metric {
+	now := time.Now()
+	m := &metricdata.Metric{
+		Descriptor: g.desc,
+	}
+	g.vals.Range(func(k, v interface{}) bool {
+		entry := v.(gaugeEntry)
+		key := k.(string)
+		labelVals := g.labelValues(key)
+		m.TimeSeries = append(m.TimeSeries, &metricdata.TimeSeries{
+			StartTime:   now, // Gauge value is instantaneous.
+			LabelValues: labelVals,
+			Points: []metricdata.Point{
+				entry.read(now),
+			},
+		})
+		return true
+	})
+	return m
+}
+
+func (g *gauge) mapKey(labelVals []metricdata.LabelValue) string {
+	vb := &tagencoding.Values{}
+	for _, v := range labelVals {
+		b := make([]byte, 1, len(v.Value)+1)
+		if v.Present {
+			b[0] = 1
+			b = append(b, []byte(v.Value)...)
+		}
+		vb.WriteValue(b)
+	}
+	return string(vb.Bytes())
+}
+
+func (g *gauge) labelValues(s string) []metricdata.LabelValue {
+	vals := make([]metricdata.LabelValue, 0, len(g.keys))
+	vb := &tagencoding.Values{Buffer: []byte(s)}
+	for range g.keys {
+		v := vb.ReadValue()
+		if v[0] == 0 {
+			vals = append(vals, metricdata.LabelValue{})
+		} else {
+			vals = append(vals, metricdata.NewLabelValue(string(v[1:])))
+		}
+	}
+	return vals
+}
+
+func (g *gauge) entryForValues(labelVals []metricdata.LabelValue, newEntry func() gaugeEntry) interface{} {
+	if len(labelVals) != len(g.keys) {
+		panic("must supply the same number of label values as keys used to construct this gauge")
+	}
+	mapKey := g.mapKey(labelVals)
+	if entry, ok := g.vals.Load(mapKey); ok {
+		return entry
+	}
+	entry, _ := g.vals.LoadOrStore(mapKey, newEntry())
+	return entry
+}
+
+// Float64Gauge represents a float64 value that can go up and down.
+//
+// Float64Gauge maintains a float64 value for each combination of of label values
+// passed to the Set or Add methods.
+type Float64Gauge struct {
+	g gauge
+}
+
+// Float64Entry represents a single value of the gauge corresponding to a set
+// of label values.
+type Float64Entry struct {
+	val uint64 // needs to be uint64 for atomic access, interpret with math.Float64frombits
+}
+
+func (e *Float64Entry) read(t time.Time) metricdata.Point {
+	v := math.Float64frombits(atomic.LoadUint64(&e.val))
+	if v < 0 {
+		v = 0
+	}
+	return metricdata.NewFloat64Point(t, v)
+}
+
+// GetEntry returns a gauge entry where each key for this gauge has the value
+// given.
+//
+// The number of label values supplied must be exactly the same as the number
+// of keys supplied when this gauge was created.
+func (g *Float64Gauge) GetEntry(labelVals ...metricdata.LabelValue) *Float64Entry {
+	return g.g.entryForValues(labelVals, func() gaugeEntry {
+		return &Float64Entry{}
+	}).(*Float64Entry)
+}
+
+// Set sets the gauge entry value to val.
+func (e *Float64Entry) Set(val float64) {
+	atomic.StoreUint64(&e.val, math.Float64bits(val))
+}
+
+// Add increments the gauge entry value by val.
+func (e *Float64Entry) Add(val float64) {
+	var swapped bool
+	for !swapped {
+		oldVal := atomic.LoadUint64(&e.val)
+		newVal := math.Float64bits(math.Float64frombits(oldVal) + val)
+		swapped = atomic.CompareAndSwapUint64(&e.val, oldVal, newVal)
+	}
+}
+
+// Int64Gauge represents a int64 gauge value that can go up and down.
+//
+// Int64Gauge maintains an int64 value for each combination of label values passed to the
+// Set or Add methods.
+type Int64Gauge struct {
+	g gauge
+}
+
+// Int64GaugeEntry represents a single value of the gauge corresponding to a set
+// of label values.
+type Int64GaugeEntry struct {
+	val int64
+}
+
+func (e *Int64GaugeEntry) read(t time.Time) metricdata.Point {
+	v := atomic.LoadInt64(&e.val)
+	if v < 0 {
+		v = 0.0
+	}
+	return metricdata.NewInt64Point(t, v)
+}
+
+// GetEntry returns a gauge entry where each key for this gauge has the value
+// given.
+//
+// The number of label values supplied must be exactly the same as the number
+// of keys supplied when this gauge was created.
+func (g *Int64Gauge) GetEntry(labelVals ...metricdata.LabelValue) *Int64GaugeEntry {
+	return g.g.entryForValues(labelVals, func() gaugeEntry {
+		return &Int64GaugeEntry{}
+	}).(*Int64GaugeEntry)
+}
+
+// Set sets the value of the gauge entry to the provided value.
+func (e *Int64GaugeEntry) Set(val int64) {
+	atomic.StoreInt64(&e.val, val)
+}
+
+// Add increments the current gauge entry value by val, which may be negative.
+func (e *Int64GaugeEntry) Add(val int64) {
+	atomic.AddInt64(&e.val, val)
+}
diff --git a/metric/gauge_test.go b/metric/gauge_test.go
new file mode 100644
index 0000000..e5f8f2e
--- /dev/null
+++ b/metric/gauge_test.go
@@ -0,0 +1,183 @@
+// 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 metric
+
+import (
+	"fmt"
+	"go.opencensus.io/metric/metricdata"
+	"sort"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestGauge(t *testing.T) {
+	r := NewRegistry()
+	f := r.AddFloat64Gauge("TestGauge", "", "", "k1", "k2")
+	f.GetEntry(metricdata.LabelValue{}, metricdata.LabelValue{}).Set(5)
+	f.GetEntry(metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}).Add(1)
+	f.GetEntry(metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}).Add(1)
+	f.GetEntry(metricdata.NewLabelValue("k1v2"), metricdata.NewLabelValue("k2v2")).Add(1)
+	m := r.ReadAll()
+	want := []*metricdata.Metric{
+		{
+			Descriptor: metricdata.Descriptor{
+				Name:      "TestGauge",
+				LabelKeys: []string{"k1", "k2"},
+			},
+			TimeSeries: []*metricdata.TimeSeries{
+				{
+					LabelValues: []metricdata.LabelValue{
+						{}, {},
+					},
+					Points: []metricdata.Point{
+						metricdata.NewFloat64Point(time.Time{}, 5),
+					},
+				},
+				{
+					LabelValues: []metricdata.LabelValue{
+						metricdata.NewLabelValue("k1v1"),
+						{},
+					},
+					Points: []metricdata.Point{
+						metricdata.NewFloat64Point(time.Time{}, 2),
+					},
+				},
+				{
+					LabelValues: []metricdata.LabelValue{
+						metricdata.NewLabelValue("k1v2"),
+						metricdata.NewLabelValue("k2v2"),
+					},
+					Points: []metricdata.Point{
+						metricdata.NewFloat64Point(time.Time{}, 1),
+					},
+				},
+			},
+		},
+	}
+	canonicalize(m)
+	canonicalize(want)
+	if diff := cmp.Diff(m, want, cmp.Comparer(ignoreTimes)); diff != "" {
+		t.Errorf("-got +want: %s", diff)
+	}
+}
+
+func TestFloat64Entry_Add(t *testing.T) {
+	r := NewRegistry()
+	g := r.AddFloat64Gauge("g", "", metricdata.UnitDimensionless)
+	g.GetEntry().Add(0)
+	ms := r.ReadAll()
+	if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), 0.0; got != want {
+		t.Errorf("value = %v, want %v", got, want)
+	}
+	g.GetEntry().Add(1)
+	ms = r.ReadAll()
+	if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), 1.0; got != want {
+		t.Errorf("value = %v, want %v", got, want)
+	}
+	g.GetEntry().Add(-1)
+	ms = r.ReadAll()
+	if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), 0.0; got != want {
+		t.Errorf("value = %v, want %v", got, want)
+	}
+}
+
+func TestFloat64Gauge_Add_NegativeTotals(t *testing.T) {
+	r := NewRegistry()
+	g := r.AddFloat64Gauge("g", "", metricdata.UnitDimensionless)
+	g.GetEntry().Add(-1.0)
+	ms := r.ReadAll()
+	if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), float64(0); got != want {
+		t.Errorf("value = %v, want %v", got, want)
+	}
+}
+
+func TestInt64GaugeEntry_Add(t *testing.T) {
+	r := NewRegistry()
+	g := r.AddInt64Gauge("g", "", metricdata.UnitDimensionless)
+	g.GetEntry().Add(0)
+	ms := r.ReadAll()
+	if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(0); got != want {
+		t.Errorf("value = %v, want %v", got, want)
+	}
+	g.GetEntry().Add(1)
+	ms = r.ReadAll()
+	if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(1); got != want {
+		t.Errorf("value = %v, want %v", got, want)
+	}
+}
+
+func TestInt64Gauge_Add_NegativeTotals(t *testing.T) {
+	r := NewRegistry()
+	g := r.AddInt64Gauge("g", "", metricdata.UnitDimensionless)
+	g.GetEntry().Add(-1)
+	ms := r.ReadAll()
+	if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(0); got != want {
+		t.Errorf("value = %v, want %v", got, want)
+	}
+}
+
+func TestMapKey(t *testing.T) {
+	cases := [][]metricdata.LabelValue{
+		{},
+		{metricdata.LabelValue{}},
+		{metricdata.NewLabelValue("")},
+		{metricdata.NewLabelValue("-")},
+		{metricdata.NewLabelValue(",")},
+		{metricdata.NewLabelValue("v1"), metricdata.NewLabelValue("v2")},
+		{metricdata.NewLabelValue("v1"), metricdata.LabelValue{}},
+		{metricdata.NewLabelValue("v1"), metricdata.LabelValue{}, metricdata.NewLabelValue(string([]byte{0}))},
+		{metricdata.LabelValue{}, metricdata.LabelValue{}},
+	}
+	for i, tc := range cases {
+		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
+			g := &gauge{
+				keys: make([]string, len(tc)),
+			}
+			mk := g.mapKey(tc)
+			vals := g.labelValues(mk)
+			if diff := cmp.Diff(vals, tc); diff != "" {
+				t.Errorf("values differ after serialization -got +want: %s", diff)
+			}
+		})
+	}
+}
+
+func ignoreTimes(_, _ time.Time) bool {
+	return true
+}
+
+func canonicalize(ms []*metricdata.Metric) {
+	for _, m := range ms {
+		sort.Slice(m.TimeSeries, func(i, j int) bool {
+			// sort time series by their label values
+			iLabels := m.TimeSeries[i].LabelValues
+			jLabels := m.TimeSeries[j].LabelValues
+			for k := 0; k < len(iLabels); k++ {
+				if !iLabels[k].Present {
+					if jLabels[k].Present {
+						return true
+					}
+				} else if !jLabels[k].Present {
+					return false
+				} else {
+					return iLabels[k].Value < jLabels[k].Value
+				}
+			}
+			panic("should have returned")
+		})
+	}
+}
diff --git a/metric/metricdata/unit.go b/metric/metricdata/unit.go
index 72887d2..b483a13 100644
--- a/metric/metricdata/unit.go
+++ b/metric/metricdata/unit.go
@@ -18,6 +18,8 @@
 // Unified Code for Units of Measure: http://unitsofmeasure.org/ucum.html
 type Unit string
 
+// Predefined units. To record against a unit not represented here, create your
+// own Unit type constant from a string.
 const (
 	UnitDimensionless Unit = "1"
 	UnitBytes         Unit = "By"
diff --git a/metric/metricexport/producer.go b/metric/metricexport/producer.go
index 0b651f7..077b9fc 100644
--- a/metric/metricexport/producer.go
+++ b/metric/metricexport/producer.go
@@ -16,8 +16,6 @@
 
 import (
 	"go.opencensus.io/metric/metricdata"
-	"sync"
-	"sync/atomic"
 )
 
 // Producer is a source of metrics.
@@ -28,73 +26,3 @@
 	// resource.
 	Read() []*metricdata.Metric
 }
-
-// Registry maintains a set of metric producers for exporting. Most users will
-// rely on the DefaultRegistry.
-type Registry struct {
-	mu    sync.RWMutex
-	state atomic.Value
-}
-
-type registryState struct {
-	producers map[Producer]struct{}
-}
-
-// NewRegistry creates a new Registry.
-func NewRegistry() *Registry {
-	m := &Registry{}
-	m.state.Store(&registryState{
-		producers: make(map[Producer]struct{}),
-	})
-	return m
-}
-
-// Read returns all the metrics from all the metric produces in this registry.
-func (m *Registry) ReadAll() []*metricdata.Metric {
-	s := m.state.Load().(*registryState)
-	ms := make([]*metricdata.Metric, 0, len(s.producers))
-	for p := range s.producers {
-		ms = append(ms, p.Read()...)
-	}
-	return ms
-}
-
-// AddProducer adds a producer to this registry.
-func (m *Registry) AddProducer(p Producer) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	newState := &registryState{
-		make(map[Producer]struct{}),
-	}
-	state := m.state.Load().(*registryState)
-	for producer := range state.producers {
-		newState.producers[producer] = struct{}{}
-	}
-	newState.producers[p] = struct{}{}
-	m.state.Store(newState)
-}
-
-// RemoveProducer removes the given producer from this registry.
-func (m *Registry) RemoveProducer(p Producer) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	newState := &registryState{
-		make(map[Producer]struct{}),
-	}
-	state := m.state.Load().(*registryState)
-	for producer := range state.producers {
-		newState.producers[producer] = struct{}{}
-	}
-	delete(newState.producers, p)
-	m.state.Store(newState)
-}
-
-var defaultReg = NewRegistry()
-
-// DefaultRegistry returns the default, global metric registry for the current
-// process.
-// Most applications will rely on this registry but libraries should not assume
-// the default registry is used.
-func DefaultRegistry() *Registry {
-	return defaultReg
-}
diff --git a/metric/metricexport/producer_test.go b/metric/metricexport/producer_test.go
deleted file mode 100644
index 7aa653f..0000000
--- a/metric/metricexport/producer_test.go
+++ /dev/null
@@ -1,45 +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 metricexport
-
-import (
-	"go.opencensus.io/metric/metricdata"
-	"testing"
-)
-
-func TestRegistry_AddProducer(t *testing.T) {
-	r := NewRegistry()
-	m1 := &metricdata.Metric{
-		Descriptor: metricdata.Descriptor{
-			Name: "test",
-			Unit: metricdata.UnitDimensionless,
-		},
-	}
-	p := &constProducer{m1}
-	r.AddProducer(p)
-	if got, want := len(r.ReadAll()), 1; got != want {
-		t.Fatal("Expected to read a single metric")
-	}
-	r.RemoveProducer(p)
-	if got, want := len(r.ReadAll()), 0; got != want {
-		t.Fatal("Expected to read no metrics")
-	}
-}
-
-type constProducer []*metricdata.Metric
-
-func (cp constProducer) Read() []*metricdata.Metric {
-	return cp
-}
diff --git a/metric/registry.go b/metric/registry.go
new file mode 100644
index 0000000..ac39e42
--- /dev/null
+++ b/metric/registry.go
@@ -0,0 +1,81 @@
+// 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 metric
+
+import (
+	"go.opencensus.io/metric/metricdata"
+	"log"
+	"time"
+)
+
+// Registry creates and manages a set of gauges.
+// External synchronization is required if you want to add gauges to the same
+// registry from multiple goroutines.
+type Registry struct {
+	gauges map[string]*gauge
+}
+
+// NewRegistry initializes a new Registry.
+func NewRegistry() *Registry {
+	return &Registry{
+		gauges: make(map[string]*gauge),
+	}
+}
+
+// AddFloat64Gauge creates and adds a new float64-valued gauge to this registry.
+func (r *Registry) AddFloat64Gauge(name, description string, unit metricdata.Unit, labelKeys ...string) *Float64Gauge {
+	f := &Float64Gauge{
+		g: gauge{
+			isFloat: true,
+		},
+	}
+	r.initGauge(&f.g, labelKeys, name, description, unit)
+	return f
+}
+
+// AddInt64Gauge creates and adds a new int64-valued gauge to this registry.
+func (r *Registry) AddInt64Gauge(name, description string, unit metricdata.Unit, labelKeys ...string) *Int64Gauge {
+	i := &Int64Gauge{}
+	r.initGauge(&i.g, labelKeys, name, description, unit)
+	return i
+}
+
+func (r *Registry) initGauge(g *gauge, labelKeys []string, name string, description string, unit metricdata.Unit) *gauge {
+	existing, ok := r.gauges[name]
+	if ok {
+		if existing.isFloat != g.isFloat {
+			log.Panicf("Gauge with name %s already exists with a different type", name)
+		}
+	}
+	g.keys = labelKeys
+	g.start = time.Now()
+	g.desc = metricdata.Descriptor{
+		Name:        name,
+		Description: description,
+		Unit:        unit,
+		LabelKeys:   labelKeys,
+	}
+	r.gauges[name] = g
+	return g
+}
+
+// ReadAll reads all gauges in this registry and returns their values as metrics.
+func (r *Registry) ReadAll() []*metricdata.Metric {
+	ms := make([]*metricdata.Metric, 0, len(r.gauges))
+	for _, g := range r.gauges {
+		ms = append(ms, g.read())
+	}
+	return ms
+}