1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-11-28 08:38:51 +02:00

Add and refine metrics examples (#4504)

This commit is contained in:
Robert Pająk 2023-09-14 08:17:20 +02:00 committed by GitHub
parent bcbee2ac1a
commit e3d6c8eec2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 368 additions and 163 deletions

View File

@ -30,7 +30,7 @@ import (
const (
instrumentationName = "github.com/instrumentron"
instrumentationVersion = "v0.1.0"
instrumentationVersion = "0.1.0"
)
var (

View File

@ -41,7 +41,7 @@ var (
Resource: res,
ScopeMetrics: []metricdata.ScopeMetrics{
{
Scope: instrumentation.Scope{Name: "example", Version: "v0.0.1"},
Scope: instrumentation.Scope{Name: "example", Version: "0.0.1"},
Metrics: []metricdata.Metrics{
{
Name: "requests",
@ -173,7 +173,7 @@ func Example() {
// {
// "Scope": {
// "Name": "example",
// "Version": "v0.0.1",
// "Version": "0.0.1",
// "SchemaURL": ""
// },
// "Metrics": [

View File

@ -29,7 +29,7 @@ import (
const (
instrumentationName = "github.com/instrumentron"
instrumentationVersion = "v0.1.0"
instrumentationVersion = "0.1.0"
)
var (

View File

@ -16,18 +16,23 @@ package metric_test
import (
"context"
"database/sql"
"fmt"
"net/http"
"runtime"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
var meter = otel.Meter("my-service-meter")
func ExampleMeter_synchronous() {
// Create a histogram using the global MeterProvider.
workDuration, err := otel.Meter("go.opentelemetry.io/otel/metric#SyncExample").Int64Histogram(
workDuration, err := meter.Int64Histogram(
"workDuration",
metric.WithUnit("ms"))
if err != nil {
@ -43,8 +48,6 @@ func ExampleMeter_synchronous() {
}
func ExampleMeter_asynchronous_single() {
meter := otel.Meter("go.opentelemetry.io/otel/metric#AsyncExample")
_, err := meter.Int64ObservableGauge(
"DiskUsage",
metric.WithUnit("By"),
@ -73,8 +76,6 @@ func ExampleMeter_asynchronous_single() {
}
func ExampleMeter_asynchronous_multiple() {
meter := otel.Meter("go.opentelemetry.io/otel/metric#MultiAsyncExample")
// This is just a sample of memory stats to record from the Memstats
heapAlloc, err := meter.Int64ObservableUpDownCounter("heapAllocs")
if err != nil {
@ -106,3 +107,175 @@ func ExampleMeter_asynchronous_multiple() {
panic(err)
}
}
// Counters can be used to measure a non-negative, increasing value.
//
// Here's how you might report the number of calls for an HTTP handler.
func ExampleMeter_counter() {
apiCounter, err := meter.Int64Counter(
"api.counter",
metric.WithDescription("Number of API calls."),
metric.WithUnit("{call}"),
)
if err != nil {
panic(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
apiCounter.Add(r.Context(), 1)
// do some work in an API call
})
}
// UpDown counters can increment and decrement, allowing you to observe
// a cumulative value that goes up or down.
//
// Here's how you might report the number of items of some collection.
func ExampleMeter_upDownCounter() {
var err error
itemsCounter, err := meter.Int64UpDownCounter(
"items.counter",
metric.WithDescription("Number of items."),
metric.WithUnit("{item}"),
)
if err != nil {
panic(err)
}
_ = func() {
// code that adds an item to the collection
itemsCounter.Add(context.Background(), 1)
}
_ = func() {
// code that removes an item from the collection
itemsCounter.Add(context.Background(), -1)
}
}
// Histograms are used to measure a distribution of values over time.
//
// Here's how you might report a distribution of response times for an HTTP handler.
func ExampleMeter_histogram() {
histogram, err := meter.Float64Histogram(
"task.duration",
metric.WithDescription("The duration of task execution."),
metric.WithUnit("s"),
)
if err != nil {
panic(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// do some work in an API call
duration := time.Since(start)
histogram.Record(r.Context(), duration.Seconds())
})
}
// Observable counters can be used to measure an additive, non-negative,
// monotonically increasing value.
//
// Here's how you might report time since the application started.
func ExampleMeter_observableCounter() {
start := time.Now()
if _, err := meter.Float64ObservableCounter(
"uptime",
metric.WithDescription("The duration since the application started."),
metric.WithUnit("s"),
metric.WithFloat64Callback(func(_ context.Context, o metric.Float64Observer) error {
o.Observe(float64(time.Since(start).Seconds()))
return nil
}),
); err != nil {
panic(err)
}
}
// Observable UpDown counters can increment and decrement, allowing you to measure
// an additive, non-negative, non-monotonically increasing cumulative value.
//
// Here's how you might report some database metrics.
func ExampleMeter_observableUpDownCounter() {
// The function registers asynchronous metrics for the provided db.
// Make sure to unregister metric.Registration before closing the provided db.
_ = func(db *sql.DB, meter metric.Meter, poolName string) (metric.Registration, error) {
max, err := meter.Int64ObservableUpDownCounter(
"db.client.connections.max",
metric.WithDescription("The maximum number of open connections allowed."),
metric.WithUnit("{connection}"),
)
if err != nil {
return nil, err
}
waitTime, err := meter.Int64ObservableUpDownCounter(
"db.client.connections.wait_time",
metric.WithDescription("The time it took to obtain an open connection from the pool."),
metric.WithUnit("ms"),
)
if err != nil {
return nil, err
}
reg, err := meter.RegisterCallback(
func(_ context.Context, o metric.Observer) error {
stats := db.Stats()
o.ObserveInt64(max, int64(stats.MaxOpenConnections))
o.ObserveInt64(waitTime, int64(stats.WaitDuration))
return nil
},
max,
waitTime,
)
if err != nil {
return nil, err
}
return reg, nil
}
}
// Observable Gauges should be used to measure non-additive values.
//
// Here's how you might report memory usage of the heap objects used
// in application.
func ExampleMeter_observableGauge() {
if _, err := meter.Int64ObservableGauge(
"memory.heap",
metric.WithDescription(
"Memory usage of the allocated heap objects.",
),
metric.WithUnit("By"),
metric.WithInt64Callback(func(_ context.Context, o metric.Int64Observer) error {
var m runtime.MemStats
runtime.ReadMemStats(&m)
o.Observe(int64(m.HeapAlloc))
return nil
}),
); err != nil {
panic(err)
}
}
// You can add Attributes by using the [WithAttributeSet] and [WithAttributes] options.
//
// Here's how you might add the HTTP status code attribute to your recordings.
func ExampleMeter_attributes() {
apiCounter, err := meter.Int64UpDownCounter(
"api.finished.counter",
metric.WithDescription("Number of finished API calls."),
metric.WithUnit("{call}"),
)
if err != nil {
panic(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// do some work in an API call and set the response HTTP status code
statusCode := http.StatusOK
apiCounter.Add(r.Context(), 1,
metric.WithAttributes(semconv.HTTPStatusCode(statusCode)))
})
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package metric provides an implementation of the OpenTelemetry metric SDK.
// Package metric provides an implementation of the OpenTelemetry metrics SDK.
//
// See https://opentelemetry.io/docs/concepts/signals/metrics/ for information
// about the concept of OpenTelemetry metrics and
@ -27,8 +27,8 @@
// A MeterProvider needs to be configured to export the measured data, this is
// done by configuring it with a Reader implementation (using the WithReader
// MeterProviderOption). Readers take two forms: ones that push to an endpoint
// (NewPeriodicReader), and ones that an endpoint pulls from. See the
// go.opentelemetry.io/otel/exporters package for exporters that can be used as
// (NewPeriodicReader), and ones that an endpoint pulls from. See
// [go.opentelemetry.io/otel/exporters] for exporters that can be used as
// or with these Readers.
//
// Each Reader, when registered with the MeterProvider, can be augmented with a
@ -41,4 +41,7 @@
// should be used to describe the unique runtime environment instrumented code
// is being run on. That way when multiple instances of the code are collected
// at a single endpoint their origin is decipherable.
//
// See [go.opentelemetry.io/otel/metric] for more information about
// the metric API.
package metric // import "go.opentelemetry.io/otel/sdk/metric"

View File

@ -16,45 +16,207 @@ package metric_test
import (
"context"
"fmt"
"log"
"regexp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
// To enable metrics in your application using the SDK,
// you'll need to have an initialized [MeterProvider]
// that will let you create a [go.opentelemetry.io/otel/metric.Meter].
//
// Here's how you might initialize a metrics provider.
func Example() {
// See [go.opentelemetry.io/otel/sdk/resource] for more
// information about how to create and use resources.
res, err := resource.Merge(resource.Default(),
resource.NewWithAttributes(semconv.SchemaURL,
semconv.ServiceName("my-service"),
semconv.ServiceVersion("0.1.0"),
))
if err != nil {
log.Fatalln(err)
}
// This reader is used as a stand-in for a reader that will actually export
// data. See exporters in the go.opentelemetry.io/otel/exporters package
// for more information.
// data. See [go.opentelemetry.io/otel/exporters] for exporters
// that can be used as or with readers.
reader := metric.NewManualReader()
// See the go.opentelemetry.io/otel/sdk/resource package for more
// information about how to create and use Resources.
res := resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("my-service"),
semconv.ServiceVersion("v0.1.0"),
)
// Create a meter provider.
// You can pass this instance directly to your instrumented code if it
// accepts a MeterProvider instance.
meterProvider := metric.NewMeterProvider(
metric.WithResource(res),
metric.WithReader(reader),
)
// Register as global meter provider so that it can
// that can used via [go.opentelemetry.io/otel.Meter]
// and accessed using [go.opentelemetry.io/otel.GetMeterProvider].
// Most instrumentation libraries use the global meter provider as default.
// If the global meter provider is not set then a no-op implementation
// is used and which fails to generate data.
otel.SetMeterProvider(meterProvider)
// Handle shutdown properly so that nothing leaks.
defer func() {
err := meterProvider.Shutdown(context.Background())
if err != nil {
log.Fatalln(err)
}
}()
// The MeterProvider is configured and registered globally. You can now run
// your code instrumented with the OpenTelemetry API that uses the global
// MeterProvider without having to pass this MeterProvider instance. Or,
// you can pass this instance directly to your instrumented code if it
// accepts a MeterProvider instance.
//
// See the go.opentelemetry.io/otel/metric package for more information
// about the metric API.
}
func ExampleView() {
// The NewView function provides convenient creation of common Views
// construction. However, it is limited in what it can create.
//
// When NewView is not able to provide the functionally needed, a custom
// View can be constructed directly. Here a custom View is constructed that
// uses Go's regular expression matching to ensure all data stream names
// have a suffix of the units it uses.
re := regexp.MustCompile(`[._](ms|byte)$`)
var view metric.View = func(i metric.Instrument) (metric.Stream, bool) {
s := metric.Stream{Name: i.Name, Description: i.Description, Unit: i.Unit}
// Any instrument that does not have a unit suffix defined, but has a
// dimensional unit defined, update the name with a unit suffix.
if re.MatchString(i.Name) {
return s, false
}
switch i.Unit {
case "ms":
s.Name += ".ms"
case "By":
s.Name += ".byte"
default:
return s, false
}
return s, true
}
// The created view can then be registered with the OpenTelemetry metric
// SDK using the WithView option.
_ = metric.NewMeterProvider(
metric.WithView(view),
)
// Below is an example of how the view will
// function in the SDK for certain instruments.
stream, _ := view(metric.Instrument{
Name: "computation.time.ms",
Unit: "ms",
})
fmt.Println("name:", stream.Name)
stream, _ = view(metric.Instrument{
Name: "heap.size",
Unit: "By",
})
fmt.Println("name:", stream.Name)
// Output:
// name: computation.time.ms
// name: heap.size.byte
}
func ExampleNewView() {
// Create a view that renames the "latency" instrument from the v0.34.0
// version of the "http" instrumentation library as "request.latency".
view := metric.NewView(metric.Instrument{
Name: "latency",
Scope: instrumentation.Scope{
Name: "http",
Version: "0.34.0",
},
}, metric.Stream{Name: "request.latency"})
// The created view can then be registered with the OpenTelemetry metric
// SDK using the WithView option.
_ = metric.NewMeterProvider(
metric.WithView(view),
)
// Below is an example of how the view will
// function in the SDK for certain instruments.
stream, _ := view(metric.Instrument{
Name: "latency",
Description: "request latency",
Unit: "ms",
Kind: metric.InstrumentKindCounter,
Scope: instrumentation.Scope{
Name: "http",
Version: "0.34.0",
SchemaURL: "https://opentelemetry.io/schemas/1.0.0",
},
})
fmt.Println("name:", stream.Name)
fmt.Println("description:", stream.Description)
fmt.Println("unit:", stream.Unit)
// Output:
// name: request.latency
// description: request latency
// unit: ms
}
func ExampleNewView_drop() {
// Create a view that sets the drop aggregator for all instrumentation from
// the "db" library, effectively turning-off all instrumentation from that
// library.
view := metric.NewView(
metric.Instrument{Scope: instrumentation.Scope{Name: "db"}},
metric.Stream{Aggregation: metric.AggregationDrop{}},
)
// The created view can then be registered with the OpenTelemetry metric
// SDK using the WithView option.
_ = metric.NewMeterProvider(
metric.WithView(view),
)
// Below is an example of how the view will
// function in the SDK for certain instruments.
stream, _ := view(metric.Instrument{
Name: "queries",
Kind: metric.InstrumentKindCounter,
Scope: instrumentation.Scope{Name: "db", Version: "v0.4.0"},
})
fmt.Println("name:", stream.Name)
fmt.Printf("aggregation: %#v", stream.Aggregation)
// Output:
// name: queries
// aggregation: metric.AggregationDrop{}
}
func ExampleNewView_wildcard() {
// Create a view that sets unit to milliseconds for any instrument with a
// name suffix of ".ms".
view := metric.NewView(
metric.Instrument{Name: "*.ms"},
metric.Stream{Unit: "ms"},
)
// The created view can then be registered with the OpenTelemetry metric
// SDK using the WithView option.
_ = metric.NewMeterProvider(
metric.WithView(view),
)
// Below is an example of how the view will
// function in the SDK for certain instruments.
stream, _ := view(metric.Instrument{
Name: "computation.time.ms",
Unit: "1",
})
fmt.Println("name:", stream.Name)
fmt.Println("unit:", stream.Unit)
// Output:
// name: computation.time.ms
// unit: ms
}

View File

@ -15,8 +15,6 @@
package metric // import "go.opentelemetry.io/otel/sdk/metric"
import (
"fmt"
"regexp"
"testing"
"github.com/go-logr/logr"
@ -493,134 +491,3 @@ func TestNewViewMultiInstMatchErrorLogged(t *testing.T) {
})
assert.Contains(t, got, errMultiInst.Error())
}
func ExampleNewView() {
// Create a view that renames the "latency" instrument from the v0.34.0
// version of the "http" instrumentation library as "request.latency".
view := NewView(Instrument{
Name: "latency",
Scope: instrumentation.Scope{
Name: "http",
Version: "v0.34.0",
},
}, Stream{Name: "request.latency"})
// The created view can then be registered with the OpenTelemetry metric
// SDK using the WithView option. Below is an example of how the view will
// function in the SDK for certain instruments.
stream, _ := view(Instrument{
Name: "latency",
Description: "request latency",
Unit: "ms",
Kind: InstrumentKindCounter,
Scope: instrumentation.Scope{
Name: "http",
Version: "v0.34.0",
SchemaURL: "https://opentelemetry.io/schemas/1.0.0",
},
})
fmt.Println("name:", stream.Name)
fmt.Println("description:", stream.Description)
fmt.Println("unit:", stream.Unit)
// Output:
// name: request.latency
// description: request latency
// unit: ms
}
func ExampleNewView_drop() {
// Create a view that sets the drop aggregator for all instrumentation from
// the "db" library, effectively turning-off all instrumentation from that
// library.
view := NewView(
Instrument{Scope: instrumentation.Scope{Name: "db"}},
Stream{Aggregation: AggregationDrop{}},
)
// The created view can then be registered with the OpenTelemetry metric
// SDK using the WithView option. Below is an example of how the view will
// function in the SDK for certain instruments.
stream, _ := view(Instrument{
Name: "queries",
Kind: InstrumentKindCounter,
Scope: instrumentation.Scope{Name: "db", Version: "v0.4.0"},
})
fmt.Println("name:", stream.Name)
fmt.Printf("aggregation: %#v", stream.Aggregation)
// Output:
// name: queries
// aggregation: metric.AggregationDrop{}
}
func ExampleNewView_wildcard() {
// Create a view that sets unit to milliseconds for any instrument with a
// name suffix of ".ms".
view := NewView(
Instrument{Name: "*.ms"},
Stream{Unit: "ms"},
)
// The created view can then be registered with the OpenTelemetry metric
// SDK using the WithView option. Below is an example of how the view
// function in the SDK for certain instruments.
stream, _ := view(Instrument{
Name: "computation.time.ms",
Unit: "1",
})
fmt.Println("name:", stream.Name)
fmt.Println("unit:", stream.Unit)
// Output:
// name: computation.time.ms
// unit: ms
}
func ExampleView() {
// The NewView function provides convenient creation of common Views
// construction. However, it is limited in what it can create.
//
// When NewView is not able to provide the functionally needed, a custom
// View can be constructed directly. Here a custom View is constructed that
// uses Go's regular expression matching to ensure all data stream names
// have a suffix of the units it uses.
re := regexp.MustCompile(`[._](ms|byte)$`)
var view View = func(i Instrument) (Stream, bool) {
s := Stream{Name: i.Name, Description: i.Description, Unit: i.Unit}
// Any instrument that does not have a unit suffix defined, but has a
// dimensional unit defined, update the name with a unit suffix.
if re.MatchString(i.Name) {
return s, false
}
switch i.Unit {
case "ms":
s.Name += ".ms"
case "By":
s.Name += ".byte"
default:
return s, false
}
return s, true
}
// The created view can then be registered with the OpenTelemetry metric
// SDK using the WithView option. Below is an example of how the view will
// function in the SDK for certain instruments.
stream, _ := view(Instrument{
Name: "computation.time.ms",
Unit: "ms",
})
fmt.Println("name:", stream.Name)
stream, _ = view(Instrument{
Name: "heap.size",
Unit: "By",
})
fmt.Println("name:", stream.Name)
// Output:
// name: computation.time.ms
// name: heap.size.byte
}