Repository: open-telemetry/opamp-go
Pull request #50 (2022-01-11 00:14:45)

Add own metric reporting to the example Server and Agent
The example server sends a connection settings to the agent
pointing to http://localhost:4318/v1/metrics

The example agent accepts the settings and sends a few of its
own metrics to the endpoint.

The results can be seen by running an OpenTelemtry Collector
with OTLP receiver on port 4318 and a for example using a
logging exporter for demo purposes.

Here is a sample output I get:

```
2022-01-10T18:59:46.610-0500	INFO	loggingexporter/logging_exporter.go:54	MetricsExporter	{"#metrics": 3}
2022-01-10T18:59:46.610-0500	DEBUG	loggingexporter/logging_exporter.go:64	ResourceMetrics #0
Resource labels:
     -> service.instance.id: STRING(01FS36A07R06AFVGQT5ZYC0GEK)
     -> service.name: STRING(io.opentelemetry.collector)
     -> service.version: STRING(1.0.0)
InstrumentationLibraryMetrics #0
InstrumentationLibrary opamp
Metric #0
Descriptor:
     -> Name: custom_metric_ticks
     -> Description:
     -> Unit:
     -> DataType: Sum
     -> IsMonotonic: true
     -> AggregationTemporality: AGGREGATION_TEMPORALITY_CUMULATIVE
NumberDataPoints #0
StartTimestamp: 2022-01-10 23:48:51.584797 +0000 UTC
Timestamp: 2022-01-10 23:59:46.610148 +0000 UTC
Value: 8515
Metric #1
Descriptor:
     -> Name: process.cpu.time
     -> Description:
     -> Unit:
     -> DataType: Sum
     -> IsMonotonic: true
     -> AggregationTemporality: AGGREGATION_TEMPORALITY_CUMULATIVE
NumberDataPoints #0
Data point attributes:
     -> state: STRING(user)
StartTimestamp: 2022-01-10 23:48:51.584797 +0000 UTC
Timestamp: 2022-01-10 23:59:46.610148 +0000 UTC
Value: 1.000000
NumberDataPoints #1
Data point attributes:
     -> state: STRING(system)
StartTimestamp: 2022-01-10 23:48:51.584797 +0000 UTC
Timestamp: 2022-01-10 23:59:46.610148 +0000 UTC
Value: 1.000000
NumberDataPoints #2
Data point attributes:
     -> state: STRING(wait)
StartTimestamp: 2022-01-10 23:48:51.584797 +0000 UTC
Timestamp: 2022-01-10 23:59:46.610148 +0000 UTC
Value: 0.159725
Metric #2
Descriptor:
     -> Name: process.memory.physical_usage
     -> Description:
     -> Unit:
     -> DataType: Gauge
NumberDataPoints #0
StartTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 2022-01-10 23:59:46.610128 +0000 UTC
Value: 25072414
```


Patch:
diff --git a/client/callbacks.go b/client/callbacks.go
index 60c02b3..6e19eef 100644
--- a/client/callbacks.go
+++ b/client/callbacks.go
@@ -14,8 +14,8 @@ type CallbacksStruct struct {
 
 	OnRemoteConfigFunc func(
 		ctx context.Context,
-		config *protobufs.AgentRemoteConfig,
-	) (*protobufs.EffectiveConfig, error)
+		remoteConfig *protobufs.AgentRemoteConfig,
+	) (effectiveConfig *protobufs.EffectiveConfig, configChanged bool, err error)
 
 	OnOpampConnectionSettingsFunc func(
 		ctx context.Context,
@@ -62,12 +62,13 @@ func (c CallbacksStruct) OnError(err *protobufs.ServerErrorResponse) {
 }
 
 func (c CallbacksStruct) OnRemoteConfig(
-	ctx context.Context, remoteConfig *protobufs.AgentRemoteConfig,
-) (*protobufs.EffectiveConfig, error) {
+	ctx context.Context,
+	remoteConfig *protobufs.AgentRemoteConfig,
+) (effectiveConfig *protobufs.EffectiveConfig, configChanged bool, err error) {
 	if c.OnRemoteConfigFunc != nil {
 		return c.OnRemoteConfigFunc(ctx, remoteConfig)
 	}
-	return nil, nil
+	return nil, false, nil
 }
 
 func (c CallbacksStruct) OnOpampConnectionSettings(
diff --git a/client/internal/receiver.go b/client/internal/receiver.go
index f21889a..42cda88 100644
--- a/client/internal/receiver.go
+++ b/client/internal/receiver.go
@@ -61,7 +61,7 @@ func (r *Receiver) receiveMessage(msg *protobufs.ServerToAgent) error {
 
 func (r *Receiver) processReceivedMessage(ctx context.Context, msg *protobufs.ServerToAgent) {
 	if r.callbacks != nil {
-		reportStatus := r.rcvRemoteConfig(ctx, msg.RemoteConfig)
+		reportStatus := r.rcvRemoteConfig(ctx, msg.RemoteConfig, msg.Flags)
 
 		r.rcvConnectionSettings(ctx, msg.ConnectionSettings)
 		r.rcvAddonsAvailable(msg.AddonsAvailable)
@@ -77,15 +77,25 @@ func (r *Receiver) processReceivedMessage(ctx context.Context, msg *protobufs.Se
 	}
 }
 
-func (r *Receiver) rcvRemoteConfig(ctx context.Context, config *protobufs.AgentRemoteConfig) (reportStatus bool) {
-	effective, err := r.callbacks.OnRemoteConfig(ctx, config)
-	if err == nil {
+func (r *Receiver) rcvRemoteConfig(
+	ctx context.Context,
+	config *protobufs.AgentRemoteConfig,
+	flags protobufs.ServerToAgent_Flags,
+) (reportStatus bool) {
+	effective, changed, err := r.callbacks.OnRemoteConfig(ctx, config)
+	if err != nil {
+		return false
+	}
+
+	// Report the effective configuration if it changed or if the Server explicitly
+	// asked us to report it.
+	reportEffective := changed || (flags&protobufs.ServerToAgent_ReportEffectiveConfig != 0)
+
+	if reportEffective {
 		r.sender.UpdateNextStatus(func(statusReport *protobufs.StatusReport) {
 			statusReport.EffectiveConfig = effective
 		})
-		if effective != nil {
-			return true
-		}
+		return true
 	}
 	return false
 }
diff --git a/client/types/callbacks.go b/client/types/callbacks.go
index d5f90ca..a51a6ba 100644
--- a/client/types/callbacks.go
+++ b/client/types/callbacks.go
@@ -35,6 +35,10 @@ type Callbacks interface {
 	//
 	// The Agent should process the config and return the effective config if processing
 	// succeeded or an error if processing failed.
+	//
+	// configChanged must be set to true if as a result of applying the remote config
+	// the effective config has changed.
+	//
 	// The returned effective config or the error will be reported back to the server
 	// via StatusReport message (using EffectiveConfig and RemoteConfigStatus fields).
 	//
@@ -45,8 +49,8 @@ type Callbacks interface {
 	// remote config received.
 	OnRemoteConfig(
 		ctx context.Context,
-		config *protobufs.AgentRemoteConfig,
-	) (*protobufs.EffectiveConfig, error)
+		remoteConfig *protobufs.AgentRemoteConfig,
+	) (effectiveConfig *protobufs.EffectiveConfig, configChanged bool, err error)
 
 	// OnOpampConnectionSettings is called when the agent receives an OpAMP
 	// connection settings offer from the server. Typically the settings can specify
diff --git a/go.mod b/go.mod
index 14ab7de..b9e4c6c 100644
--- a/go.mod
+++ b/go.mod
@@ -7,18 +7,41 @@ require (
 	github.com/gorilla/websocket v1.4.2
 	github.com/knadh/koanf v1.3.3
 	github.com/oklog/ulid/v2 v2.0.2
+	github.com/shirou/gopsutil v3.21.11+incompatible
 	github.com/stretchr/testify v1.7.0
+	go.opentelemetry.io/otel v1.3.0
+	go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0
+	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.26.0
+	go.opentelemetry.io/otel/metric v0.26.0
+	go.opentelemetry.io/otel/sdk v1.3.0
+	go.opentelemetry.io/otel/sdk/metric v0.26.0
 	google.golang.org/protobuf v1.27.1
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/go-logr/logr v1.2.1 // indirect
+	github.com/go-logr/stdr v1.2.0 // indirect
+	github.com/go-ole/go-ole v1.2.6 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
 	github.com/mitchellh/copystructure v1.2.0 // indirect
 	github.com/mitchellh/mapstructure v1.4.1 // indirect
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
-	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	github.com/tklauser/go-sysconf v0.3.9 // indirect
+	github.com/tklauser/numcpus v0.3.0 // indirect
+	github.com/yusufpapurcu/wmi v1.2.2 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect
+	go.opentelemetry.io/otel/internal/metric v0.26.0 // indirect
+	go.opentelemetry.io/otel/sdk/export/metric v0.26.0 // indirect
+	go.opentelemetry.io/otel/trace v1.3.0 // indirect
+	go.opentelemetry.io/proto/otlp v0.11.0 // indirect
+	golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
+	golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71 // indirect
+	golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db // indirect
+	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
+	google.golang.org/grpc v1.42.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 )
diff --git a/go.sum b/go.sum
index 0e3813b..ae87dc8 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
@@ -12,32 +14,75 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72H
 github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
 github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
 github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
+github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
+github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
 github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA=
+github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE=
+github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@@ -94,28 +139,67 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
+github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
+github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
+github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
+github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
+github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
+github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
+github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
+go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
+go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 h1:R/OBkMoGgfy2fLhs2QhkCI1w4HLEQX92GCcJB6SSdNk=
+go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0 h1:dIE9swzwOnkGaJ6OF1QQQdBk2EdrJnD9Ilao2G9DeLU=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0/go.mod h1:1E0NE+3ywwedkOEl3d7nFjyI/bqRECMhI3xTGh13pxY=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.26.0 h1:B4KUKYd6VEiqKh0kHf4+RZOhY/kANSvGydrQdE1dNkY=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.26.0/go.mod h1:CaqVOcEDDQ1XY7zEJT81c8aUNPju3jRqjva6ROUi+z0=
+go.opentelemetry.io/otel/internal/metric v0.26.0 h1:dlrvawyd/A+X8Jp0EBT4wWEe4k5avYaXsXrBr4dbfnY=
+go.opentelemetry.io/otel/internal/metric v0.26.0/go.mod h1:CbBP6AxKynRs3QCbhklyLUtpfzbqCLiafV9oY2Zj1Jk=
+go.opentelemetry.io/otel/metric v0.26.0 h1:VaPYBTvA13h/FsiWfxa3yZnZEm15BhStD8JZQSA773M=
+go.opentelemetry.io/otel/metric v0.26.0/go.mod h1:c6YL0fhRo4YVoNs6GoByzUgBp36hBL523rECoZA5UWg=
+go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI=
+go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
+go.opentelemetry.io/otel/sdk/export/metric v0.26.0 h1:eNseg5yyZqaAAY+Att3owR3Bl0Is5rCZywqO1OrGx18=
+go.opentelemetry.io/otel/sdk/export/metric v0.26.0/go.mod h1:UpqzSnUOjFeSIVQLPp3pYIXfB/MiMFyXXzYT/bercxQ=
+go.opentelemetry.io/otel/sdk/metric v0.26.0 h1:7IKp3gc/ObieCtshBeYYVFp3ZP7xIH1OzODi1Wao90Y=
+go.opentelemetry.io/otel/sdk/metric v0.26.0/go.mod h1:2VIeK0kS1YvRLFg3J58ptZTXYpiWlkq2n5RQt6w7He8=
+go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY=
+go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v0.11.0 h1:cLDgIBTf4lLOlztkhzAEdQsJ4Lj+i5Wc9k6Nn0K1VyU=
+go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -126,11 +210,16 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71 h1:ikCpsnYR+Ew0vu99XlDp55lGgDJdIMx3f4a18jfse/s=
+golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
 golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -145,16 +234,39 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
+google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/internal/examples/agent/agent/agent.go b/internal/examples/agent/agent/agent.go
index 8dbe1b7..ba0889b 100644
--- a/internal/examples/agent/agent/agent.go
+++ b/internal/examples/agent/agent/agent.go
@@ -55,6 +55,8 @@ type Agent struct {
 	opampClient client.OpAMPClient
 
 	remoteConfigHash []byte
+
+	metricReporter *MetricReporter
 }
 
 func NewAgent(logger types.Logger, agentType string, agentVersion string) *Agent {
@@ -95,7 +97,8 @@ func (agent *Agent) start() error {
 			OnErrorFunc: func(err *protobufs.ServerErrorResponse) {
 				agent.logger.Errorf("Server returned an error response: %v", err.ErrorMessage)
 			},
-			OnRemoteConfigFunc: agent.onRemoteConfig,
+			OnRemoteConfigFunc:                   agent.onRemoteConfig,
+			OnOwnTelemetryConnectionSettingsFunc: agent.onOwnTelemetryConnectionSettings,
 		},
 		LastRemoteConfigHash: agent.remoteConfigHash,
 		LastEffectiveConfig:  agent.composeEffectiveConfig(),
@@ -183,14 +186,46 @@ func (agent *Agent) composeEffectiveConfig() *protobufs.EffectiveConfig {
 }
 
 func (agent *Agent) onRemoteConfig(
-	ctx context.Context,
-	config *protobufs.AgentRemoteConfig,
-) (*protobufs.EffectiveConfig, error) {
-	err := agent.applyRemoteConfig(config)
+	_ context.Context,
+	remoteConfig *protobufs.AgentRemoteConfig,
+) (effectiveConfig *protobufs.EffectiveConfig, configChanged bool, err error) {
+	configChanged, err = agent.applyRemoteConfig(remoteConfig)
 	if err != nil {
-		return nil, err
+		return nil, false, err
+	}
+
+	return agent.composeEffectiveConfig(), configChanged, nil
+}
+
+func (agent *Agent) onOwnTelemetryConnectionSettings(
+	_ context.Context,
+	telemetryType types.OwnTelemetryType,
+	settings *protobufs.ConnectionSettings,
+) error {
+	switch telemetryType {
+	case types.OwnMetrics:
+		agent.initMeter(settings)
 	}
-	return agent.composeEffectiveConfig(), nil
+
+	return nil
+}
+
+func (agent *Agent) initMeter(settings *protobufs.ConnectionSettings) {
+	reporter, err := NewMetricReporter(agent.logger, settings, agent.agentType, agent.agentVersion, agent.instanceId)
+	if err != nil {
+		agent.logger.Errorf("Cannot collect metrics: %v", err)
+		return
+	}
+
+	prevReporter := agent.metricReporter
+
+	agent.metricReporter = reporter
+
+	if prevReporter != nil {
+		prevReporter.Shutdown()
+	}
+
+	return
 }
 
 type agentConfigFileItem struct {
@@ -214,9 +249,9 @@ func (a agentConfigFileSlice) Len() int {
 	return len(a)
 }
 
-func (agent *Agent) applyRemoteConfig(config *protobufs.AgentRemoteConfig) error {
+func (agent *Agent) applyRemoteConfig(config *protobufs.AgentRemoteConfig) (configChanged bool, err error) {
 	if config == nil {
-		return nil
+		return false, nil
 	}
 
 	agent.logger.Debugf("Received remote config from server, hash=%x.", config.ConfigHash)
@@ -224,7 +259,7 @@ func (agent *Agent) applyRemoteConfig(config *protobufs.AgentRemoteConfig) error
 	// Begin with local config. We will later merge received configs on top of it.
 	var k = koanf.New(".")
 	if err := k.Load(rawbytes.Provider([]byte(localConfig)), yaml.Parser()); err != nil {
-		return err
+		return false, err
 	}
 
 	orderedConfigs := agentConfigFileSlice{}
@@ -256,11 +291,11 @@ func (agent *Agent) applyRemoteConfig(config *protobufs.AgentRemoteConfig) error
 		var k2 = koanf.New(".")
 		err := k2.Load(rawbytes.Provider(item.file.Body), yaml.Parser())
 		if err != nil {
-			return fmt.Errorf("cannot parse config named %s: %v", item.name, err)
+			return false, fmt.Errorf("cannot parse config named %s: %v", item.name, err)
 		}
 		err = k.Merge(k2)
 		if err != nil {
-			return fmt.Errorf("cannot merge config named %s: %v", item.name, err)
+			return false, fmt.Errorf("cannot merge config named %s: %v", item.name, err)
 		}
 	}
 
@@ -271,16 +306,18 @@ func (agent *Agent) applyRemoteConfig(config *protobufs.AgentRemoteConfig) error
 	}
 
 	newEffectiveConfig := string(effectiveConfigBytes)
+	configChanged = false
 	if agent.effectiveConfig != newEffectiveConfig {
 		agent.logger.Debugf("Effective config changed. Need to report to server.")
 		agent.effectiveConfig = newEffectiveConfig
 		hash := sha256.Sum256(effectiveConfigBytes)
 		agent.effectiveConfigHash = hash[:]
+		configChanged = true
 	}
 
 	agent.remoteConfigHash = config.ConfigHash
 
-	return nil
+	return configChanged, nil
 }
 
 func (agent *Agent) Shutdown() {
diff --git a/internal/examples/agent/agent/metricreporter.go b/internal/examples/agent/agent/metricreporter.go
new file mode 100644
index 0000000..a448103
--- /dev/null
+++ b/internal/examples/agent/agent/metricreporter.go
@@ -0,0 +1,205 @@
+package agent
+
+import (
+	"context"
+	"fmt"
+	"math"
+	"math/rand"
+	"net/url"
+	"os"
+	"time"
+
+	"github.com/oklog/ulid/v2"
+	"github.com/shirou/gopsutil/process"
+	"go.opentelemetry.io/otel/attribute"
+	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric"
+	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
+	"go.opentelemetry.io/otel/metric"
+	"go.opentelemetry.io/otel/metric/global"
+	controller "go.opentelemetry.io/otel/sdk/metric/controller/basic"
+	processor "go.opentelemetry.io/otel/sdk/metric/processor/basic"
+	"go.opentelemetry.io/otel/sdk/metric/selector/simple"
+	otelresource "go.opentelemetry.io/otel/sdk/resource"
+	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
+
+	"github.com/open-telemetry/opamp-go/client/types"
+	"github.com/open-telemetry/opamp-go/protobufs"
+)
+
+// MetricReporter is a metric reporter that collects agent metrics and sends them to an
+// OTLP/HTTP destination.
+type MetricReporter struct {
+	logger types.Logger
+
+	meter           metric.Meter
+	meterShutdowner func()
+	done            chan struct{}
+
+	// The agent's process.
+	process *process.Process
+
+	// Some example metrics to report.
+	processMemoryPhysical metric.Int64GaugeObserver
+	counter               metric.Int64Counter
+	processCpuTime        metric.Float64CounterObserver
+}
+
+func NewMetricReporter(
+	logger types.Logger,
+	dest *protobufs.ConnectionSettings,
+	agentType string,
+	agentVersion string,
+	instanceId ulid.ULID,
+) (*MetricReporter, error) {
+
+	// Check the destination credentials to make sure they look like a valid OTLP/HTTP
+	// destination.
+
+	if dest.DestinationEndpoint == "" {
+		err := fmt.Errorf("metric destination must specify DestinationEndpoint")
+		return nil, err
+	}
+	u, err := url.Parse(dest.DestinationEndpoint)
+	if err != nil {
+		err := fmt.Errorf("invalid DestinationEndpoint: %v", err)
+		return nil, err
+	}
+
+	// Create OTLP/HTTP metric exporter.
+	opts := []otlpmetrichttp.Option{
+		otlpmetrichttp.WithEndpoint(u.Host),
+		otlpmetrichttp.WithURLPath(u.Path),
+	}
+
+	if u.Scheme == "http" {
+		opts = append(opts, otlpmetrichttp.WithInsecure())
+	}
+
+	client := otlpmetrichttp.NewClient(opts...)
+
+	metricExporter, err := otlpmetric.New(context.Background(), client)
+	if err != nil {
+		err := fmt.Errorf("failed to initialize stdoutmetric export pipeline: %v", err)
+		return nil, err
+	}
+
+	// Define the Resource to be exported with all metrics. Use OpenTelemetry semantic
+	// conventions as the OpAMP spec requires:
+	// https://github.com/open-telemetry/opamp-spec/blob/main/specification.md#own-telemetry-reporting
+	resource, err := otelresource.New(context.Background(),
+		otelresource.WithAttributes(
+			semconv.ServiceNameKey.String(agentType),
+			semconv.ServiceVersionKey.String(agentVersion),
+			semconv.ServiceInstanceIDKey.String(instanceId.String()),
+		),
+	)
+
+	// Wire up the Resource and the exporter together.
+	cont := controller.New(
+		processor.NewFactory(
+			simple.NewWithInexpensiveDistribution(),
+			metricExporter,
+		),
+		controller.WithExporter(metricExporter),
+		controller.WithCollectPeriod(5*time.Second),
+		controller.WithResource(resource),
+	)
+
+	err = cont.Start(context.Background())
+	if err != nil {
+		err := fmt.Errorf("failed to initialize metric controller: %v", err)
+		return nil, err
+	}
+
+	global.SetMeterProvider(cont)
+
+	reporter := &MetricReporter{
+		logger: logger,
+	}
+
+	reporter.done = make(chan struct{})
+
+	reporter.meter = global.Meter("opamp")
+
+	reporter.process, err = process.NewProcess(int32(os.Getpid()))
+	if err != nil {
+		err := fmt.Errorf("cannot query own process: %v", err)
+		return nil, err
+	}
+
+	// Create some metrics that will be reported according to OpenTelemetry semantic
+	// conventions for process metrics (conventions are TBD for now).
+	reporter.processCpuTime = metric.Must(reporter.meter).NewFloat64CounterObserver(
+		"process.cpu.time",
+		reporter.processCpuTimeFunc,
+	)
+
+	reporter.processMemoryPhysical = metric.Must(reporter.meter).NewInt64GaugeObserver(
+		"process.memory.physical_usage",
+		reporter.processMemoryPhysicalFunc,
+	)
+
+	reporter.counter = metric.Must(reporter.meter).NewInt64Counter("custom_metric_ticks")
+
+	reporter.meterShutdowner = func() { _ = cont.Stop(context.Background()) }
+
+	go reporter.sendMetrics()
+
+	return reporter, nil
+}
+
+func (reporter *MetricReporter) processCpuTimeFunc(_ context.Context, result metric.Float64ObserverResult) {
+	times, err := reporter.process.Times()
+	if err != nil {
+		reporter.logger.Errorf("Cannot get process CPU times: %v", err)
+	}
+
+	// Report process CPU times, but also add some randomness to make it interesting for demo.
+	result.Observe(math.Min(times.User+rand.Float64(), 1), attribute.String("state", "user"))
+	result.Observe(math.Min(times.System+rand.Float64(), 1), attribute.String("state", "system"))
+	result.Observe(math.Min(times.Iowait+rand.Float64(), 1), attribute.String("state", "wait"))
+}
+
+func (reporter *MetricReporter) processMemoryPhysicalFunc(_ context.Context, result metric.Int64ObserverResult) {
+	memory, err := reporter.process.MemoryInfo()
+	if err != nil {
+		reporter.logger.Errorf("Cannot get process memory information: %v", err)
+		return
+	}
+
+	// Report the RSS, but also add some randomness to make it interesting for demo.
+	result.Observe(int64(memory.RSS) + rand.Int63n(10000000))
+}
+
+func (reporter *MetricReporter) sendMetrics() {
+
+	// Collect metrics every 5 seconds.
+	t := time.NewTicker(time.Second * 5)
+	ticks := int64(0)
+
+	for {
+		select {
+		case <-reporter.done:
+			return
+
+		case <-t.C:
+			ctx := context.Background()
+			reporter.meter.RecordBatch(
+				ctx,
+				[]attribute.KeyValue{},
+				reporter.counter.Measurement(ticks),
+			)
+			ticks++
+		}
+	}
+}
+
+func (reporter *MetricReporter) Shutdown() {
+	if reporter.done != nil {
+		close(reporter.done)
+	}
+
+	if reporter.meterShutdowner != nil {
+		reporter.meterShutdowner()
+	}
+}
diff --git a/internal/examples/server/data/agent.go b/internal/examples/server/data/agent.go
index 61ec0bc..c774a15 100644
--- a/internal/examples/server/data/agent.go
+++ b/internal/examples/server/data/agent.go
@@ -95,14 +95,13 @@ func notifyStatusWatchers(statusUpdateWatchers []chan<- struct{}) {
 	}
 }
 
-func (agent *Agent) updateStatusField(newStatus *protobufs.StatusReport) (needCalculateConfig bool) {
+func (agent *Agent) updateStatusField(newStatus *protobufs.StatusReport) (agentDescrChanged bool) {
 	prevStatus := agent.Status
 
 	if agent.Status == nil {
-		// First time this agent reports a status, remember it and ensure we calculate
-		// the config.
+		// First time this agent reports a status, remember it.
 		agent.Status = newStatus
-		needCalculateConfig = true
+		agentDescrChanged = true
 	} else {
 		// Not a new agent. Checks what's changed in the agent's description.
 		if newStatus.AgentDescription != nil {
@@ -112,18 +111,16 @@ func (agent *Agent) updateStatusField(newStatus *protobufs.StatusReport) (needCa
 			// Make full comparison of previous and new descriptions to see if it
 			// really is different.
 			if prevStatus != nil && isEqualAgentDescr(prevStatus.AgentDescription, newStatus.AgentDescription) {
-				// Agent description didn't change, no need to invalidate configuration.
-				needCalculateConfig = false
+				// Agent description didn't change.
+				agentDescrChanged = false
 			} else {
-				// Yes, the description is different, update it and force config
-				// recalculation.
+				// Yes, the description is different, update it.
 				agent.Status.AgentDescription = newStatus.AgentDescription
-				needCalculateConfig = true
+				agentDescrChanged = true
 			}
 		} else {
-			// AgentDescription field is not set, which means description didn't
-			// change, no need to invalidate configuration.
-			needCalculateConfig = false
+			// AgentDescription field is not set, which means description didn't change.
+			agentDescrChanged = false
 		}
 
 		// Update remote config status if it is provided.
@@ -132,7 +129,7 @@ func (agent *Agent) updateStatusField(newStatus *protobufs.StatusReport) (needCa
 		}
 	}
 
-	return needCalculateConfig
+	return agentDescrChanged
 }
 
 func (agent *Agent) updateEffectiveConfig(
@@ -169,12 +166,17 @@ func (agent *Agent) processStatusUpdate(
 	newStatus *protobufs.StatusReport,
 	response *protobufs.ServerToAgent,
 ) {
-	needCalculateConfig := agent.updateStatusField(newStatus)
+	agentDescrChanged := agent.updateStatusField(newStatus)
 
 	configChanged := false
-	if needCalculateConfig {
+	if agentDescrChanged {
+		// Agent description is changed.
+		//
 		// We need to recalculate the config.
 		configChanged = agent.calcRemoteConfig()
+
+		// And set connection settings that are appropriate for the agent description.
+		agent.calcConnectionSettings(response)
 	}
 
 	// If remote config is changed and different from what the agent has then
@@ -310,6 +312,27 @@ func isEqualConfigFile(f1, f2 *protobufs.AgentConfigFile) bool {
 	return bytes.Compare(f1.Body, f2.Body) == 0 && f1.ContentType == f2.ContentType
 }
 
+func (agent *Agent) calcConnectionSettings(response *protobufs.ServerToAgent) {
+	// Here we can use agent's description to send the appropriate connection
+	// settings to the agent.
+	// In this simple example the connection settings do not depend on the
+	// agent description, so we jst set them directly.
+
+	response.ConnectionSettings = &protobufs.ConnectionSettingsOffers{
+		Hash:  nil, // TODO: calc has from settings.
+		Opamp: nil,
+		OwnMetrics: &protobufs.ConnectionSettings{
+			// We just hard-code this to a port on a localhost on which we can
+			// run an Otel Collector for demo purposes. With real production
+			// servers this should likely point to an OTLP backend.
+			DestinationEndpoint: "http://localhost:4318/v1/metrics",
+		},
+		OwnTraces:        nil,
+		OwnLogs:          nil,
+		OtherConnections: nil,
+	}
+}
+
 func (agent *Agent) SendToAgent(msg *protobufs.ServerToAgent) {
 	agent.connMutex.Lock()
 	defer agent.connMutex.Unlock()


Test patch:
diff --git a/client/clientimpl_test.go b/client/clientimpl_test.go
index 04d7558..583f5b9 100644
--- a/client/clientimpl_test.go
+++ b/client/clientimpl_test.go
@@ -271,15 +271,16 @@ func TestFirstStatusReport(t *testing.T) {
 				atomic.AddInt64(&connected, 1)
 			},
 			OnRemoteConfigFunc: func(
-				ctx context.Context, config *protobufs.AgentRemoteConfig,
-			) (*protobufs.EffectiveConfig, error) {
+				ctx context.Context,
+				config *protobufs.AgentRemoteConfig,
+			) (effectiveConfig *protobufs.EffectiveConfig, configChanged bool, err error) {
 				// Verify that the client received exactly the remote config that
 				// the server sent.
 				assert.True(t, proto.Equal(remoteConfig, config))
 				atomic.AddInt64(&remoteConfigReceived, 1)
 				return &protobufs.EffectiveConfig{
 					ConfigMap: remoteConfig.Config,
-				}, nil
+				}, true, nil
 			},
 		},
 		AgentDescription: &protobufs.AgentDescription{},