<commit_before>// Copyright 2016 The Prometheus 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 v1

import (
	"context"
	"fmt"
	"math"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"
	"unsafe"

	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/grafana/regexp"
	jsoniter "github.com/json-iterator/go"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/model"
	"github.com/prometheus/common/route"

	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/model/exemplar"
	"github.com/prometheus/prometheus/model/histogram"
	"github.com/prometheus/prometheus/model/labels"
	"github.com/prometheus/prometheus/model/textparse"
	"github.com/prometheus/prometheus/model/timestamp"
	"github.com/prometheus/prometheus/promql"
	"github.com/prometheus/prometheus/promql/parser"
	"github.com/prometheus/prometheus/rules"
	"github.com/prometheus/prometheus/scrape"
	"github.com/prometheus/prometheus/storage"
	"github.com/prometheus/prometheus/storage/remote"
	"github.com/prometheus/prometheus/tsdb"
	"github.com/prometheus/prometheus/tsdb/index"
	"github.com/prometheus/prometheus/util/httputil"
	"github.com/prometheus/prometheus/util/stats"
)

type status string

const (
	statusSuccess status = "success"
	statusError   status = "error"
)

type errorType string

const (
	errorNone        errorType = ""
	errorTimeout     errorType = "timeout"
	errorCanceled    errorType = "canceled"
	errorExec        errorType = "execution"
	errorBadData     errorType = "bad_data"
	errorInternal    errorType = "internal"
	errorUnavailable errorType = "unavailable"
	errorNotFound    errorType = "not_found"
)

var LocalhostRepresentations = []string{"127.0.0.1", "localhost", "::1"}

type apiError struct {
	typ errorType
	err error
}

func (e *apiError) Error() string {
	return fmt.Sprintf("%s: %s", e.typ, e.err)
}

// TargetRetriever provides the list of active/dropped targets to scrape or not.
type TargetRetriever interface {
	TargetsActive() map[string][]*scrape.Target
	TargetsDropped() map[string][]*scrape.Target
}

// AlertmanagerRetriever provides a list of all/dropped AlertManager URLs.
type AlertmanagerRetriever interface {
	Alertmanagers() []*url.URL
	DroppedAlertmanagers() []*url.URL
}

// RulesRetriever provides a list of active rules and alerts.
type RulesRetriever interface {
	RuleGroups() []*rules.Group
	AlertingRules() []*rules.AlertingRule
}

type StatsRenderer func(context.Context, *stats.Statistics, string) stats.QueryStats

func defaultStatsRenderer(ctx context.Context, s *stats.Statistics, param string) stats.QueryStats {
	if param != "" {
		return stats.NewQueryStats(s)
	}
	return nil
}

// PrometheusVersion contains build information about Prometheus.
type PrometheusVersion struct {
	Version   string `json:"version"`
	Revision  string `json:"revision"`
	Branch    string `json:"branch"`
	BuildUser string `json:"buildUser"`
	BuildDate string `json:"buildDate"`
	GoVersion string `json:"goVersion"`
}

// RuntimeInfo contains runtime information about Prometheus.
type RuntimeInfo struct {
	StartTime           time.Time `json:"startTime"`
	CWD                 string    `json:"CWD"`
	ReloadConfigSuccess bool      `json:"reloadConfigSuccess"`
	LastConfigTime      time.Time `json:"lastConfigTime"`
	CorruptionCount     int64     `json:"corruptionCount"`
	GoroutineCount      int       `json:"goroutineCount"`
	GOMAXPROCS          int       `json:"GOMAXPROCS"`
	GOGC                string    `json:"GOGC"`
	GODEBUG             string    `json:"GODEBUG"`
	StorageRetention    string    `json:"storageRetention"`
}

type response struct {
	Status    status      `json:"status"`
	Data      interface{} `json:"data,omitempty"`
	ErrorType errorType   `json:"errorType,omitempty"`
	Error     string      `json:"error,omitempty"`
	Warnings  []string    `json:"warnings,omitempty"`
}

type apiFuncResult struct {
	data      interface{}
	err       *apiError
	warnings  storage.Warnings
	finalizer func()
}

type apiFunc func(r *http.Request) apiFuncResult

// TSDBAdminStats defines the tsdb interfaces used by the v1 API for admin operations as well as statistics.
type TSDBAdminStats interface {
	CleanTombstones() error
	Delete(mint, maxt int64, ms ...*labels.Matcher) error
	Snapshot(dir string, withHead bool) error
	Stats(statsByLabelName string) (*tsdb.Stats, error)
	WALReplayStatus() (tsdb.WALReplayStatus, error)
}

// QueryEngine defines the interface for the *promql.Engine, so it can be replaced, wrapped or mocked.
type QueryEngine interface {
	SetQueryLogger(l promql.QueryLogger)
	NewInstantQuery(q storage.Queryable, opts *promql.QueryOpts, qs string, ts time.Time) (promql.Query, error)
	NewRangeQuery(q storage.Queryable, opts *promql.QueryOpts, qs string, start, end time.Time, interval time.Duration) (promql.Query, error)
}

// API can register a set of endpoints in a router and handle
// them using the provided storage and query engine.
type API struct {
	Queryable         storage.SampleAndChunkQueryable
	QueryEngine       QueryEngine
	ExemplarQueryable storage.ExemplarQueryable

	targetRetriever       func(context.Context) TargetRetriever
	alertmanagerRetriever func(context.Context) AlertmanagerRetriever
	rulesRetriever        func(context.Context) RulesRetriever
	now                   func() time.Time
	config                func() config.Config
	flagsMap              map[string]string
	ready                 func(http.HandlerFunc) http.HandlerFunc
	globalURLOptions      GlobalURLOptions

	db            TSDBAdminStats
	dbDir         string
	enableAdmin   bool
	logger        log.Logger
	CORSOrigin    *regexp.Regexp
	buildInfo     *PrometheusVersion
	runtimeInfo   func() (RuntimeInfo, error)
	gatherer      prometheus.Gatherer
	isAgent       bool
	statsRenderer StatsRenderer

	remoteWriteHandler http.Handler
	remoteReadHandler  http.Handler
}

func init() {
	jsoniter.RegisterTypeEncoderFunc("promql.Series", marshalSeriesJSON, marshalSeriesJSONIsEmpty)
	jsoniter.RegisterTypeEncoderFunc("promql.Sample", marshalSampleJSON, marshalSampleJSONIsEmpty)
	jsoniter.RegisterTypeEncoderFunc("promql.Point", marshalPointJSON, marshalPointJSONIsEmpty)
	jsoniter.RegisterTypeEncoderFunc("exemplar.Exemplar", marshalExemplarJSON, marshalExemplarJSONEmpty)
}

// NewAPI returns an initialized API type.
func NewAPI(
	qe QueryEngine,
	q storage.SampleAndChunkQueryable,
	ap storage.Appendable,
	eq storage.ExemplarQueryable,
	tr func(context.Context) TargetRetriever,
	ar func(context.Context) AlertmanagerRetriever,
	configFunc func() config.Config,
	flagsMap map[string]string,
	globalURLOptions GlobalURLOptions,
	readyFunc func(http.HandlerFunc) http.HandlerFunc,
	db TSDBAdminStats,
	dbDir string,
	enableAdmin bool,
	logger log.Logger,
	rr func(context.Context) RulesRetriever,
	remoteReadSampleLimit int,
	remoteReadConcurrencyLimit int,
	remoteReadMaxBytesInFrame int,
	isAgent bool,
	CORSOrigin *regexp.Regexp,
	runtimeInfo func() (RuntimeInfo, error),
	buildInfo *PrometheusVersion,
	gatherer prometheus.Gatherer,
	registerer prometheus.Registerer,
	statsRenderer StatsRenderer,
) *API {
	a := &API{
		QueryEngine:       qe,
		Queryable:         q,
		ExemplarQueryable: eq,

		targetRetriever:       tr,
		alertmanagerRetriever: ar,

		now:              time.Now,
		config:           configFunc,
		flagsMap:         flagsMap,
		ready:            readyFunc,
		globalURLOptions: globalURLOptions,
		db:               db,
		dbDir:            dbDir,
		enableAdmin:      enableAdmin,
		rulesRetriever:   rr,
		logger:           logger,
		CORSOrigin:       CORSOrigin,
		runtimeInfo:      runtimeInfo,
		buildInfo:        buildInfo,
		gatherer:         gatherer,
		isAgent:          isAgent,
		statsRenderer:    defaultStatsRenderer,

		remoteReadHandler: remote.NewReadHandler(logger, registerer, q, configFunc, remoteReadSampleLimit, remoteReadConcurrencyLimit, remoteReadMaxBytesInFrame),
	}

	if statsRenderer != nil {
		a.statsRenderer = statsRenderer
	}

	if ap != nil {
		a.remoteWriteHandler = remote.NewWriteHandler(logger, ap)
	}

	return a
}

func setUnavailStatusOnTSDBNotReady(r apiFuncResult) apiFuncResult {
	if r.err != nil && errors.Cause(r.err.err) == tsdb.ErrNotReady {
		r.err.typ = errorUnavailable
	}
	return r
}

// Register the API's endpoints in the given router.
func (api *API) Register(r *route.Router) {
	wrap := func(f apiFunc) http.HandlerFunc {
		hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			httputil.SetCORS(w, api.CORSOrigin, r)
			result := setUnavailStatusOnTSDBNotReady(f(r))
			if result.finalizer != nil {
				defer result.finalizer()
			}
			if result.err != nil {
				api.respondError(w, result.err, result.data)
				return
			}

			if result.data != nil {
				api.respond(w, result.data, result.warnings)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})
		return api.ready(httputil.CompressionHandler{
			Handler: hf,
		}.ServeHTTP)
	}

	wrapAgent := func(f apiFunc) http.HandlerFunc {
		return wrap(func(r *http.Request) apiFuncResult {
			if api.isAgent {
				return apiFuncResult{nil, &apiError{errorExec, errors.New("unavailable with Prometheus Agent")}, nil, nil}
			}
			return f(r)
		})
	}

	r.Options("/*path", wrap(api.options))

	r.Get("/query", wrapAgent(api.query))
	r.Post("/query", wrapAgent(api.query))
	r.Get("/query_range", wrapAgent(api.queryRange))
	r.Post("/query_range", wrapAgent(api.queryRange))
	r.Get("/query_exemplars", wrapAgent(api.queryExemplars))
	r.Post("/query_exemplars", wrapAgent(api.queryExemplars))

	r.Get("/labels", wrapAgent(api.labelNames))
	r.Post("/labels", wrapAgent(api.labelNames))
	r.Get("/label/:name/values", wrapAgent(api.labelValues))

	r.Get("/series", wrapAgent(api.series))
	r.Post("/series", wrapAgent(api.series))
	r.Del("/series", wrapAgent(api.dropSeries))

	r.Get("/targets", wrap(api.targets))
	r.Get("/targets/metadata", wrap(api.targetMetadata))
	r.Get("/alertmanagers", wrapAgent(api.alertmanagers))

	r.Get("/metadata", wrap(api.metricMetadata))

	r.Get("/status/config", wrap(api.serveConfig))
	r.Get("/status/runtimeinfo", wrap(api.serveRuntimeInfo))
	r.Get("/status/buildinfo", wrap(api.serveBuildInfo))
	r.Get("/status/flags", wrap(api.serveFlags))
	r.Get("/status/tsdb", wrapAgent(api.serveTSDBStatus))
	r.Get("/status/walreplay", api.serveWALReplayStatus)
	r.Post("/read", api.ready(api.remoteRead))
	r.Post("/write", api.ready(api.remoteWrite))

	r.Get("/alerts", wrapAgent(api.alerts))
	r.Get("/rules", wrapAgent(api.rules))

	// Admin APIs
	r.Post("/admin/tsdb/delete_series", wrapAgent(api.deleteSeries))
	r.Post("/admin/tsdb/clean_tombstones", wrapAgent(api.cleanTombstones))
	r.Post("/admin/tsdb/snapshot", wrapAgent(api.snapshot))

	r.Put("/admin/tsdb/delete_series", wrapAgent(api.deleteSeries))
	r.Put("/admin/tsdb/clean_tombstones", wrapAgent(api.cleanTombstones))
	r.Put("/admin/tsdb/snapshot", wrapAgent(api.snapshot))
}

type queryData struct {
	ResultType parser.ValueType `json:"resultType"`
	Result     parser.Value     `json:"result"`
	Stats      stats.QueryStats `json:"stats,omitempty"`
}

func invalidParamError(err error, parameter string) apiFuncResult {
	return apiFuncResult{nil, &apiError{
		errorBadData, errors.Wrapf(err, "invalid parameter %q", parameter),
	}, nil, nil}
}

func (api *API) options(r *http.Request) apiFuncResult {
	return apiFuncResult{nil, nil, nil, nil}
}

func (api *API) query(r *http.Request) (result apiFuncResult) {
	ts, err := parseTimeParam(r, "time", api.now())
	if err != nil {
		return invalidParamError(err, "time")
	}
	ctx := r.Context()
	if to := r.FormValue("timeout"); to != "" {
		var cancel context.CancelFunc
		timeout, err := parseDuration(to)
		if err != nil {
			return invalidParamError(err, "timeout")
		}

		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}

	opts := extractQueryOpts(r)
	qry, err := api.QueryEngine.NewInstantQuery(api.Queryable, opts, r.FormValue("query"), ts)
	if err != nil {
		return invalidParamError(err, "query")
	}

	// From now on, we must only return with a finalizer in the result (to
	// be called by the caller) or call qry.Close ourselves (which is
	// required in the case of a panic).
	defer func() {
		if result.finalizer == nil {
			qry.Close()
		}
	}()

	ctx = httputil.ContextFromRequest(ctx, r)

	res := qry.Exec(ctx)
	if res.Err != nil {
		return apiFuncResult{nil, returnAPIError(res.Err), res.Warnings, qry.Close}
	}

	// Optional stats field in response if parameter "stats" is not empty.
	sr := api.statsRenderer
	if sr == nil {
		sr = defaultStatsRenderer
	}
	qs := sr(ctx, qry.Stats(), r.FormValue("stats"))

	return apiFuncResult{&queryData{
		ResultType: res.Value.Type(),
		Result:     res.Value,
		Stats:      qs,
	}, nil, res.Warnings, qry.Close}
}

func extractQueryOpts(r *http.Request) *promql.QueryOpts {
	return &promql.QueryOpts{
		EnablePerStepStats: r.FormValue("stats") == "all",
	}
}

func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
	start, err := parseTime(r.FormValue("start"))
	if err != nil {
		return invalidParamError(err, "start")
	}
	end, err := parseTime(r.FormValue("end"))
	if err != nil {
		return invalidParamError(err, "end")
	}
	if end.Before(start) {
		return invalidParamError(errors.New("end timestamp must not be before start time"), "end")
	}

	step, err := parseDuration(r.FormValue("step"))
	if err != nil {
		return invalidParamError(err, "step")
	}

	if step <= 0 {
		return invalidParamError(errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer"), "step")
	}

	// For safety, limit the number of returned points per timeseries.
	// This is sufficient for 60s resolution for a week or 1h resolution for a year.
	if end.Sub(start)/step > 11000 {
		err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
		return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
	}

	ctx := r.Context()
	if to := r.FormValue("timeout"); to != "" {
		var cancel context.CancelFunc
		timeout, err := parseDuration(to)
		if err != nil {
			return invalidParamError(err, "timeout")
		}

		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}

	opts := extractQueryOpts(r)
	qry, err := api.QueryEngine.NewRangeQuery(api.Queryable, opts, r.FormValue("query"), start, end, step)
	if err != nil {
		return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
	}
	// From now on, we must only return with a finalizer in the result (to
	// be called by the caller) or call qry.Close ourselves (which is
	// required in the case of a panic).
	defer func() {
		if result.finalizer == nil {
			qry.Close()
		}
	}()

	ctx = httputil.ContextFromRequest(ctx, r)

	res := qry.Exec(ctx)
	if res.Err != nil {
		return apiFuncResult{nil, returnAPIError(res.Err), res.Warnings, qry.Close}
	}

	// Optional stats field in response if parameter "stats" is not empty.
	sr := api.statsRenderer
	if sr == nil {
		sr = defaultStatsRenderer
	}
	qs := sr(ctx, qry.Stats(), r.FormValue("stats"))

	return apiFuncResult{&queryData{
		ResultType: res.Value.Type(),
		Result:     res.Value,
		Stats:      qs,
	}, nil, res.Warnings, qry.Close}
}

// TODO: remove this when we have sparse histogram support in PromQL.
// This is a hack to query sparse histogram for buckets.
//func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
//	start, err := parseTime(r.FormValue("start"))
//	if err != nil {
//		return invalidParamError(err, "start")
//	}
//	end, err := parseTime(r.FormValue("end"))
//	if err != nil {
//		return invalidParamError(err, "end")
//	}
//	if end.Before(start) {
//		return invalidParamError(errors.New("end timestamp must not be before start time"), "end")
//	}
//
//	step, err := parseDuration(r.FormValue("step"))
//	if err != nil {
//		return invalidParamError(err, "step")
//	}
//
//	if step <= 0 {
//		return invalidParamError(errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer"), "step")
//	}
//
//	// For safety, limit the number of returned points per timeseries.
//	// This is sufficient for 60s resolution for a week or 1h resolution for a year.
//	if end.Sub(start)/step > 11000 {
//		err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
//		return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
//	}
//
//	ctx := r.Context()
//	if to := r.FormValue("timeout"); to != "" {
//		var cancel context.CancelFunc
//		timeout, err := parseDuration(to)
//		if err != nil {
//			return invalidParamError(err, "timeout")
//		}
//
//		ctx, cancel = context.WithTimeout(ctx, timeout)
//		defer cancel()
//	}
//
//	expr, err := parser.ParseExpr(r.FormValue("query"))
//	if err != nil {
//		return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
//	}
//
//	selectors := parser.ExtractSelectors(expr)
//	if len(selectors) < 1 {
//		return apiFuncResult{nil, nil, nil, nil}
//	}
//
//	if len(selectors) > 1 {
//		return apiFuncResult{nil, &apiError{errorBadData, errors.New("need exactly 1 selector")}, nil, nil}
//	}
//
//	hasRate, rateDuration := false, time.Duration(0)
//	parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
//		switch n := node.(type) {
//		case *parser.Call:
//			if n.Func.Name == "rate" {
//				hasRate = true
//				rateDuration = n.Args[0].(*parser.MatrixSelector).Range
//				return errors.New("stop it here")
//			}
//		}
//		return nil
//	})
//	var numRateSamples int
//	if hasRate {
//		numRateSamples = int(end.Sub(start)/step + 1)
//		if start.Add(time.Duration(numRateSamples-1) * step).After(end) {
//			numRateSamples--
//		}
//		start = start.Add(-rateDuration) // Adjusting for the first point lookback.
//	}
//
//	q, err := api.Queryable.Querier(ctx, timestamp.FromTime(start), timestamp.FromTime(end))
//	if err != nil {
//		return apiFuncResult{nil, &apiError{errorExec, err}, nil, nil}
//	}
//
//	res := promql.Matrix{}
//	ss := q.Select(true, nil, selectors[0]...)
//
//	for ss.Next() {
//		resSeries := make(map[float64]promql.Series) // le -> series.
//
//		s := ss.At()
//		it := s.Iterator()
//		for it.Next() { // Per histogram.
//			t, h := it.AtHistogram()
//			buckets := histogram.CumulativeExpandSparseHistogram(h)
//			for buckets.Next() {
//				// Every bucket is a different series with different "le".
//				b := buckets.At()
//				rs, ok := resSeries[b.Le]
//				if !ok {
//					rs = promql.Series{
//						Metric: append(
//							s.Labels(),
//							labels.Label{Name: "le", Value: fmt.Sprintf("%.16f", b.Le)}, // TODO: Set some precision for 'le'?
//						),
//					}
//					sort.Sort(rs.Metric)
//					resSeries[b.Le] = rs
//				}
//
//				rs.Points = append(rs.Points, promql.Point{
//					T: t,
//					V: float64(b.Count),
//				})
//				resSeries[b.Le] = rs
//			}
//			if buckets.Err() != nil {
//				return apiFuncResult{nil, &apiError{errorExec, buckets.Err()}, nil, nil}
//			}
//		}
//
//		for _, rs := range resSeries {
//			res = append(res, rs)
//		}
//	}
//
//	if hasRate {
//		newRes := make(promql.Matrix, len(res))
//		for i := range newRes {
//			newRes[i].Metric = res[i].Metric
//			points := make([]promql.Point, numRateSamples)
//
//			rawPoints := res[i].Points
//
//			startIdx, endIdx := 0, 0
//			for idx := range points {
//				pointTime := start.Add(time.Duration(idx) * step)
//				lookbackTime := pointTime.Add(-rateDuration)
//				points[idx].T = timestamp.FromTime(pointTime)
//				if len(rawPoints) == 0 {
//					continue
//				}
//
//				for startIdx < len(rawPoints) && timestamp.Time(rawPoints[startIdx].T).Before(lookbackTime) {
//					startIdx++
//				}
//				if startIdx >= len(rawPoints) {
//					startIdx = len(rawPoints) - 1
//				}
//
//				for endIdx < len(rawPoints) && timestamp.Time(rawPoints[endIdx].T).Before(pointTime) {
//					endIdx++
//				}
//				if endIdx >= len(rawPoints) {
//					endIdx = len(rawPoints) - 1
//				} else if timestamp.Time(rawPoints[endIdx].T).After(pointTime) && (len(rawPoints) == 1 || endIdx == 0) {
//					continue
//				} else {
//					endIdx--
//				}
//
//				valDiff := rawPoints[endIdx].V - rawPoints[startIdx].V
//				timeDiffSeconds := float64(timestamp.Time(rawPoints[endIdx].T).Sub(timestamp.Time(rawPoints[startIdx].T))) / float64(time.Second)
//
//				if timeDiffSeconds != 0 {
//					points[idx].V = valDiff / timeDiffSeconds
//				}
//			}
//
//			newRes[i].Points = points
//		}
//
//		res = newRes
//	}
//
//	sort.Sort(res)
//
//	return apiFuncResult{&queryData{
//		ResultType: res.Type(),
//		Result:     res,
//	}, nil, nil, nil}
//}

func (api *API) queryExemplars(r *http.Request) apiFuncResult {
	start, err := parseTimeParam(r, "start", minTime)
	if err != nil {<commit_msg>Histogram: Remove obsolete work-around code<commit_after>// Copyright 2016 The Prometheus 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 v1

import (
	"context"
	"fmt"
	"math"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"
	"unsafe"

	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/grafana/regexp"
	jsoniter "github.com/json-iterator/go"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/model"
	"github.com/prometheus/common/route"

	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/model/exemplar"
	"github.com/prometheus/prometheus/model/histogram"
	"github.com/prometheus/prometheus/model/labels"
	"github.com/prometheus/prometheus/model/textparse"
	"github.com/prometheus/prometheus/model/timestamp"
	"github.com/prometheus/prometheus/promql"
	"github.com/prometheus/prometheus/promql/parser"
	"github.com/prometheus/prometheus/rules"
	"github.com/prometheus/prometheus/scrape"
	"github.com/prometheus/prometheus/storage"
	"github.com/prometheus/prometheus/storage/remote"
	"github.com/prometheus/prometheus/tsdb"
	"github.com/prometheus/prometheus/tsdb/index"
	"github.com/prometheus/prometheus/util/httputil"
	"github.com/prometheus/prometheus/util/stats"
)

type status string

const (
	statusSuccess status = "success"
	statusError   status = "error"
)

type errorType string

const (
	errorNone        errorType = ""
	errorTimeout     errorType = "timeout"
	errorCanceled    errorType = "canceled"
	errorExec        errorType = "execution"
	errorBadData     errorType = "bad_data"
	errorInternal    errorType = "internal"
	errorUnavailable errorType = "unavailable"
	errorNotFound    errorType = "not_found"
)

var LocalhostRepresentations = []string{"127.0.0.1", "localhost", "::1"}

type apiError struct {
	typ errorType
	err error
}

func (e *apiError) Error() string {
	return fmt.Sprintf("%s: %s", e.typ, e.err)
}

// TargetRetriever provides the list of active/dropped targets to scrape or not.
type TargetRetriever interface {
	TargetsActive() map[string][]*scrape.Target
	TargetsDropped() map[string][]*scrape.Target
}

// AlertmanagerRetriever provides a list of all/dropped AlertManager URLs.
type AlertmanagerRetriever interface {
	Alertmanagers() []*url.URL
	DroppedAlertmanagers() []*url.URL
}

// RulesRetriever provides a list of active rules and alerts.
type RulesRetriever interface {
	RuleGroups() []*rules.Group
	AlertingRules() []*rules.AlertingRule
}

type StatsRenderer func(context.Context, *stats.Statistics, string) stats.QueryStats

func defaultStatsRenderer(ctx context.Context, s *stats.Statistics, param string) stats.QueryStats {
	if param != "" {
		return stats.NewQueryStats(s)
	}
	return nil
}

// PrometheusVersion contains build information about Prometheus.
type PrometheusVersion struct {
	Version   string `json:"version"`
	Revision  string `json:"revision"`
	Branch    string `json:"branch"`
	BuildUser string `json:"buildUser"`
	BuildDate string `json:"buildDate"`
	GoVersion string `json:"goVersion"`
}

// RuntimeInfo contains runtime information about Prometheus.
type RuntimeInfo struct {
	StartTime           time.Time `json:"startTime"`
	CWD                 string    `json:"CWD"`
	ReloadConfigSuccess bool      `json:"reloadConfigSuccess"`
	LastConfigTime      time.Time `json:"lastConfigTime"`
	CorruptionCount     int64     `json:"corruptionCount"`
	GoroutineCount      int       `json:"goroutineCount"`
	GOMAXPROCS          int       `json:"GOMAXPROCS"`
	GOGC                string    `json:"GOGC"`
	GODEBUG             string    `json:"GODEBUG"`
	StorageRetention    string    `json:"storageRetention"`
}

type response struct {
	Status    status      `json:"status"`
	Data      interface{} `json:"data,omitempty"`
	ErrorType errorType   `json:"errorType,omitempty"`
	Error     string      `json:"error,omitempty"`
	Warnings  []string    `json:"warnings,omitempty"`
}

type apiFuncResult struct {
	data      interface{}
	err       *apiError
	warnings  storage.Warnings
	finalizer func()
}

type apiFunc func(r *http.Request) apiFuncResult

// TSDBAdminStats defines the tsdb interfaces used by the v1 API for admin operations as well as statistics.
type TSDBAdminStats interface {
	CleanTombstones() error
	Delete(mint, maxt int64, ms ...*labels.Matcher) error
	Snapshot(dir string, withHead bool) error
	Stats(statsByLabelName string) (*tsdb.Stats, error)
	WALReplayStatus() (tsdb.WALReplayStatus, error)
}

// QueryEngine defines the interface for the *promql.Engine, so it can be replaced, wrapped or mocked.
type QueryEngine interface {
	SetQueryLogger(l promql.QueryLogger)
	NewInstantQuery(q storage.Queryable, opts *promql.QueryOpts, qs string, ts time.Time) (promql.Query, error)
	NewRangeQuery(q storage.Queryable, opts *promql.QueryOpts, qs string, start, end time.Time, interval time.Duration) (promql.Query, error)
}

// API can register a set of endpoints in a router and handle
// them using the provided storage and query engine.
type API struct {
	Queryable         storage.SampleAndChunkQueryable
	QueryEngine       QueryEngine
	ExemplarQueryable storage.ExemplarQueryable

	targetRetriever       func(context.Context) TargetRetriever
	alertmanagerRetriever func(context.Context) AlertmanagerRetriever
	rulesRetriever        func(context.Context) RulesRetriever
	now                   func() time.Time
	config                func() config.Config
	flagsMap              map[string]string
	ready                 func(http.HandlerFunc) http.HandlerFunc
	globalURLOptions      GlobalURLOptions

	db            TSDBAdminStats
	dbDir         string
	enableAdmin   bool
	logger        log.Logger
	CORSOrigin    *regexp.Regexp
	buildInfo     *PrometheusVersion
	runtimeInfo   func() (RuntimeInfo, error)
	gatherer      prometheus.Gatherer
	isAgent       bool
	statsRenderer StatsRenderer

	remoteWriteHandler http.Handler
	remoteReadHandler  http.Handler
}

func init() {
	jsoniter.RegisterTypeEncoderFunc("promql.Series", marshalSeriesJSON, marshalSeriesJSONIsEmpty)
	jsoniter.RegisterTypeEncoderFunc("promql.Sample", marshalSampleJSON, marshalSampleJSONIsEmpty)
	jsoniter.RegisterTypeEncoderFunc("promql.Point", marshalPointJSON, marshalPointJSONIsEmpty)
	jsoniter.RegisterTypeEncoderFunc("exemplar.Exemplar", marshalExemplarJSON, marshalExemplarJSONEmpty)
}

// NewAPI returns an initialized API type.
func NewAPI(
	qe QueryEngine,
	q storage.SampleAndChunkQueryable,
	ap storage.Appendable,
	eq storage.ExemplarQueryable,
	tr func(context.Context) TargetRetriever,
	ar func(context.Context) AlertmanagerRetriever,
	configFunc func() config.Config,
	flagsMap map[string]string,
	globalURLOptions GlobalURLOptions,
	readyFunc func(http.HandlerFunc) http.HandlerFunc,
	db TSDBAdminStats,
	dbDir string,
	enableAdmin bool,
	logger log.Logger,
	rr func(context.Context) RulesRetriever,
	remoteReadSampleLimit int,
	remoteReadConcurrencyLimit int,
	remoteReadMaxBytesInFrame int,
	isAgent bool,
	CORSOrigin *regexp.Regexp,
	runtimeInfo func() (RuntimeInfo, error),
	buildInfo *PrometheusVersion,
	gatherer prometheus.Gatherer,
	registerer prometheus.Registerer,
	statsRenderer StatsRenderer,
) *API {
	a := &API{
		QueryEngine:       qe,
		Queryable:         q,
		ExemplarQueryable: eq,

		targetRetriever:       tr,
		alertmanagerRetriever: ar,

		now:              time.Now,
		config:           configFunc,
		flagsMap:         flagsMap,
		ready:            readyFunc,
		globalURLOptions: globalURLOptions,
		db:               db,
		dbDir:            dbDir,
		enableAdmin:      enableAdmin,
		rulesRetriever:   rr,
		logger:           logger,
		CORSOrigin:       CORSOrigin,
		runtimeInfo:      runtimeInfo,
		buildInfo:        buildInfo,
		gatherer:         gatherer,
		isAgent:          isAgent,
		statsRenderer:    defaultStatsRenderer,

		remoteReadHandler: remote.NewReadHandler(logger, registerer, q, configFunc, remoteReadSampleLimit, remoteReadConcurrencyLimit, remoteReadMaxBytesInFrame),
	}

	if statsRenderer != nil {
		a.statsRenderer = statsRenderer
	}

	if ap != nil {
		a.remoteWriteHandler = remote.NewWriteHandler(logger, ap)
	}

	return a
}

func setUnavailStatusOnTSDBNotReady(r apiFuncResult) apiFuncResult {
	if r.err != nil && errors.Cause(r.err.err) == tsdb.ErrNotReady {
		r.err.typ = errorUnavailable
	}
	return r
}

// Register the API's endpoints in the given router.
func (api *API) Register(r *route.Router) {
	wrap := func(f apiFunc) http.HandlerFunc {
		hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			httputil.SetCORS(w, api.CORSOrigin, r)
			result := setUnavailStatusOnTSDBNotReady(f(r))
			if result.finalizer != nil {
				defer result.finalizer()
			}
			if result.err != nil {
				api.respondError(w, result.err, result.data)
				return
			}

			if result.data != nil {
				api.respond(w, result.data, result.warnings)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})
		return api.ready(httputil.CompressionHandler{
			Handler: hf,
		}.ServeHTTP)
	}

	wrapAgent := func(f apiFunc) http.HandlerFunc {
		return wrap(func(r *http.Request) apiFuncResult {
			if api.isAgent {
				return apiFuncResult{nil, &apiError{errorExec, errors.New("unavailable with Prometheus Agent")}, nil, nil}
			}
			return f(r)
		})
	}

	r.Options("/*path", wrap(api.options))

	r.Get("/query", wrapAgent(api.query))
	r.Post("/query", wrapAgent(api.query))
	r.Get("/query_range", wrapAgent(api.queryRange))
	r.Post("/query_range", wrapAgent(api.queryRange))
	r.Get("/query_exemplars", wrapAgent(api.queryExemplars))
	r.Post("/query_exemplars", wrapAgent(api.queryExemplars))

	r.Get("/labels", wrapAgent(api.labelNames))
	r.Post("/labels", wrapAgent(api.labelNames))
	r.Get("/label/:name/values", wrapAgent(api.labelValues))

	r.Get("/series", wrapAgent(api.series))
	r.Post("/series", wrapAgent(api.series))
	r.Del("/series", wrapAgent(api.dropSeries))

	r.Get("/targets", wrap(api.targets))
	r.Get("/targets/metadata", wrap(api.targetMetadata))
	r.Get("/alertmanagers", wrapAgent(api.alertmanagers))

	r.Get("/metadata", wrap(api.metricMetadata))

	r.Get("/status/config", wrap(api.serveConfig))
	r.Get("/status/runtimeinfo", wrap(api.serveRuntimeInfo))
	r.Get("/status/buildinfo", wrap(api.serveBuildInfo))
	r.Get("/status/flags", wrap(api.serveFlags))
	r.Get("/status/tsdb", wrapAgent(api.serveTSDBStatus))
	r.Get("/status/walreplay", api.serveWALReplayStatus)
	r.Post("/read", api.ready(api.remoteRead))
	r.Post("/write", api.ready(api.remoteWrite))

	r.Get("/alerts", wrapAgent(api.alerts))
	r.Get("/rules", wrapAgent(api.rules))

	// Admin APIs
	r.Post("/admin/tsdb/delete_series", wrapAgent(api.deleteSeries))
	r.Post("/admin/tsdb/clean_tombstones", wrapAgent(api.cleanTombstones))
	r.Post("/admin/tsdb/snapshot", wrapAgent(api.snapshot))

	r.Put("/admin/tsdb/delete_series", wrapAgent(api.deleteSeries))
	r.Put("/admin/tsdb/clean_tombstones", wrapAgent(api.cleanTombstones))
	r.Put("/admin/tsdb/snapshot", wrapAgent(api.snapshot))
}

type queryData struct {
	ResultType parser.ValueType `json:"resultType"`
	Result     parser.Value     `json:"result"`
	Stats      stats.QueryStats `json:"stats,omitempty"`
}

func invalidParamError(err error, parameter string) apiFuncResult {
	return apiFuncResult{nil, &apiError{
		errorBadData, errors.Wrapf(err, "invalid parameter %q", parameter),
	}, nil, nil}
}

func (api *API) options(r *http.Request) apiFuncResult {
	return apiFuncResult{nil, nil, nil, nil}
}

func (api *API) query(r *http.Request) (result apiFuncResult) {
	ts, err := parseTimeParam(r, "time", api.now())
	if err != nil {
		return invalidParamError(err, "time")
	}
	ctx := r.Context()
	if to := r.FormValue("timeout"); to != "" {
		var cancel context.CancelFunc
		timeout, err := parseDuration(to)
		if err != nil {
			return invalidParamError(err, "timeout")
		}

		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}

	opts := extractQueryOpts(r)
	qry, err := api.QueryEngine.NewInstantQuery(api.Queryable, opts, r.FormValue("query"), ts)
	if err != nil {
		return invalidParamError(err, "query")
	}

	// From now on, we must only return with a finalizer in the result (to
	// be called by the caller) or call qry.Close ourselves (which is
	// required in the case of a panic).
	defer func() {
		if result.finalizer == nil {
			qry.Close()
		}
	}()

	ctx = httputil.ContextFromRequest(ctx, r)

	res := qry.Exec(ctx)
	if res.Err != nil {
		return apiFuncResult{nil, returnAPIError(res.Err), res.Warnings, qry.Close}
	}

	// Optional stats field in response if parameter "stats" is not empty.
	sr := api.statsRenderer
	if sr == nil {
		sr = defaultStatsRenderer
	}
	qs := sr(ctx, qry.Stats(), r.FormValue("stats"))

	return apiFuncResult{&queryData{
		ResultType: res.Value.Type(),
		Result:     res.Value,
		Stats:      qs,
	}, nil, res.Warnings, qry.Close}
}

func extractQueryOpts(r *http.Request) *promql.QueryOpts {
	return &promql.QueryOpts{
		EnablePerStepStats: r.FormValue("stats") == "all",
	}
}

func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
	start, err := parseTime(r.FormValue("start"))
	if err != nil {
		return invalidParamError(err, "start")
	}
	end, err := parseTime(r.FormValue("end"))
	if err != nil {
		return invalidParamError(err, "end")
	}
	if end.Before(start) {
		return invalidParamError(errors.New("end timestamp must not be before start time"), "end")
	}

	step, err := parseDuration(r.FormValue("step"))
	if err != nil {
		return invalidParamError(err, "step")
	}

	if step <= 0 {
		return invalidParamError(errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer"), "step")
	}

	// For safety, limit the number of returned points per timeseries.
	// This is sufficient for 60s resolution for a week or 1h resolution for a year.
	if end.Sub(start)/step > 11000 {
		err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
		return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
	}

	ctx := r.Context()
	if to := r.FormValue("timeout"); to != "" {
		var cancel context.CancelFunc
		timeout, err := parseDuration(to)
		if err != nil {
			return invalidParamError(err, "timeout")
		}

		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}

	opts := extractQueryOpts(r)
	qry, err := api.QueryEngine.NewRangeQuery(api.Queryable, opts, r.FormValue("query"), start, end, step)
	if err != nil {
		return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
	}
	// From now on, we must only return with a finalizer in the result (to
	// be called by the caller) or call qry.Close ourselves (which is
	// required in the case of a panic).
	defer func() {
		if result.finalizer == nil {
			qry.Close()
		}
	}()

	ctx = httputil.ContextFromRequest(ctx, r)

	res := qry.Exec(ctx)
	if res.Err != nil {
		return apiFuncResult{nil, returnAPIError(res.Err), res.Warnings, qry.Close}
	}

	// Optional stats field in response if parameter "stats" is not empty.
	sr := api.statsRenderer
	if sr == nil {
		sr = defaultStatsRenderer
	}
	qs := sr(ctx, qry.Stats(), r.FormValue("stats"))

	return apiFuncResult{&queryData{
		ResultType: res.Value.Type(),
		Result:     res.Value,
		Stats:      qs,
	}, nil, res.Warnings, qry.Close}
}

func (api *API) queryExemplars(r *http.Request) apiFuncResult {
	start, err := parseTimeParam(r, "start", minTime)
	if err != nil {
{'fraction_non_alphanumeric': 0.13360800470865214, 'fraction_numerical': 0.002942907592701589, 'mean_word_length': 3.8569386694386694, 'pattern_counts': {'":': 2, '<': 9, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3347198', 'n_tokens_mistral': 13102, 'n_tokens_neox': 12439, 'n_words': 2953}