colors-e2e/colors-be/metrics.go (158 lines of code) (raw):
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"sync/atomic"
"time"
"cloud.google.com/go/compute/metadata"
monitoring "cloud.google.com/go/monitoring/apiv3/v2"
"cloud.google.com/go/monitoring/apiv3/v2/monitoringpb"
"github.com/golang/protobuf/proto"
googlepb "github.com/golang/protobuf/ptypes/timestamp"
metricpb "google.golang.org/genproto/googleapis/api/metric"
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
)
type ServiceMetadata struct {
podName string
podNamespace string
clusterName string
clusterLocation string
projectId string
releaseId string
deploymentName string
}
func GetSerivceMetadata() (*ServiceMetadata, error) {
clusterName, err := metadata.InstanceAttributeValue("cluster-name")
if err != nil {
return nil, fmt.Errorf("failed to get cluster name: %w", err)
}
clusterLocation, err := metadata.InstanceAttributeValue("cluster-location")
if err != nil {
return nil, fmt.Errorf("failed to get cluster location: %w", err)
}
projectId, err := metadata.ProjectID()
if err != nil {
return nil, fmt.Errorf("failed to get project id: %w", err)
}
podName := os.Getenv("PodName")
if podName == "" {
return nil, errors.New("PodName env var not set")
}
podNamespace := os.Getenv("PodNamespace")
if podNamespace == "" {
return nil, errors.New("podNamespace env var not set")
}
deploymentName := os.Getenv("DeploymentName")
if deploymentName == "" {
return nil, errors.New("DeploymentName env var not set")
}
releaseId := os.Getenv("ReleaseId")
if releaseId == "" {
return nil, errors.New("ReleaseId env var not set")
}
return &ServiceMetadata{
podName: podName,
podNamespace: podNamespace,
clusterName: clusterName,
clusterLocation: clusterLocation,
projectId: projectId,
releaseId: releaseId,
deploymentName: deploymentName,
}, nil
}
// Simple request logger for sample application
// Real applications should use something like OpenTelemetry
type RequestLogger struct {
client *monitoring.MetricClient
serviceMetadata *ServiceMetadata
goodRequests int64
badRequests int64
metricSendCount int64
ctx context.Context
}
func NewRequestLogger(ctx context.Context, serviceMetadata *ServiceMetadata) (*RequestLogger, error) {
// Creates a client.
client, err := monitoring.NewMetricClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create metric client: %w", err)
}
logger := &RequestLogger{
client: client,
serviceMetadata: serviceMetadata,
goodRequests: 0,
badRequests: 0,
metricSendCount: 0,
ctx: ctx,
}
// For sample application, just send collected metrics every 10 seconds
go func() {
for {
logger.SendMetrics()
time.Sleep(time.Second * 10)
logger.metricSendCount++
if logger.metricSendCount%6 == 0 {
log.Printf("[Metric Heartbeat] Current sent sample count %v", logger.metricSendCount)
}
}
}()
return logger, nil
}
// Sends the current counts to monitoring and clears the counters
func (l *RequestLogger) SendMetrics() {
var goodCount int64 = 0
var badCount int64 = 0
badCount = atomic.SwapInt64(&l.badRequests, badCount)
goodCount = atomic.SwapInt64(&l.goodRequests, goodCount)
request := &monitoringpb.CreateTimeSeriesRequest{
Name: fmt.Sprintf("projects/%s", l.serviceMetadata.projectId),
TimeSeries: []*monitoringpb.TimeSeries{
l.MakeTimeSeriesWithDataPoint("2xx", goodCount),
l.MakeTimeSeriesWithDataPoint("5xx", badCount),
}}
if err := l.client.CreateTimeSeries(l.ctx, request); err != nil {
log.Printf("Failed to write time series data: %v\n", err)
log.Printf("Request message: %v\n", proto.MarshalTextString(request))
}
}
func (l *RequestLogger) LogRequest(ctx context.Context, isGood bool) {
if isGood {
atomic.AddInt64(&l.goodRequests, 1)
} else {
atomic.AddInt64(&l.badRequests, 1)
}
}
func (l *RequestLogger) MakeTimeSeriesWithDataPoint(responseCodeClass string, metricValue int64) *monitoringpb.TimeSeries {
dataPoint := &monitoringpb.Point{
Interval: &monitoringpb.TimeInterval{
EndTime: &googlepb.Timestamp{
Seconds: time.Now().Unix(),
},
},
Value: &monitoringpb.TypedValue{
Value: &monitoringpb.TypedValue_Int64Value{
Int64Value: metricValue,
},
},
}
return &monitoringpb.TimeSeries{
Metric: &metricpb.Metric{
Type: "custom.googleapis.com/requests/request_count",
Labels: map[string]string{
"deployment_name": l.serviceMetadata.deploymentName,
"release_id": l.serviceMetadata.releaseId,
"response_code_class": responseCodeClass,
},
},
Resource: &monitoredrespb.MonitoredResource{
Type: "k8s_pod",
Labels: map[string]string{
"project_id": l.serviceMetadata.projectId,
"location": l.serviceMetadata.clusterLocation,
"cluster_name": l.serviceMetadata.clusterName,
"pod_name": l.serviceMetadata.podName,
"namespace_name": l.serviceMetadata.podNamespace,
},
},
Points: []*monitoringpb.Point{
dataPoint,
},
}
}