195 lines
6.4 KiB
Go
195 lines
6.4 KiB
Go
/*
|
|
*
|
|
* Copyright 2022 gRPC 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 orca
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/orca/internal"
|
|
"google.golang.org/grpc/status"
|
|
|
|
v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
|
|
v3orcaservicegrpc "github.com/cncf/xds/go/xds/service/orca/v3"
|
|
v3orcaservicepb "github.com/cncf/xds/go/xds/service/orca/v3"
|
|
)
|
|
|
|
func init() {
|
|
internal.AllowAnyMinReportingInterval = func(so *ServiceOptions) {
|
|
so.allowAnyMinReportingInterval = true
|
|
}
|
|
}
|
|
|
|
// minReportingInterval is the absolute minimum value supported for
|
|
// out-of-band metrics reporting from the ORCA service implementation
|
|
// provided by the orca package.
|
|
const minReportingInterval = 30 * time.Second
|
|
|
|
// Service provides an implementation of the OpenRcaService as defined in the
|
|
// [ORCA] service protos. Instances of this type must be created via calls to
|
|
// Register() or NewService().
|
|
//
|
|
// Server applications can use the SetXxx() and DeleteXxx() methods to record
|
|
// measurements corresponding to backend metrics, which eventually get pushed to
|
|
// clients who have initiated the SteamCoreMetrics streaming RPC.
|
|
//
|
|
// [ORCA]: https://github.com/cncf/xds/blob/main/xds/service/orca/v3/orca.proto
|
|
type Service struct {
|
|
v3orcaservicegrpc.UnimplementedOpenRcaServiceServer
|
|
|
|
// Minimum reporting interval, as configured by the user, or the default.
|
|
minReportingInterval time.Duration
|
|
|
|
// mu guards the custom metrics injected by the server application.
|
|
mu sync.RWMutex
|
|
cpu float64
|
|
memory float64
|
|
utilization map[string]float64
|
|
}
|
|
|
|
// ServiceOptions contains options to configure the ORCA service implementation.
|
|
type ServiceOptions struct {
|
|
// MinReportingInterval sets the lower bound for how often out-of-band
|
|
// metrics are reported on the streaming RPC initiated by the client. If
|
|
// unspecified, negative or less than the default value of 30s, the default
|
|
// is used. Clients may request a higher value as part of the
|
|
// StreamCoreMetrics streaming RPC.
|
|
MinReportingInterval time.Duration
|
|
|
|
// Allow a minReportingInterval which is less than the default of 30s.
|
|
// Used for testing purposes only.
|
|
allowAnyMinReportingInterval bool
|
|
}
|
|
|
|
// NewService creates a new ORCA service implementation configured using the
|
|
// provided options.
|
|
func NewService(opts ServiceOptions) (*Service, error) {
|
|
// The default minimum supported reporting interval value can be overridden
|
|
// for testing purposes through the orca internal package.
|
|
if !opts.allowAnyMinReportingInterval {
|
|
if opts.MinReportingInterval < 0 || opts.MinReportingInterval < minReportingInterval {
|
|
opts.MinReportingInterval = minReportingInterval
|
|
}
|
|
}
|
|
service := &Service{
|
|
minReportingInterval: opts.MinReportingInterval,
|
|
utilization: make(map[string]float64),
|
|
}
|
|
return service, nil
|
|
}
|
|
|
|
// Register creates a new ORCA service implementation configured using the
|
|
// provided options and registers the same on the provided service registrar.
|
|
func Register(s *grpc.Server, opts ServiceOptions) (*Service, error) {
|
|
service, err := NewService(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v3orcaservicegrpc.RegisterOpenRcaServiceServer(s, service)
|
|
return service, nil
|
|
}
|
|
|
|
// determineReportingInterval determines the reporting interval for out-of-band
|
|
// metrics. If the reporting interval is not specified in the request, or is
|
|
// negative or is less than the configured minimum (via
|
|
// ServiceOptions.MinReportingInterval), the latter is used. Else the value from
|
|
// the incoming request is used.
|
|
func (s *Service) determineReportingInterval(req *v3orcaservicepb.OrcaLoadReportRequest) time.Duration {
|
|
if req.GetReportInterval() == nil {
|
|
return s.minReportingInterval
|
|
}
|
|
dur := req.GetReportInterval().AsDuration()
|
|
if dur < s.minReportingInterval {
|
|
logger.Warningf("Received reporting interval %q is less than configured minimum: %v. Using default: %s", dur, s.minReportingInterval)
|
|
return s.minReportingInterval
|
|
}
|
|
return dur
|
|
}
|
|
|
|
func (s *Service) sendMetricsResponse(stream v3orcaservicegrpc.OpenRcaService_StreamCoreMetricsServer) error {
|
|
return stream.Send(s.toLoadReportProto())
|
|
}
|
|
|
|
// StreamCoreMetrics streams custom backend metrics injected by the server
|
|
// application.
|
|
func (s *Service) StreamCoreMetrics(req *v3orcaservicepb.OrcaLoadReportRequest, stream v3orcaservicegrpc.OpenRcaService_StreamCoreMetricsServer) error {
|
|
ticker := time.NewTicker(s.determineReportingInterval(req))
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
if err := s.sendMetricsResponse(stream); err != nil {
|
|
return err
|
|
}
|
|
// Send a response containing the currently recorded metrics
|
|
select {
|
|
case <-stream.Context().Done():
|
|
return status.Error(codes.Canceled, "Stream has ended.")
|
|
case <-ticker.C:
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetCPUUtilization records a measurement for the CPU utilization metric.
|
|
func (s *Service) SetCPUUtilization(val float64) {
|
|
s.mu.Lock()
|
|
s.cpu = val
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
// SetMemoryUtilization records a measurement for the memory utilization metric.
|
|
func (s *Service) SetMemoryUtilization(val float64) {
|
|
s.mu.Lock()
|
|
s.memory = val
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
// SetUtilization records a measurement for a utilization metric uniquely
|
|
// identifiable by name.
|
|
func (s *Service) SetUtilization(name string, val float64) {
|
|
s.mu.Lock()
|
|
s.utilization[name] = val
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
// DeleteUtilization deletes any previously recorded measurement for a
|
|
// utilization metric uniquely identifiable by name.
|
|
func (s *Service) DeleteUtilization(name string) {
|
|
s.mu.Lock()
|
|
delete(s.utilization, name)
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
// toLoadReportProto dumps the recorded measurements as an OrcaLoadReport proto.
|
|
func (s *Service) toLoadReportProto() *v3orcapb.OrcaLoadReport {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
util := make(map[string]float64, len(s.utilization))
|
|
for k, v := range s.utilization {
|
|
util[k] = v
|
|
}
|
|
return &v3orcapb.OrcaLoadReport{
|
|
CpuUtilization: s.cpu,
|
|
MemUtilization: s.memory,
|
|
Utilization: util,
|
|
}
|
|
}
|