Skip to content

Commit

Permalink
Add metric API (census-instrumentation#975)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ramon Nogueira committed Nov 20, 2018
1 parent 91a0276 commit a91cf18
Show file tree
Hide file tree
Showing 9 changed files with 497 additions and 0 deletions.
16 changes: 16 additions & 0 deletions metric/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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 contains a data model and exporter support for metrics.
package metric // import "go.opencensus.io/metric"
55 changes: 55 additions & 0 deletions metric/metric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 (
"time"

"go.opencensus.io/resource"
)

// LabelValue represents the value of a label. A missing value (nil) is distinct
// from an empty string value.
type LabelValue *string

// NewLabelValue creates a new non-nil LabelValue that represents the given string.
func NewLabelValue(val string) LabelValue {
return &val
}

// Descriptor holds metadata about a metric.
type Descriptor struct {
Name string // full name of the metric
Description string // human-readable description
Unit Unit // units for the measure
Type Type // type of measure
LabelKeys []string // label keys
}

// Metric represents a quantity measured against a resource with different
// label value combinations.
type Metric struct {
Descriptor Descriptor // metric descriptor
Resource *resource.Resource // resource against which this was measured
TimeSeries []*TimeSeries // one time series for each combination of label values
}

// TimeSeries is a sequence of points associated with a combination of label
// values.
type TimeSeries struct {
LabelValues []LabelValue // label values, same order as keys in the metric descriptor
Points []Point // points sequence
StartTime time.Time // time we started recording this time series
}
239 changes: 239 additions & 0 deletions metric/point.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// 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 (
"time"

"go.opencensus.io/exemplar"
)

// Point is a single data point of a time series.
type Point struct {
// Time is the point in time that this point represents in a time series.
Time time.Time
// ValueType is the type of value held in this point.
ValueType ValueType
// Value is the value of this point. Prefer using ReadValue to switching on
// the value type, since new value types might be added.
Value interface{}
}

//go:generate stringer -type ValueType

// ValueType is the type of value held in a point.
type ValueType int

// Value types. New value types may be added.
const (
ValueTypeFloat64 ValueType = iota
ValueTypeInt64
ValueTypeDistribution
ValueTypeSummary
)

// NewFloat64Point creates a new Point holding a float64 value.
func NewFloat64Point(t time.Time, val float64) Point {
return Point{
ValueType: ValueTypeFloat64,
Value: val,
Time: t,
}
}

// NewInt64Point creates a new Point holding an int64 value.
func NewInt64Point(t time.Time, val int64) Point {
return Point{
ValueType: ValueTypeInt64,
Value: val,
Time: t,
}
}

// NewDistributionPoint creates a new Point holding a Distribution value.
func NewDistributionPoint(t time.Time, val *Distribution) Point {
return Point{
ValueType: ValueTypeDistribution,
Value: val,
Time: t,
}
}

// NewSummaryPoint creates a new Point holding a Summary value.
func NewSummaryPoint(t time.Time, val *Summary) Point {
return Point{
ValueType: ValueTypeSummary,
Value: val,
Time: t,
}
}

// ValueVisitor allows reading the value of a point.
type ValueVisitor interface {
VisitFloat64Value(float64)
VisitInt64Value(int64)
VisitDistributionValue(*Distribution)
VisitSummaryValue(*Summary)
}

// ReadValue accepts a ValueVisitor and calls the appropriate method with the
// value of this point.
// Consumers of Point should use this in preference to switching on the type
// of the value directly, since new value types may be added.
func (p Point) ReadValue(vv ValueVisitor) {
switch v := p.Value.(type) {
case int64:
vv.VisitInt64Value(v)
case float64:
vv.VisitFloat64Value(v)
case *Distribution:
vv.VisitDistributionValue(v)
case *Summary:
vv.VisitSummaryValue(v)
default:
panic("unexpected value type")
}
}

// Distribution contains summary statistics for a population of values. It
// optionally contains a histogram representing the distribution of those
// values across a set of buckets.
type Distribution struct {
// Count is the number of values in the population. Must be non-negative. This value
// must equal the sum of the values in bucket_counts if a histogram is
// provided.
Count int64
// Sum is the sum of the values in the population. If count is zero then this field
// must be zero.
Sum float64
// SumOfSquaredDeviation is the sum of squared deviations from the mean of the values in the
// population. For values x_i this is:
//
// Sum[i=1..n]((x_i - mean)^2)
//
// Knuth, "The Art of Computer Programming", Vol. 2, page 323, 3rd edition
// describes Welford's method for accumulating this sum in one pass.
//
// If count is zero then this field must be zero.
SumOfSquaredDeviation float64
// BucketOptions describes the bounds of the histogram buckets in this
// distribution.
//
// A Distribution may optionally contain a histogram of the values in the
// population.
//
// If nil, there is no associated histogram.
BucketOptions *BucketOptions
// Bucket If the distribution does not have a histogram, then omit this field.
// If there is a histogram, then the sum of the values in the Bucket counts
// must equal the value in the count field of the distribution.
Buckets []Bucket
}

// BucketOptions describes the bounds of the histogram buckets in this
// distribution.
type BucketOptions struct {
// Bounds specifies a set of buckets with arbitrary upper-bounds.
// This defines len(bounds) + 1 (= N) buckets. The boundaries for bucket
// index i are:
//
// [0, Bounds[i]) for i == 0
// [Bounds[i-1], Bounds[i]) for 0 < i < N-1
// [Bounds[i-1], +infinity) for i == N-1
Bounds []float64
}

// Bucket represents a single bucket (value range) in a distribution.
type Bucket struct {
// Count is the number of values in each bucket of the histogram, as described in
// bucket_bounds.
Count int64
// Exemplar associated with this bucket (if any).
Exemplar *exemplar.Exemplar
}

// Summary is a representation of percentiles.
type Summary struct {
// Count is the cumulative count (if available).
Count int64
// Sum is the cumulative sum of values (if available).
Sum float64
// HasCountAndSum is true if Count and Sum are available.
HasCountAndSum bool
// Snapshot represents percentiles calculated over an arbitrary time window.
// The values in this struct can be reset at arbitrary unknown times, with
// the requirement that all of them are reset at the same time.
Snapshot Snapshot
}

// Snapshot represents percentiles over an arbitrary time.
// The values in this struct can be reset at arbitrary unknown times, with
// the requirement that all of them are reset at the same time.
type Snapshot struct {
// Count is the number of values in the snapshot. Optional since some systems don't
// expose this. Set to 0 if not available.
Count int64
// Sum is the sum of values in the snapshot. Optional since some systems don't
// expose this. If count is 0 then this field must be zero.
Sum float64
// Percentiles is a map from percentile (range (0-100.0]) to the value of
// the percentile.
Percentiles map[float64]float64
}

//go:generate stringer -type Type

// Type is the overall type of metric, including its value type and whether it
// represents a cumulative total (since the start time) or if it represents a
// gauge value.
type Type int

// Metric types.
const (
TypeGaugeInt64 Type = iota
TypeGaugeFloat64
TypeGaugeDistribution
TypeCumulativeInt64
TypeCumulativeFloat64
TypeCumulativeDistribution
TypeSummary
)

// IsGuage returns true if the metric type represents a gauge-type value.
func (t Type) IsGuage() bool {
switch t {
case TypeGaugeInt64, TypeGaugeFloat64, TypeGaugeDistribution:
return true
default:
return false
}
}

// ValueType returns the type of value of the points of metrics of the receiver
// type.
func (t Type) ValueType() ValueType {
switch t {
case TypeGaugeFloat64, TypeCumulativeFloat64:
return ValueTypeFloat64
case TypeGaugeDistribution, TypeCumulativeDistribution:
return ValueTypeDistribution
case TypeGaugeInt64, TypeCumulativeInt64:
return ValueTypeInt64
case TypeSummary:
return ValueTypeSummary
default:
panic("unexpected metric.Type value")
}
}
84 changes: 84 additions & 0 deletions metric/producer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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 (
"sync"
)

// Producer is a source of metrics.
type Producer interface {
// Read should return the current values of all metrics supported by this
// metric provider.
// The returned metrics should be unique for each combination of name and
// resource.
Read() []*Metric
}

// Registry maintains a set of metric producers for exporting. Most users will
// rely on the DefaultRegistry.
type Registry struct {
mu sync.RWMutex
sources map[*uintptr]Producer
ind uint64
}

var _ Producer = (*Registry)(nil)

// NewRegistry creates a new Registry.
func NewRegistry() *Registry {
m := &Registry{
sources: make(map[*uintptr]Producer),
ind: 0,
}
return m
}

// Read returns all the metrics from all the metric produces in this registry.
func (m *Registry) Read() []*Metric {
m.mu.RLock()
defer m.mu.RUnlock()
ms := make([]*Metric, 0, len(m.sources))
for _, s := range m.sources {
ms = append(ms, s.Read()...)
}
return ms
}

// AddProducer adds a producer to this registry.
func (m *Registry) AddProducer(source Producer) (remove func()) {
m.mu.Lock()
defer m.mu.Unlock()
if source == m {
panic("attempt to add registry to itself")
}
tok := new(uintptr)
m.sources[tok] = source
return func() {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.sources, tok)
}
}

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
}
Loading

0 comments on commit a91cf18

Please sign in to comment.