mirror of https://github.com/knative/caching.git
278 lines
8.8 KiB
Go
278 lines
8.8 KiB
Go
/*
|
|
Copyright 2018 The Knative 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 metrics
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.opencensus.io/stats"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"knative.dev/pkg/metrics/metricskey"
|
|
)
|
|
|
|
// metricsBackend specifies the backend to use for metrics
|
|
type metricsBackend string
|
|
|
|
const (
|
|
// BackendDestinationKey points to the config map entry key for metrics backend destination.
|
|
BackendDestinationKey = "metrics.backend-destination"
|
|
// DomainEnv points to the metrics domain env var.
|
|
DomainEnv = "METRICS_DOMAIN"
|
|
|
|
// The following keys are used to configure metrics reporting.
|
|
// See https://github.com/knative/serving/blob/main/config/core/configmaps/observability.yaml
|
|
// for details.
|
|
collectorAddressKey = "metrics.opencensus-address"
|
|
collectorSecureKey = "metrics.opencensus-require-tls"
|
|
reportingPeriodKey = "metrics.reporting-period-seconds"
|
|
|
|
defaultBackendEnvName = "DEFAULT_METRICS_BACKEND"
|
|
defaultPrometheusPort = 9090
|
|
defaultPrometheusReportingPeriod = 5
|
|
defaultOpenCensusReportingPeriod = 60
|
|
maxPrometheusPort = 65535
|
|
minPrometheusPort = 1024
|
|
defaultPrometheusHost = "0.0.0.0"
|
|
prometheusPortEnvName = "METRICS_PROMETHEUS_PORT"
|
|
prometheusHostEnvName = "METRICS_PROMETHEUS_HOST"
|
|
)
|
|
|
|
// TestOverrideBundleCount is a variable for testing to reduce the size (number of metrics) buffered before
|
|
// OpenCensus will send a bundled metric report. Only applies if non-zero.
|
|
var TestOverrideBundleCount = 0
|
|
|
|
// Metrics backend "enum".
|
|
const (
|
|
// prometheus is used for Prometheus backend
|
|
prometheus metricsBackend = "prometheus"
|
|
// openCensus is used to export to the OpenCensus Agent / Collector,
|
|
// which can send to many other services.
|
|
openCensus metricsBackend = "opencensus"
|
|
// none is used to export, well, nothing.
|
|
none metricsBackend = "none"
|
|
)
|
|
|
|
type metricsConfig struct {
|
|
// The metrics domain. e.g. "serving.knative.dev" or "build.knative.dev".
|
|
domain string
|
|
// The component that emits the metrics. e.g. "activator", "autoscaler".
|
|
component string
|
|
// The metrics backend destination.
|
|
backendDestination metricsBackend
|
|
// reportingPeriod specifies the interval between reporting aggregated views.
|
|
// If duration is less than or equal to zero, it enables the default behavior.
|
|
reportingPeriod time.Duration
|
|
|
|
// secret contains credentials for an exporter to use for authentication.
|
|
secret *corev1.Secret
|
|
|
|
// ---- OpenCensus specific below ----
|
|
// collectorAddress is the address of the collector, if not `localhost:55678`
|
|
collectorAddress string
|
|
|
|
// Require mutual TLS. Defaults to "false" because mutual TLS is hard to set up.
|
|
requireSecure bool
|
|
|
|
// ---- Prometheus specific below ----
|
|
// prometheusPort is the port where metrics are exposed in Prometheus
|
|
// format. It defaults to 9090.
|
|
prometheusPort int
|
|
|
|
// prometheusHost is the host where the metrics are exposed in Prometheus
|
|
// format. It defaults to "0.0.0.0"
|
|
prometheusHost string
|
|
}
|
|
|
|
// record applies the `ros` Options to each measurement in `mss` and then records the resulting
|
|
// measurements in the metricsConfig's designated backend.
|
|
func (mc *metricsConfig) record(ctx context.Context, mss []stats.Measurement, ros ...stats.Options) error {
|
|
if mc == nil || mc.backendDestination == none {
|
|
// Don't record data points if the metric config is not initialized yet or if
|
|
// the defined backend is "none" explicitly.
|
|
return nil
|
|
}
|
|
|
|
opt, err := optionForResource(metricskey.GetResource(ctx))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ros = append(ros, opt)
|
|
|
|
return stats.RecordWithOptions(ctx, append(ros, stats.WithMeasurements(mss...))...)
|
|
}
|
|
|
|
func createMetricsConfig(_ context.Context, ops ExporterOptions) (*metricsConfig, error) {
|
|
var mc metricsConfig
|
|
mc.domain = ops.Domain
|
|
if ops.Component == "" {
|
|
return nil, errors.New("metrics component name cannot be empty")
|
|
}
|
|
mc.component = ops.Component
|
|
|
|
if ops.ConfigMap == nil {
|
|
return nil, errors.New("metrics config map cannot be empty")
|
|
}
|
|
m := ops.ConfigMap
|
|
// Read backend setting from environment variable first
|
|
backend := os.Getenv(defaultBackendEnvName)
|
|
if backend == "" {
|
|
// Use Prometheus if DEFAULT_METRICS_BACKEND does not exist or is empty
|
|
backend = string(prometheus)
|
|
}
|
|
// Override backend if it is set in the config map.
|
|
if backendFromConfig, ok := m[BackendDestinationKey]; ok {
|
|
backend = backendFromConfig
|
|
}
|
|
|
|
switch lb := metricsBackend(strings.ToLower(backend)); lb {
|
|
case prometheus, openCensus, none:
|
|
mc.backendDestination = lb
|
|
default:
|
|
return nil, fmt.Errorf("unsupported metrics backend value %q", backend)
|
|
}
|
|
|
|
switch mc.backendDestination {
|
|
case openCensus:
|
|
if ops.Domain == "" {
|
|
return nil, errors.New("metrics domain cannot be empty")
|
|
}
|
|
mc.collectorAddress = ops.ConfigMap[collectorAddressKey]
|
|
if isSecure := ops.ConfigMap[collectorSecureKey]; isSecure != "" {
|
|
var err error
|
|
if mc.requireSecure, err = strconv.ParseBool(isSecure); err != nil {
|
|
return nil, fmt.Errorf("invalid %s value %q", collectorSecureKey, isSecure)
|
|
}
|
|
|
|
if mc.requireSecure {
|
|
mc.secret, err = getOpenCensusSecret(ops.Component, ops.Secrets)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
case prometheus:
|
|
pp := ops.PrometheusPort
|
|
if pp == 0 {
|
|
var err error
|
|
pp, err = prometheusPort()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to determine Prometheus port: %w", err)
|
|
}
|
|
}
|
|
|
|
if pp < minPrometheusPort || pp > maxPrometheusPort {
|
|
return nil, fmt.Errorf("invalid port %d, should be between %d and %d",
|
|
pp, minPrometheusPort, maxPrometheusPort)
|
|
}
|
|
|
|
mc.prometheusPort = pp
|
|
mc.prometheusHost = prometheusHost()
|
|
}
|
|
|
|
// If reporting period is specified, use the value from the configuration.
|
|
// If not, set a default value based on the selected backend.
|
|
// Each exporter makes different promises about what the lowest supported
|
|
// reporting period is. For OpenCensus, this value is 1 minute.
|
|
// For Prometheus, we will use a lower value since the exporter doesn't
|
|
// push anything but just responds to pull requests, and shorter durations
|
|
// do not really hurt the performance and we rely on the scraping configuration.
|
|
if repStr := m[reportingPeriodKey]; repStr != "" {
|
|
repInt, err := strconv.Atoi(repStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid %s value %q", reportingPeriodKey, repStr)
|
|
}
|
|
mc.reportingPeriod = time.Duration(repInt) * time.Second
|
|
} else {
|
|
switch mc.backendDestination {
|
|
case openCensus:
|
|
mc.reportingPeriod = defaultOpenCensusReportingPeriod * time.Second
|
|
case prometheus:
|
|
mc.reportingPeriod = defaultPrometheusReportingPeriod * time.Second
|
|
}
|
|
}
|
|
return &mc, nil
|
|
}
|
|
|
|
// Domain holds the metrics domain to use for surfacing metrics.
|
|
func Domain() string {
|
|
if domain := os.Getenv(DomainEnv); domain != "" {
|
|
return domain
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// prometheusPort returns the TCP port number configured via the environment
|
|
// for the Prometheus metrics exporter if it's set, a default value otherwise.
|
|
// No validation is performed on the port value, other than ensuring that value
|
|
// is a valid port number (16-bit unsigned integer).
|
|
func prometheusPort() (int, error) {
|
|
ppStr := os.Getenv(prometheusPortEnvName)
|
|
if ppStr == "" {
|
|
return defaultPrometheusPort, nil
|
|
}
|
|
|
|
pp, err := strconv.ParseInt(ppStr, 10, 16)
|
|
if err != nil {
|
|
return -1, fmt.Errorf("the environment variable %q could not be parsed as a port number: %w",
|
|
prometheusPortEnvName, err)
|
|
}
|
|
|
|
return int(pp), nil
|
|
}
|
|
|
|
// prometheusHost returns the host configured via the environment
|
|
// for the Prometheus metrics exporter if it's set, a default value otherwise.
|
|
// No validation is done here.
|
|
func prometheusHost() string {
|
|
phStr := os.Getenv(prometheusHostEnvName)
|
|
if phStr == "" {
|
|
return defaultPrometheusHost
|
|
}
|
|
return phStr
|
|
}
|
|
|
|
// JSONToOptions converts a json string to ExporterOptions.
|
|
func JSONToOptions(jsonOpts string) (*ExporterOptions, error) {
|
|
var opts ExporterOptions
|
|
if jsonOpts == "" {
|
|
return nil, errors.New("json options string is empty")
|
|
}
|
|
|
|
if err := json.Unmarshal([]byte(jsonOpts), &opts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &opts, nil
|
|
}
|
|
|
|
// OptionsToJSON converts an ExporterOptions object to a JSON string.
|
|
func OptionsToJSON(opts *ExporterOptions) (string, error) {
|
|
if opts == nil {
|
|
return "", nil
|
|
}
|
|
|
|
jsonOpts, err := json.Marshal(opts)
|
|
return string(jsonOpts), err
|
|
}
|