|
|
@@ -3,8 +3,6 @@ package prom
|
|
|
import (
|
|
|
"context"
|
|
|
"fmt"
|
|
|
- "net/http"
|
|
|
- "strconv"
|
|
|
"time"
|
|
|
|
|
|
"github.com/Masterminds/semver/v3"
|
|
|
@@ -13,58 +11,12 @@ import (
|
|
|
"github.com/opencost/opencost/core/pkg/clusters"
|
|
|
"github.com/opencost/opencost/core/pkg/diagnostics"
|
|
|
"github.com/opencost/opencost/core/pkg/log"
|
|
|
- "github.com/opencost/opencost/core/pkg/protocol"
|
|
|
"github.com/opencost/opencost/core/pkg/source"
|
|
|
- "github.com/opencost/opencost/core/pkg/util/httputil"
|
|
|
- "github.com/opencost/opencost/core/pkg/util/json"
|
|
|
|
|
|
prometheus "github.com/prometheus/client_golang/api"
|
|
|
prometheusAPI "github.com/prometheus/client_golang/api/prometheus/v1"
|
|
|
)
|
|
|
|
|
|
-const (
|
|
|
- apiPrefix = "/api/v1"
|
|
|
- epAlertManagers = apiPrefix + "/alertmanagers"
|
|
|
- epLabelValues = apiPrefix + "/label/:name/values"
|
|
|
- epSeries = apiPrefix + "/series"
|
|
|
- epTargets = apiPrefix + "/targets"
|
|
|
- epSnapshot = apiPrefix + "/admin/tsdb/snapshot"
|
|
|
- epDeleteSeries = apiPrefix + "/admin/tsdb/delete_series"
|
|
|
- epCleanTombstones = apiPrefix + "/admin/tsdb/clean_tombstones"
|
|
|
- epConfig = apiPrefix + "/status/config"
|
|
|
- epFlags = apiPrefix + "/status/flags"
|
|
|
- epRules = apiPrefix + "/rules"
|
|
|
-)
|
|
|
-
|
|
|
-// helper for query range proxy requests
|
|
|
-func toStartEndStep(qp httputil.QueryParams) (start, end time.Time, step time.Duration, err error) {
|
|
|
- var e error
|
|
|
-
|
|
|
- ss := qp.Get("start", "")
|
|
|
- es := qp.Get("end", "")
|
|
|
- ds := qp.Get("duration", "")
|
|
|
- layout := "2006-01-02T15:04:05.000Z"
|
|
|
-
|
|
|
- start, e = time.Parse(layout, ss)
|
|
|
- if e != nil {
|
|
|
- err = fmt.Errorf("Error parsing time %s. Error: %s", ss, err)
|
|
|
- return
|
|
|
- }
|
|
|
- end, e = time.Parse(layout, es)
|
|
|
- if e != nil {
|
|
|
- err = fmt.Errorf("Error parsing time %s. Error: %s", es, err)
|
|
|
- return
|
|
|
- }
|
|
|
- step, e = time.ParseDuration(ds)
|
|
|
- if e != nil {
|
|
|
- err = fmt.Errorf("Error parsing duration %s. Error: %s", ds, err)
|
|
|
- return
|
|
|
- }
|
|
|
- err = nil
|
|
|
-
|
|
|
- return
|
|
|
-}
|
|
|
-
|
|
|
// creates a new help error which indicates the caller can retry and is non-fatal.
|
|
|
func newHelpRetryError(format string, args ...any) error {
|
|
|
formatWithHelp := format + "\nTroubleshooting help available at: %s"
|
|
|
@@ -179,191 +131,6 @@ func NewPrometheusDataSource(infoProvider clusters.ClusterInfoProvider, promConf
|
|
|
}, nil
|
|
|
}
|
|
|
|
|
|
-var proto = protocol.HTTP()
|
|
|
-
|
|
|
-// prometheusMetadata returns the metadata for the prometheus server
|
|
|
-func (pds *PrometheusDataSource) prometheusMetadata(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
-
|
|
|
- resp := proto.ToResponse(Validate(pds.promClient, pds.promConfig))
|
|
|
- proto.WriteResponse(w, resp)
|
|
|
-}
|
|
|
-
|
|
|
-// prometheusRecordingRules is a proxy for /rules against prometheus
|
|
|
-func (pds *PrometheusDataSource) prometheusRecordingRules(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
-
|
|
|
- u := pds.promClient.URL(epRules, nil)
|
|
|
-
|
|
|
- req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(w, "error creating Prometheus rule request: %s", err)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- _, body, err := pds.promClient.Do(r.Context(), req)
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(w, "error making Prometheus rule request: %s", err)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- w.Write(body)
|
|
|
-}
|
|
|
-
|
|
|
-// prometheusConfig returns the current configuration of the prometheus server
|
|
|
-func (pds *PrometheusDataSource) prometheusConfig(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
-
|
|
|
- pConfig := map[string]string{
|
|
|
- "address": pds.promConfig.ServerEndpoint,
|
|
|
- }
|
|
|
-
|
|
|
- body, err := json.Marshal(pConfig)
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(w, "Error marshalling prometheus config")
|
|
|
- } else {
|
|
|
- w.Write(body)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// prometheusTargets is a proxy for /targets against prometheus
|
|
|
-func (pds *PrometheusDataSource) prometheusTargets(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
-
|
|
|
- u := pds.promClient.URL(epTargets, nil)
|
|
|
-
|
|
|
- req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(w, "error creating Prometheus rule request: %s", err)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- _, body, err := pds.promClient.Do(r.Context(), req)
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(w, "error making Prometheus rule request: %s", err)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- w.Write(body)
|
|
|
-}
|
|
|
-
|
|
|
-// status returns the status of the prometheus client
|
|
|
-func (pds *PrometheusDataSource) status(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
-
|
|
|
- promServer := pds.promConfig.ServerEndpoint
|
|
|
-
|
|
|
- api := prometheusAPI.NewAPI(pds.promClient)
|
|
|
- result, err := api.Buildinfo(r.Context())
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(w, "Using Prometheus at %s, Error: %s", promServer, err)
|
|
|
- } else {
|
|
|
- fmt.Fprintf(w, "Using Prometheus at %s, version: %s", promServer, result.Version)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// prometheusQuery is a proxy for /query against prometheus
|
|
|
-func (pds *PrometheusDataSource) prometheusQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
-
|
|
|
- qp := httputil.NewQueryParams(r.URL.Query())
|
|
|
- query := qp.Get("query", "")
|
|
|
- if query == "" {
|
|
|
- proto.WriteResponse(w, proto.ToResponse(nil, fmt.Errorf("Query Parameter 'query' is unset'")))
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- // Attempt to parse time as either a unix timestamp or as an RFC3339 value
|
|
|
- var timeVal time.Time
|
|
|
- timeStr := qp.Get("time", "")
|
|
|
- if len(timeStr) > 0 {
|
|
|
- if t, err := strconv.ParseInt(timeStr, 10, 64); err == nil {
|
|
|
- timeVal = time.Unix(t, 0)
|
|
|
- } else if t, err := time.Parse(time.RFC3339, timeStr); err == nil {
|
|
|
- timeVal = t
|
|
|
- }
|
|
|
-
|
|
|
- // If time is given, but not parse-able, return an error
|
|
|
- if timeVal.IsZero() {
|
|
|
- http.Error(w, fmt.Sprintf("time must be a unix timestamp or RFC3339 value; illegal value given: %s", timeStr), http.StatusBadRequest)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- ctx := pds.promContexts.NewNamedContext(FrontendContextName)
|
|
|
- body, err := ctx.RawQuery(query, timeVal)
|
|
|
- if err != nil {
|
|
|
- proto.WriteResponse(w, proto.ToResponse(nil, fmt.Errorf("Error running query %s. Error: %s", query, err)))
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- w.Write(body) // prometheusQueryRange is a proxy for /query_range against prometheus
|
|
|
-}
|
|
|
-
|
|
|
-func (pds *PrometheusDataSource) prometheusQueryRange(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
-
|
|
|
- qp := httputil.NewQueryParams(r.URL.Query())
|
|
|
- query := qp.Get("query", "")
|
|
|
- if query == "" {
|
|
|
- fmt.Fprintf(w, "Error parsing query from request parameters.")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- start, end, duration, err := toStartEndStep(qp)
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(w, "error: %s", err)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- ctx := pds.promContexts.NewNamedContext(FrontendContextName)
|
|
|
- body, err := ctx.RawQueryRange(query, start, end, duration)
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(w, "Error running query %s. Error: %s", query, err)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- w.Write(body)
|
|
|
-}
|
|
|
-
|
|
|
-// promtheusQueueState returns the current state of the prometheus and thanos request queues
|
|
|
-func (pds *PrometheusDataSource) prometheusQueueState(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
-
|
|
|
- promQueueState, err := GetPrometheusQueueState(pds.promClient, pds.promConfig)
|
|
|
- if err != nil {
|
|
|
- proto.WriteResponse(w, proto.ToResponse(nil, err))
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- result := map[string]*PrometheusQueueState{
|
|
|
- "prometheus": promQueueState,
|
|
|
- }
|
|
|
-
|
|
|
- proto.WriteResponse(w, proto.ToResponse(result, nil))
|
|
|
-}
|
|
|
-
|
|
|
-// prometheusMetrics retrieves availability of Prometheus and Thanos metrics
|
|
|
-func (pds *PrometheusDataSource) prometheusMetrics(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
-
|
|
|
- promMetrics := GetPrometheusMetrics(pds.promClient, pds.promConfig, "")
|
|
|
-
|
|
|
- result := map[string][]*PrometheusDiagnostic{
|
|
|
- "prometheus": promMetrics,
|
|
|
- }
|
|
|
-
|
|
|
- proto.WriteResponse(w, proto.ToResponse(result, nil))
|
|
|
-}
|
|
|
-
|
|
|
func (pds *PrometheusDataSource) PrometheusClient() prometheus.Client {
|
|
|
return pds.promClient
|
|
|
}
|
|
|
@@ -376,22 +143,7 @@ func (pds *PrometheusDataSource) PrometheusContexts() *ContextFactory {
|
|
|
return pds.promContexts
|
|
|
}
|
|
|
|
|
|
-func (pds *PrometheusDataSource) RegisterEndPoints(router *httprouter.Router) {
|
|
|
- // endpoints migrated from server
|
|
|
- router.GET("/validatePrometheus", pds.prometheusMetadata)
|
|
|
- router.GET("/prometheusRecordingRules", pds.prometheusRecordingRules)
|
|
|
- router.GET("/prometheusConfig", pds.prometheusConfig)
|
|
|
- router.GET("/prometheusTargets", pds.prometheusTargets)
|
|
|
- router.GET("/status", pds.status)
|
|
|
-
|
|
|
- // prom query proxies
|
|
|
- router.GET("/prometheusQuery", pds.prometheusQuery)
|
|
|
- router.GET("/prometheusQueryRange", pds.prometheusQueryRange)
|
|
|
-
|
|
|
- // diagnostics
|
|
|
- router.GET("/diagnostics/requestQueue", pds.prometheusQueueState)
|
|
|
- router.GET("/diagnostics/prometheusMetrics", pds.prometheusMetrics)
|
|
|
-}
|
|
|
+func (pds *PrometheusDataSource) RegisterEndPoints(_ *httprouter.Router) {}
|
|
|
|
|
|
// RegisterDiagnostics registers any custom data source diagnostics with the `DiagnosticService` that can
|
|
|
// be used to report externally.
|