2015-01-21 11:07:45 -08:00
|
|
|
// Copyright 2013 The Prometheus Authors
|
2013-02-08 05:49:55 -08:00
|
|
|
// 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 web
|
|
|
|
|
|
|
|
import (
|
2019-11-04 00:17:50 -08:00
|
|
|
"bytes"
|
2017-10-24 21:21:42 -07:00
|
|
|
"context"
|
2015-06-16 05:57:30 -07:00
|
|
|
"encoding/json"
|
2013-04-05 04:41:52 -07:00
|
|
|
"fmt"
|
2015-06-15 03:50:53 -07:00
|
|
|
"io"
|
2017-08-11 11:45:52 -07:00
|
|
|
stdlog "log"
|
2018-03-21 09:08:37 -07:00
|
|
|
"math"
|
2015-06-30 05:38:01 -07:00
|
|
|
"net"
|
2013-02-08 05:49:55 -08:00
|
|
|
"net/http"
|
2017-08-22 16:00:56 -07:00
|
|
|
"net/http/pprof"
|
2015-06-15 03:50:53 -07:00
|
|
|
"net/url"
|
2013-08-09 09:09:44 -07:00
|
|
|
"os"
|
2017-05-22 05:15:02 -07:00
|
|
|
"path"
|
2015-06-15 03:50:53 -07:00
|
|
|
"path/filepath"
|
2018-03-07 07:14:46 -08:00
|
|
|
"runtime"
|
2021-11-02 20:47:14 -07:00
|
|
|
"strconv"
|
2015-07-08 07:14:57 -07:00
|
|
|
"strings"
|
2015-06-15 03:50:53 -07:00
|
|
|
"sync"
|
2019-03-25 16:01:12 -07:00
|
|
|
"time"
|
2013-06-28 01:19:16 -07:00
|
|
|
|
2020-02-18 03:25:36 -08:00
|
|
|
"github.com/alecthomas/units"
|
2021-06-11 09:17:59 -07:00
|
|
|
"github.com/go-kit/log"
|
|
|
|
"github.com/go-kit/log/level"
|
2022-02-12 15:58:27 -08:00
|
|
|
"github.com/grafana/regexp"
|
2019-03-25 16:01:12 -07:00
|
|
|
conntrack "github.com/mwitkow/go-conntrack"
|
|
|
|
"github.com/pkg/errors"
|
2013-06-28 01:19:16 -07:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2018-03-21 01:16:16 -07:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2019-03-25 16:01:12 -07:00
|
|
|
io_prometheus_client "github.com/prometheus/client_model/go"
|
2015-08-20 08:18:46 -07:00
|
|
|
"github.com/prometheus/common/model"
|
2015-09-24 08:07:11 -07:00
|
|
|
"github.com/prometheus/common/route"
|
2019-05-09 08:10:15 -07:00
|
|
|
"github.com/prometheus/common/server"
|
2021-01-13 12:37:01 -08:00
|
|
|
toolkit_web "github.com/prometheus/exporter-toolkit/web"
|
2022-01-25 02:08:04 -08:00
|
|
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
2020-07-30 00:45:42 -07:00
|
|
|
"go.uber.org/atomic"
|
2016-12-06 01:45:59 -08:00
|
|
|
"golang.org/x/net/netutil"
|
2013-06-28 01:19:16 -07:00
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
"github.com/prometheus/prometheus/config"
|
2016-11-23 09:23:09 -08:00
|
|
|
"github.com/prometheus/prometheus/notifier"
|
2015-06-15 03:50:53 -07:00
|
|
|
"github.com/prometheus/prometheus/promql"
|
|
|
|
"github.com/prometheus/prometheus/rules"
|
2018-02-01 01:55:07 -08:00
|
|
|
"github.com/prometheus/prometheus/scrape"
|
2017-10-24 21:21:42 -07:00
|
|
|
"github.com/prometheus/prometheus/storage"
|
2015-06-15 03:50:53 -07:00
|
|
|
"github.com/prometheus/prometheus/template"
|
2015-09-17 05:49:50 -07:00
|
|
|
"github.com/prometheus/prometheus/util/httputil"
|
2016-06-23 08:14:32 -07:00
|
|
|
api_v1 "github.com/prometheus/prometheus/web/api/v1"
|
2015-11-11 07:31:09 -08:00
|
|
|
"github.com/prometheus/prometheus/web/ui"
|
2013-02-08 05:49:55 -08:00
|
|
|
)
|
|
|
|
|
2020-02-17 09:19:15 -08:00
|
|
|
// Paths that are handled by the React / Reach router that should all be served the main React app's index.html.
|
|
|
|
var reactRouterPaths = []string{
|
|
|
|
"/config",
|
|
|
|
"/flags",
|
|
|
|
"/service-discovery",
|
|
|
|
"/status",
|
|
|
|
"/targets",
|
2021-06-05 07:29:32 -07:00
|
|
|
"/starting",
|
2020-02-17 09:19:15 -08:00
|
|
|
}
|
2015-05-18 03:16:25 -07:00
|
|
|
|
2021-11-30 02:21:07 -08:00
|
|
|
// Paths that are handled by the React router when the Agent mode is set.
|
|
|
|
var reactRouterAgentPaths = []string{
|
|
|
|
"/agent",
|
|
|
|
}
|
|
|
|
|
|
|
|
// Paths that are handled by the React router when the Agent mode is not set.
|
|
|
|
var reactRouterServerPaths = []string{
|
|
|
|
"/alerts",
|
|
|
|
"/graph",
|
|
|
|
"/rules",
|
|
|
|
"/tsdb-status",
|
|
|
|
}
|
|
|
|
|
2018-11-20 05:25:03 -08:00
|
|
|
// withStackTrace logs the stack trace in case the request panics. The function
|
|
|
|
// will re-raise the error which will then be handled by the net/http package.
|
|
|
|
// It is needed because the go-kit log package doesn't manage properly the
|
|
|
|
// panics from net/http (see https://github.com/go-kit/kit/issues/233).
|
|
|
|
func withStackTracer(h http.Handler, l log.Logger) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer func() {
|
|
|
|
if err := recover(); err != nil {
|
|
|
|
const size = 64 << 10
|
|
|
|
buf := make([]byte, size)
|
|
|
|
buf = buf[:runtime.Stack(buf, false)]
|
|
|
|
level.Error(l).Log("msg", "panic while serving request", "client", r.RemoteAddr, "url", r.URL, "err", err, "stack", buf)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
h.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-06-24 06:48:15 -07:00
|
|
|
type metrics struct {
|
|
|
|
requestCounter *prometheus.CounterVec
|
|
|
|
requestDuration *prometheus.HistogramVec
|
|
|
|
responseSize *prometheus.HistogramVec
|
2022-05-23 07:00:59 -07:00
|
|
|
readyStatus prometheus.Gauge
|
2019-06-24 06:48:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func newMetrics(r prometheus.Registerer) *metrics {
|
|
|
|
m := &metrics{
|
|
|
|
requestCounter: prometheus.NewCounterVec(
|
|
|
|
prometheus.CounterOpts{
|
|
|
|
Name: "prometheus_http_requests_total",
|
|
|
|
Help: "Counter of HTTP requests.",
|
|
|
|
},
|
|
|
|
[]string{"handler", "code"},
|
|
|
|
),
|
|
|
|
requestDuration: prometheus.NewHistogramVec(
|
|
|
|
prometheus.HistogramOpts{
|
|
|
|
Name: "prometheus_http_request_duration_seconds",
|
|
|
|
Help: "Histogram of latencies for HTTP requests.",
|
|
|
|
Buckets: []float64{.1, .2, .4, 1, 3, 8, 20, 60, 120},
|
|
|
|
},
|
|
|
|
[]string{"handler"},
|
|
|
|
),
|
|
|
|
responseSize: prometheus.NewHistogramVec(
|
|
|
|
prometheus.HistogramOpts{
|
|
|
|
Name: "prometheus_http_response_size_bytes",
|
|
|
|
Help: "Histogram of response size for HTTP requests.",
|
|
|
|
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
|
|
|
|
},
|
|
|
|
[]string{"handler"},
|
|
|
|
),
|
2022-05-23 07:00:59 -07:00
|
|
|
readyStatus: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
|
|
Name: "prometheus_ready",
|
|
|
|
Help: "Whether Prometheus startup was fully completed and the server is ready for normal operation.",
|
|
|
|
}),
|
2019-06-24 06:48:15 -07:00
|
|
|
}
|
2018-03-21 01:16:16 -07:00
|
|
|
|
2019-06-24 06:48:15 -07:00
|
|
|
if r != nil {
|
2022-05-23 07:00:59 -07:00
|
|
|
r.MustRegister(m.requestCounter, m.requestDuration, m.responseSize, m.readyStatus)
|
2020-04-06 01:05:01 -07:00
|
|
|
registerFederationMetrics(r)
|
2019-06-24 06:48:15 -07:00
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *metrics) instrumentHandlerWithPrefix(prefix string) func(handlerName string, handler http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return func(handlerName string, handler http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return m.instrumentHandler(prefix+handlerName, handler)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *metrics) instrumentHandler(handlerName string, handler http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return promhttp.InstrumentHandlerCounter(
|
|
|
|
m.requestCounter.MustCurryWith(prometheus.Labels{"handler": handlerName}),
|
|
|
|
promhttp.InstrumentHandlerDuration(
|
|
|
|
m.requestDuration.MustCurryWith(prometheus.Labels{"handler": handlerName}),
|
|
|
|
promhttp.InstrumentHandlerResponseSize(
|
|
|
|
m.responseSize.MustCurryWith(prometheus.Labels{"handler": handlerName}),
|
|
|
|
handler,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
2018-03-21 01:16:16 -07:00
|
|
|
}
|
|
|
|
|
2019-11-02 08:53:32 -07:00
|
|
|
// PrometheusVersion contains build information about Prometheus.
|
|
|
|
type PrometheusVersion = api_v1.PrometheusVersion
|
|
|
|
|
2020-04-29 09:16:14 -07:00
|
|
|
type LocalStorage interface {
|
|
|
|
storage.Storage
|
|
|
|
api_v1.TSDBAdminStats
|
|
|
|
}
|
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
// Handler serves various HTTP endpoints of the Prometheus server
|
|
|
|
type Handler struct {
|
2017-08-11 11:45:52 -07:00
|
|
|
logger log.Logger
|
|
|
|
|
2019-06-24 06:48:15 -07:00
|
|
|
gatherer prometheus.Gatherer
|
|
|
|
metrics *metrics
|
|
|
|
|
2021-03-16 02:47:45 -07:00
|
|
|
scrapeManager *scrape.Manager
|
|
|
|
ruleManager *rules.Manager
|
|
|
|
queryEngine *promql.Engine
|
|
|
|
lookbackDelta time.Duration
|
|
|
|
context context.Context
|
|
|
|
storage storage.Storage
|
|
|
|
localStorage LocalStorage
|
|
|
|
exemplarStorage storage.ExemplarQueryable
|
|
|
|
notifier *notifier.Manager
|
2015-06-15 03:50:53 -07:00
|
|
|
|
2016-06-23 08:14:32 -07:00
|
|
|
apiV1 *api_v1.API
|
2015-06-15 03:50:53 -07:00
|
|
|
|
2019-01-04 05:47:38 -08:00
|
|
|
router *route.Router
|
|
|
|
quitCh chan struct{}
|
2020-12-02 00:39:54 -08:00
|
|
|
quitOnce sync.Once
|
2019-01-04 05:47:38 -08:00
|
|
|
reloadCh chan chan error
|
|
|
|
options *Options
|
|
|
|
config *config.Config
|
|
|
|
versionInfo *PrometheusVersion
|
|
|
|
birth time.Time
|
|
|
|
cwd string
|
|
|
|
flagsMap map[string]string
|
|
|
|
|
|
|
|
mtx sync.RWMutex
|
|
|
|
now func() model.Time
|
2017-07-25 17:47:45 -07:00
|
|
|
|
2020-07-30 00:45:42 -07:00
|
|
|
ready atomic.Uint32 // ready is uint32 rather than boolean to be able to use atomic functions.
|
2015-09-01 09:47:48 -07:00
|
|
|
}
|
|
|
|
|
2017-05-11 08:09:24 -07:00
|
|
|
// ApplyConfig updates the config field of the Handler struct
|
2016-07-11 07:24:54 -07:00
|
|
|
func (h *Handler) ApplyConfig(conf *config.Config) error {
|
2015-09-01 09:47:48 -07:00
|
|
|
h.mtx.Lock()
|
|
|
|
defer h.mtx.Unlock()
|
|
|
|
|
2017-05-11 08:09:24 -07:00
|
|
|
h.config = conf
|
2015-09-01 09:47:48 -07:00
|
|
|
|
2016-07-11 07:24:54 -07:00
|
|
|
return nil
|
2015-06-15 03:50:53 -07:00
|
|
|
}
|
2013-02-08 06:38:50 -08:00
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
// Options for the web Handler.
|
|
|
|
type Options struct {
|
2020-02-18 03:25:36 -08:00
|
|
|
Context context.Context
|
|
|
|
TSDBRetentionDuration model.Duration
|
2020-04-29 09:16:14 -07:00
|
|
|
TSDBDir string
|
2020-02-18 03:25:36 -08:00
|
|
|
TSDBMaxBytes units.Base2Bytes
|
2020-04-29 09:16:14 -07:00
|
|
|
LocalStorage LocalStorage
|
2020-02-18 03:25:36 -08:00
|
|
|
Storage storage.Storage
|
2021-03-16 02:47:45 -07:00
|
|
|
ExemplarStorage storage.ExemplarQueryable
|
2020-02-18 03:25:36 -08:00
|
|
|
QueryEngine *promql.Engine
|
|
|
|
LookbackDelta time.Duration
|
|
|
|
ScrapeManager *scrape.Manager
|
|
|
|
RuleManager *rules.Manager
|
|
|
|
Notifier *notifier.Manager
|
|
|
|
Version *PrometheusVersion
|
|
|
|
Flags map[string]string
|
2016-09-15 15:58:06 -07:00
|
|
|
|
2018-09-25 12:07:34 -07:00
|
|
|
ListenAddress string
|
2019-01-17 07:01:06 -08:00
|
|
|
CORSOrigin *regexp.Regexp
|
2018-09-25 12:07:34 -07:00
|
|
|
ReadTimeout time.Duration
|
|
|
|
MaxConnections int
|
|
|
|
ExternalURL *url.URL
|
|
|
|
RoutePrefix string
|
|
|
|
UseLocalAssets bool
|
|
|
|
UserAssetsPath string
|
|
|
|
ConsoleTemplatesPath string
|
|
|
|
ConsoleLibrariesPath string
|
|
|
|
EnableLifecycle bool
|
|
|
|
EnableAdminAPI bool
|
2018-11-20 20:45:06 -08:00
|
|
|
PageTitle string
|
2018-09-25 12:07:34 -07:00
|
|
|
RemoteReadSampleLimit int
|
|
|
|
RemoteReadConcurrencyLimit int
|
2019-08-19 13:16:10 -07:00
|
|
|
RemoteReadBytesInFrame int
|
2022-01-05 06:26:24 -08:00
|
|
|
EnableRemoteWriteReceiver bool
|
2021-10-29 08:25:05 -07:00
|
|
|
IsAgent bool
|
2021-11-01 13:44:49 -07:00
|
|
|
AppName string
|
2015-06-15 03:50:53 -07:00
|
|
|
|
2019-06-24 06:48:15 -07:00
|
|
|
Gatherer prometheus.Gatherer
|
|
|
|
Registerer prometheus.Registerer
|
2018-03-21 01:16:16 -07:00
|
|
|
}
|
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
// New initializes a new web Handler.
|
2017-08-11 11:45:52 -07:00
|
|
|
func New(logger log.Logger, o *Options) *Handler {
|
2019-06-24 06:48:15 -07:00
|
|
|
if logger == nil {
|
|
|
|
logger = log.NewNopLogger()
|
|
|
|
}
|
|
|
|
|
|
|
|
m := newMetrics(o.Registerer)
|
2020-01-10 04:56:36 -08:00
|
|
|
router := route.New().
|
2020-01-18 16:07:10 -08:00
|
|
|
WithInstrumentation(m.instrumentHandler).
|
|
|
|
WithInstrumentation(setPathWithPrefix(""))
|
2016-12-03 15:37:59 -08:00
|
|
|
|
2019-06-24 06:48:15 -07:00
|
|
|
cwd, err := os.Getwd()
|
2016-12-03 15:37:59 -08:00
|
|
|
if err != nil {
|
|
|
|
cwd = "<error retrieving current working directory>"
|
|
|
|
}
|
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
h := &Handler{
|
2019-06-24 06:48:15 -07:00
|
|
|
logger: logger,
|
|
|
|
|
|
|
|
gatherer: o.Gatherer,
|
|
|
|
metrics: m,
|
|
|
|
|
2015-08-20 09:23:57 -07:00
|
|
|
router: router,
|
|
|
|
quitCh: make(chan struct{}),
|
2016-07-11 07:24:54 -07:00
|
|
|
reloadCh: make(chan chan error),
|
2015-08-20 09:23:57 -07:00
|
|
|
options: o,
|
2016-09-15 15:58:06 -07:00
|
|
|
versionInfo: o.Version,
|
2020-03-29 09:35:39 -07:00
|
|
|
birth: time.Now().UTC(),
|
2016-12-03 15:37:59 -08:00
|
|
|
cwd: cwd,
|
2016-09-15 15:58:06 -07:00
|
|
|
flagsMap: o.Flags,
|
2015-06-15 03:50:53 -07:00
|
|
|
|
2021-03-16 02:47:45 -07:00
|
|
|
context: o.Context,
|
|
|
|
scrapeManager: o.ScrapeManager,
|
|
|
|
ruleManager: o.RuleManager,
|
|
|
|
queryEngine: o.QueryEngine,
|
|
|
|
lookbackDelta: o.LookbackDelta,
|
|
|
|
storage: o.Storage,
|
|
|
|
localStorage: o.LocalStorage,
|
|
|
|
exemplarStorage: o.ExemplarStorage,
|
|
|
|
notifier: o.Notifier,
|
2015-06-15 03:50:53 -07:00
|
|
|
|
2017-05-11 08:09:24 -07:00
|
|
|
now: model.Now,
|
2015-06-02 04:07:46 -07:00
|
|
|
}
|
2022-05-23 07:00:59 -07:00
|
|
|
h.SetReady(false)
|
2013-05-06 00:56:32 -07:00
|
|
|
|
2020-05-18 11:02:32 -07:00
|
|
|
factoryTr := func(_ context.Context) api_v1.TargetRetriever { return h.scrapeManager }
|
|
|
|
factoryAr := func(_ context.Context) api_v1.AlertmanagerRetriever { return h.notifier }
|
|
|
|
FactoryRr := func(_ context.Context) api_v1.RulesRetriever { return h.ruleManager }
|
|
|
|
|
2021-02-15 03:30:00 -08:00
|
|
|
var app storage.Appendable
|
2022-01-05 06:26:24 -08:00
|
|
|
if o.EnableRemoteWriteReceiver {
|
2021-02-15 03:30:00 -08:00
|
|
|
app = h.storage
|
|
|
|
}
|
|
|
|
|
2021-03-16 02:47:45 -07:00
|
|
|
h.apiV1 = api_v1.NewAPI(h.queryEngine, h.storage, app, h.exemplarStorage, factoryTr, factoryAr,
|
2017-05-11 08:09:24 -07:00
|
|
|
func() config.Config {
|
|
|
|
h.mtx.RLock()
|
|
|
|
defer h.mtx.RUnlock()
|
|
|
|
return *h.config
|
|
|
|
},
|
api: Added v1/status/flags endpoint. (#3864)
Endpoint URL: /api/v1/status/flags
Example Output:
```json
{
"status": "success",
"data": {
"alertmanager.notification-queue-capacity": "10000",
"alertmanager.timeout": "10s",
"completion-bash": "false",
"completion-script-bash": "false",
"completion-script-zsh": "false",
"config.file": "my_cool_prometheus.yaml",
"help": "false",
"help-long": "false",
"help-man": "false",
"log.level": "info",
"query.lookback-delta": "5m",
"query.max-concurrency": "20",
"query.timeout": "2m",
"storage.tsdb.max-block-duration": "36h",
"storage.tsdb.min-block-duration": "2h",
"storage.tsdb.no-lockfile": "false",
"storage.tsdb.path": "data/",
"storage.tsdb.retention": "15d",
"version": "false",
"web.console.libraries": "console_libraries",
"web.console.templates": "consoles",
"web.enable-admin-api": "false",
"web.enable-lifecycle": "false",
"web.external-url": "",
"web.listen-address": "0.0.0.0:9090",
"web.max-connections": "512",
"web.read-timeout": "5m",
"web.route-prefix": "/",
"web.user-assets": ""
}
}
```
Signed-off-by: Bartek Plotka <bwplotka@gmail.com>
2018-02-21 00:49:02 -08:00
|
|
|
o.Flags,
|
2020-02-17 09:19:15 -08:00
|
|
|
api_v1.GlobalURLOptions{
|
|
|
|
ListenAddress: o.ListenAddress,
|
|
|
|
Host: o.ExternalURL.Host,
|
|
|
|
Scheme: o.ExternalURL.Scheme,
|
|
|
|
},
|
2017-10-06 08:20:20 -07:00
|
|
|
h.testReady,
|
2020-04-29 09:16:14 -07:00
|
|
|
h.options.LocalStorage,
|
|
|
|
h.options.TSDBDir,
|
2017-12-02 21:07:05 -08:00
|
|
|
h.options.EnableAdminAPI,
|
2018-07-06 10:44:45 -07:00
|
|
|
logger,
|
2020-05-18 11:02:32 -07:00
|
|
|
FactoryRr,
|
2018-09-25 12:07:34 -07:00
|
|
|
h.options.RemoteReadSampleLimit,
|
|
|
|
h.options.RemoteReadConcurrencyLimit,
|
2019-08-19 13:16:10 -07:00
|
|
|
h.options.RemoteReadBytesInFrame,
|
2021-10-29 08:25:05 -07:00
|
|
|
h.options.IsAgent,
|
2019-01-17 07:01:06 -08:00
|
|
|
h.options.CORSOrigin,
|
2019-11-02 08:53:32 -07:00
|
|
|
h.runtimeInfo,
|
|
|
|
h.versionInfo,
|
2020-09-29 13:05:33 -07:00
|
|
|
o.Gatherer,
|
2021-02-26 08:43:19 -08:00
|
|
|
o.Registerer,
|
2022-02-10 06:17:05 -08:00
|
|
|
nil,
|
2017-05-11 08:09:24 -07:00
|
|
|
)
|
2017-07-06 05:38:40 -07:00
|
|
|
|
2016-07-05 06:05:43 -07:00
|
|
|
if o.RoutePrefix != "/" {
|
2015-06-15 03:50:53 -07:00
|
|
|
// If the prefix is missing for the root path, prepend it.
|
2015-06-02 23:38:50 -07:00
|
|
|
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
2016-07-05 06:05:43 -07:00
|
|
|
http.Redirect(w, r, o.RoutePrefix, http.StatusFound)
|
2015-06-02 23:38:50 -07:00
|
|
|
})
|
2016-07-05 06:05:43 -07:00
|
|
|
router = router.WithPrefix(o.RoutePrefix)
|
2015-06-02 23:38:50 -07:00
|
|
|
}
|
|
|
|
|
2021-11-30 02:21:07 -08:00
|
|
|
homePage := "/graph"
|
|
|
|
if o.IsAgent {
|
|
|
|
homePage = "/agent"
|
|
|
|
}
|
|
|
|
|
2017-07-25 17:47:45 -07:00
|
|
|
readyf := h.testReady
|
2015-06-02 23:38:50 -07:00
|
|
|
|
2015-08-28 01:53:13 -07:00
|
|
|
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
2021-11-30 02:21:07 -08:00
|
|
|
http.Redirect(w, r, path.Join(o.ExternalURL.Path, homePage), http.StatusFound)
|
2015-08-28 01:53:13 -07:00
|
|
|
})
|
2015-06-02 23:38:50 -07:00
|
|
|
|
2022-02-02 02:26:11 -08:00
|
|
|
// The console library examples at 'console_libraries/prom.lib' still depend on old asset files being served under `classic`.
|
2020-11-03 05:51:48 -08:00
|
|
|
router.Get("/classic/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
|
2018-09-19 00:20:53 -07:00
|
|
|
r.URL.Path = path.Join("/static", route.Param(r.Context(), "filepath"))
|
2019-05-09 08:10:15 -07:00
|
|
|
fs := server.StaticFileServer(ui.Assets)
|
2018-08-24 00:03:10 -07:00
|
|
|
fs.ServeHTTP(w, r)
|
|
|
|
})
|
2019-11-14 05:58:22 -08:00
|
|
|
|
2020-11-03 05:51:48 -08:00
|
|
|
router.Get("/version", h.version)
|
|
|
|
router.Get("/metrics", promhttp.Handler().ServeHTTP)
|
2019-10-28 02:45:53 -07:00
|
|
|
|
2020-11-03 05:51:48 -08:00
|
|
|
router.Get("/federate", readyf(httputil.CompressionHandler{
|
|
|
|
Handler: http.HandlerFunc(h.federation),
|
|
|
|
}.ServeHTTP))
|
2019-11-04 00:17:50 -08:00
|
|
|
|
2020-11-03 05:51:48 -08:00
|
|
|
router.Get("/consoles/*filepath", readyf(h.consoles))
|
|
|
|
|
|
|
|
serveReactApp := func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
f, err := ui.Assets.Open("/static/react/index.html")
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
fmt.Fprintf(w, "Error opening React index.html: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2021-10-06 04:02:46 -07:00
|
|
|
defer func() { _ = f.Close() }()
|
2022-04-27 02:24:36 -07:00
|
|
|
idx, err := io.ReadAll(f)
|
2020-11-03 05:51:48 -08:00
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
fmt.Fprintf(w, "Error reading React index.html: %v", err)
|
2019-11-04 00:17:50 -08:00
|
|
|
return
|
2019-10-28 02:45:53 -07:00
|
|
|
}
|
2020-11-03 05:51:48 -08:00
|
|
|
replacedIdx := bytes.ReplaceAll(idx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
|
|
|
|
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle))
|
2021-11-30 02:21:07 -08:00
|
|
|
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("AGENT_MODE_PLACEHOLDER"), []byte(strconv.FormatBool(h.options.IsAgent)))
|
2020-11-03 05:51:48 -08:00
|
|
|
w.Write(replacedIdx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serve the React app.
|
|
|
|
for _, p := range reactRouterPaths {
|
|
|
|
router.Get(p, serveReactApp)
|
|
|
|
}
|
2019-10-28 02:45:53 -07:00
|
|
|
|
2021-11-30 02:21:07 -08:00
|
|
|
if h.options.IsAgent {
|
|
|
|
for _, p := range reactRouterAgentPaths {
|
|
|
|
router.Get(p, serveReactApp)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for _, p := range reactRouterServerPaths {
|
|
|
|
router.Get(p, serveReactApp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-03 05:51:48 -08:00
|
|
|
// The favicon and manifest are bundled as part of the React app, but we want to serve
|
|
|
|
// them on the root.
|
|
|
|
for _, p := range []string{"/favicon.ico", "/manifest.json"} {
|
|
|
|
assetPath := "/static/react" + p
|
|
|
|
router.Get(p, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.URL.Path = assetPath
|
|
|
|
fs := server.StaticFileServer(ui.Assets)
|
|
|
|
fs.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Static files required by the React app.
|
|
|
|
router.Get("/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.URL.Path = path.Join("/static/react/static", route.Param(r.Context(), "filepath"))
|
2019-10-28 02:45:53 -07:00
|
|
|
fs := server.StaticFileServer(ui.Assets)
|
|
|
|
fs.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
if o.UserAssetsPath != "" {
|
2018-03-21 01:16:16 -07:00
|
|
|
router.Get("/user/*filepath", route.FileServe(o.UserAssetsPath))
|
2013-05-23 06:47:00 -07:00
|
|
|
}
|
|
|
|
|
2017-07-10 06:44:29 -07:00
|
|
|
if o.EnableLifecycle {
|
2015-06-15 03:50:53 -07:00
|
|
|
router.Post("/-/quit", h.quit)
|
2019-03-20 10:33:45 -07:00
|
|
|
router.Put("/-/quit", h.quit)
|
2017-07-10 06:44:29 -07:00
|
|
|
router.Post("/-/reload", h.reload)
|
2019-03-20 10:33:45 -07:00
|
|
|
router.Put("/-/reload", h.reload)
|
2017-07-10 06:44:29 -07:00
|
|
|
} else {
|
2019-09-25 02:48:36 -07:00
|
|
|
forbiddenAPINotEnabled := func(w http.ResponseWriter, _ *http.Request) {
|
2017-07-10 06:44:29 -07:00
|
|
|
w.WriteHeader(http.StatusForbidden)
|
2019-09-25 02:50:39 -07:00
|
|
|
w.Write([]byte("Lifecycle API is not enabled."))
|
2019-09-25 02:48:36 -07:00
|
|
|
}
|
|
|
|
router.Post("/-/quit", forbiddenAPINotEnabled)
|
|
|
|
router.Put("/-/quit", forbiddenAPINotEnabled)
|
|
|
|
router.Post("/-/reload", forbiddenAPINotEnabled)
|
|
|
|
router.Put("/-/reload", forbiddenAPINotEnabled)
|
2015-03-24 14:04:38 -07:00
|
|
|
}
|
2017-07-10 06:44:29 -07:00
|
|
|
router.Get("/-/quit", func(w http.ResponseWriter, _ *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
2019-03-20 10:33:45 -07:00
|
|
|
w.Write([]byte("Only POST or PUT requests allowed"))
|
2017-07-10 06:44:29 -07:00
|
|
|
})
|
|
|
|
router.Get("/-/reload", func(w http.ResponseWriter, _ *http.Request) {
|
2016-11-07 07:14:22 -08:00
|
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
2019-03-20 10:33:45 -07:00
|
|
|
w.Write([]byte("Only POST or PUT requests allowed"))
|
2016-11-07 07:14:22 -08:00
|
|
|
})
|
2015-08-26 08:42:25 -07:00
|
|
|
|
2017-09-18 03:32:17 -07:00
|
|
|
router.Get("/debug/*subpath", serveDebug)
|
|
|
|
router.Post("/debug/*subpath", serveDebug)
|
2017-07-25 17:47:45 -07:00
|
|
|
|
|
|
|
router.Get("/-/healthy", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
2021-11-01 13:44:49 -07:00
|
|
|
fmt.Fprintf(w, o.AppName+" is Healthy.\n")
|
2017-07-25 17:47:45 -07:00
|
|
|
})
|
2022-08-16 12:06:26 -07:00
|
|
|
router.Head("/-/healthy", func(w http.ResponseWriter, _ *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
})
|
2017-07-25 17:47:45 -07:00
|
|
|
router.Get("/-/ready", readyf(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
2021-11-01 13:44:49 -07:00
|
|
|
fmt.Fprintf(w, o.AppName+" is Ready.\n")
|
2017-07-25 17:47:45 -07:00
|
|
|
}))
|
2022-08-16 12:06:26 -07:00
|
|
|
router.Head("/-/ready", readyf(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
}))
|
2015-07-21 15:27:55 -07:00
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2017-08-22 16:00:56 -07:00
|
|
|
func serveDebug(w http.ResponseWriter, req *http.Request) {
|
|
|
|
ctx := req.Context()
|
|
|
|
subpath := route.Param(ctx, "subpath")
|
|
|
|
|
2017-09-01 04:12:51 -07:00
|
|
|
if subpath == "/pprof" {
|
|
|
|
http.Redirect(w, req, req.URL.Path+"/", http.StatusMovedPermanently)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-09-07 07:24:12 -07:00
|
|
|
if !strings.HasPrefix(subpath, "/pprof/") {
|
|
|
|
http.NotFound(w, req)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
subpath = strings.TrimPrefix(subpath, "/pprof/")
|
|
|
|
|
|
|
|
switch subpath {
|
|
|
|
case "cmdline":
|
2017-08-22 16:00:56 -07:00
|
|
|
pprof.Cmdline(w, req)
|
2017-09-07 07:24:12 -07:00
|
|
|
case "profile":
|
2017-08-22 16:00:56 -07:00
|
|
|
pprof.Profile(w, req)
|
2017-09-07 07:24:12 -07:00
|
|
|
case "symbol":
|
2017-08-22 16:00:56 -07:00
|
|
|
pprof.Symbol(w, req)
|
2017-09-07 07:24:12 -07:00
|
|
|
case "trace":
|
2017-08-22 16:00:56 -07:00
|
|
|
pprof.Trace(w, req)
|
2017-09-07 07:24:12 -07:00
|
|
|
default:
|
2017-09-01 04:12:51 -07:00
|
|
|
req.URL.Path = "/debug/pprof/" + subpath
|
2017-09-07 07:24:12 -07:00
|
|
|
pprof.Index(w, req)
|
2017-08-22 16:00:56 -07:00
|
|
|
}
|
|
|
|
}
|
2015-06-15 03:50:53 -07:00
|
|
|
|
2022-05-23 07:00:59 -07:00
|
|
|
// SetReady sets the ready status of our web Handler
|
|
|
|
func (h *Handler) SetReady(v bool) {
|
|
|
|
if v {
|
|
|
|
h.ready.Store(1)
|
|
|
|
h.metrics.readyStatus.Set(1)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
h.ready.Store(0)
|
|
|
|
h.metrics.readyStatus.Set(0)
|
2017-07-25 17:47:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Verifies whether the server is ready or not.
|
|
|
|
func (h *Handler) isReady() bool {
|
2020-07-30 00:45:42 -07:00
|
|
|
return h.ready.Load() > 0
|
2017-07-25 17:47:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Checks if server is ready, calls f if it is, returns 503 if it is not.
|
|
|
|
func (h *Handler) testReady(f http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if h.isReady() {
|
|
|
|
f(w, r)
|
|
|
|
} else {
|
|
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
|
|
fmt.Fprintf(w, "Service Unavailable")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
// Quit returns the receive-only quit channel.
|
|
|
|
func (h *Handler) Quit() <-chan struct{} {
|
|
|
|
return h.quitCh
|
2015-06-02 04:07:46 -07:00
|
|
|
}
|
|
|
|
|
2015-08-20 09:23:57 -07:00
|
|
|
// Reload returns the receive-only channel that signals configuration reload requests.
|
2016-07-11 07:24:54 -07:00
|
|
|
func (h *Handler) Reload() <-chan chan error {
|
2015-08-11 00:08:17 -07:00
|
|
|
return h.reloadCh
|
|
|
|
}
|
|
|
|
|
2020-12-03 06:33:16 -08:00
|
|
|
// Listener creates the TCP listener for web requests.
|
|
|
|
func (h *Handler) Listener() (net.Listener, error) {
|
2017-08-11 11:45:52 -07:00
|
|
|
level.Info(h.logger).Log("msg", "Start listening for connections", "address", h.options.ListenAddress)
|
2017-07-06 05:38:40 -07:00
|
|
|
|
2017-10-06 03:22:19 -07:00
|
|
|
listener, err := net.Listen("tcp", h.options.ListenAddress)
|
2017-07-06 05:38:40 -07:00
|
|
|
if err != nil {
|
2020-12-03 06:33:16 -08:00
|
|
|
return listener, err
|
2017-07-06 05:38:40 -07:00
|
|
|
}
|
2017-10-06 03:22:19 -07:00
|
|
|
listener = netutil.LimitListener(listener, h.options.MaxConnections)
|
|
|
|
|
|
|
|
// Monitor incoming connections with conntrack.
|
|
|
|
listener = conntrack.NewListener(listener,
|
|
|
|
conntrack.TrackWithName("http"),
|
|
|
|
conntrack.TrackWithTracing())
|
2017-07-06 05:38:40 -07:00
|
|
|
|
2020-12-03 06:33:16 -08:00
|
|
|
return listener, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run serves the HTTP endpoints.
|
2021-01-13 12:37:01 -08:00
|
|
|
func (h *Handler) Run(ctx context.Context, listener net.Listener, webConfig string) error {
|
2020-12-25 03:45:31 -08:00
|
|
|
if listener == nil {
|
|
|
|
var err error
|
|
|
|
listener, err = h.Listener()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2022-01-25 02:08:04 -08:00
|
|
|
|
2017-07-06 05:38:40 -07:00
|
|
|
mux := http.NewServeMux()
|
|
|
|
mux.Handle("/", h.router)
|
|
|
|
|
2017-09-06 05:41:07 -07:00
|
|
|
apiPath := "/api"
|
|
|
|
if h.options.RoutePrefix != "/" {
|
|
|
|
apiPath = h.options.RoutePrefix + apiPath
|
2020-04-11 01:22:18 -07:00
|
|
|
level.Info(h.logger).Log("msg", "Router prefix", "prefix", h.options.RoutePrefix)
|
2017-09-06 05:41:07 -07:00
|
|
|
}
|
2020-01-10 04:56:36 -08:00
|
|
|
av1 := route.New().
|
2020-01-18 16:07:10 -08:00
|
|
|
WithInstrumentation(h.metrics.instrumentHandlerWithPrefix("/api/v1")).
|
|
|
|
WithInstrumentation(setPathWithPrefix(apiPath + "/v1"))
|
2020-01-10 04:56:36 -08:00
|
|
|
h.apiV1.Register(av1)
|
2017-09-06 05:41:07 -07:00
|
|
|
|
|
|
|
mux.Handle(apiPath+"/v1/", http.StripPrefix(apiPath+"/v1", av1))
|
2017-07-06 05:38:40 -07:00
|
|
|
|
2017-08-11 11:45:52 -07:00
|
|
|
errlog := stdlog.New(log.NewStdlibAdapter(level.Error(h.logger)), "", 0)
|
|
|
|
|
2022-01-25 02:08:04 -08:00
|
|
|
spanNameFormatter := otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
|
|
|
|
return fmt.Sprintf("%s %s", r.Method, r.URL.Path)
|
|
|
|
})
|
|
|
|
|
2017-07-06 05:38:40 -07:00
|
|
|
httpSrv := &http.Server{
|
2022-01-25 02:08:04 -08:00
|
|
|
Handler: withStackTracer(otelhttp.NewHandler(mux, "", spanNameFormatter), h.logger),
|
2017-08-11 11:45:52 -07:00
|
|
|
ErrorLog: errlog,
|
2016-12-01 05:29:45 -08:00
|
|
|
ReadTimeout: h.options.ReadTimeout,
|
2016-05-25 04:13:22 -07:00
|
|
|
}
|
2017-07-06 05:38:40 -07:00
|
|
|
|
2021-11-23 00:58:17 -08:00
|
|
|
errCh := make(chan error, 1)
|
2017-08-10 05:48:31 -07:00
|
|
|
go func() {
|
2021-01-13 12:37:01 -08:00
|
|
|
errCh <- toolkit_web.Serve(listener, httpSrv, webConfig, h.logger)
|
2017-11-11 04:06:13 -08:00
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case e := <-errCh:
|
|
|
|
return e
|
|
|
|
case <-ctx.Done():
|
|
|
|
httpSrv.Shutdown(ctx)
|
|
|
|
return nil
|
|
|
|
}
|
2013-02-08 05:49:55 -08:00
|
|
|
}
|
2013-03-27 09:40:01 -07:00
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) {
|
2017-05-02 16:49:29 -07:00
|
|
|
ctx := r.Context()
|
2015-06-15 03:50:53 -07:00
|
|
|
name := route.Param(ctx, "filepath")
|
|
|
|
|
|
|
|
file, err := http.Dir(h.options.ConsoleTemplatesPath).Open(name)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
2018-11-27 05:58:27 -08:00
|
|
|
defer file.Close()
|
2022-04-27 02:24:36 -07:00
|
|
|
text, err := io.ReadAll(file)
|
2015-06-15 03:50:53 -07:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-02-18 06:52:29 -08:00
|
|
|
ctx = httputil.ContextFromRequest(ctx, r)
|
2020-01-10 04:56:36 -08:00
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
// Provide URL parameters as a map for easy use. Advanced users may have need for
|
|
|
|
// parameters beyond the first, so provide RawParams.
|
|
|
|
rawParams, err := url.ParseQuery(r.URL.RawQuery)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
params := map[string]string{}
|
|
|
|
for k, v := range rawParams {
|
|
|
|
params[k] = v[0]
|
|
|
|
}
|
2018-12-17 11:16:28 -08:00
|
|
|
|
2019-04-15 09:52:58 -07:00
|
|
|
externalLabels := map[string]string{}
|
|
|
|
h.mtx.RLock()
|
|
|
|
els := h.config.GlobalConfig.ExternalLabels
|
|
|
|
h.mtx.RUnlock()
|
|
|
|
for _, el := range els {
|
|
|
|
externalLabels[el.Name] = el.Value
|
2018-12-17 11:16:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Inject some convenience variables that are easier to remember for users
|
|
|
|
// who are not used to Go's templating system.
|
2019-04-15 09:52:58 -07:00
|
|
|
defs := []string{
|
|
|
|
"{{$rawParams := .RawParams }}",
|
|
|
|
"{{$params := .Params}}",
|
|
|
|
"{{$path := .Path}}",
|
|
|
|
"{{$externalLabels := .ExternalLabels}}",
|
|
|
|
}
|
2018-12-17 11:16:28 -08:00
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
data := struct {
|
2018-12-17 11:16:28 -08:00
|
|
|
RawParams url.Values
|
|
|
|
Params map[string]string
|
|
|
|
Path string
|
|
|
|
ExternalLabels map[string]string
|
2015-06-15 03:50:53 -07:00
|
|
|
}{
|
2018-12-17 11:16:28 -08:00
|
|
|
RawParams: rawParams,
|
|
|
|
Params: params,
|
|
|
|
Path: strings.TrimLeft(name, "/"),
|
2019-04-15 09:52:58 -07:00
|
|
|
ExternalLabels: externalLabels,
|
2015-06-15 03:50:53 -07:00
|
|
|
}
|
|
|
|
|
2017-11-23 04:04:54 -08:00
|
|
|
tmpl := template.NewTemplateExpander(
|
2020-01-10 04:56:36 -08:00
|
|
|
ctx,
|
2019-04-15 09:52:58 -07:00
|
|
|
strings.Join(append(defs, string(text)), ""),
|
2017-11-23 04:04:54 -08:00
|
|
|
"__console_"+name,
|
|
|
|
data,
|
|
|
|
h.now(),
|
2018-01-09 08:44:23 -08:00
|
|
|
template.QueryFunc(rules.EngineQueryFunc(h.queryEngine, h.storage)),
|
2017-11-23 04:04:54 -08:00
|
|
|
h.options.ExternalURL,
|
2021-09-13 04:49:08 -07:00
|
|
|
nil,
|
2017-11-23 04:04:54 -08:00
|
|
|
)
|
2015-06-15 03:50:53 -07:00
|
|
|
filenames, err := filepath.Glob(h.options.ConsoleLibrariesPath + "/*.lib")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
result, err := tmpl.ExpandHTML(filenames)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
io.WriteString(w, result)
|
|
|
|
}
|
|
|
|
|
2019-11-02 08:53:32 -07:00
|
|
|
func (h *Handler) runtimeInfo() (api_v1.RuntimeInfo, error) {
|
|
|
|
status := api_v1.RuntimeInfo{
|
|
|
|
StartTime: h.birth,
|
|
|
|
CWD: h.cwd,
|
|
|
|
GoroutineCount: runtime.NumGoroutine(),
|
|
|
|
GOMAXPROCS: runtime.GOMAXPROCS(0),
|
|
|
|
GOGC: os.Getenv("GOGC"),
|
|
|
|
GODEBUG: os.Getenv("GODEBUG"),
|
|
|
|
}
|
|
|
|
|
2020-02-18 03:25:36 -08:00
|
|
|
if h.options.TSDBRetentionDuration != 0 {
|
|
|
|
status.StorageRetention = h.options.TSDBRetentionDuration.String()
|
2019-11-02 08:53:32 -07:00
|
|
|
}
|
2020-02-18 03:25:36 -08:00
|
|
|
if h.options.TSDBMaxBytes != 0 {
|
2019-11-02 08:53:32 -07:00
|
|
|
if status.StorageRetention != "" {
|
|
|
|
status.StorageRetention = status.StorageRetention + " or "
|
|
|
|
}
|
2020-02-18 03:25:36 -08:00
|
|
|
status.StorageRetention = status.StorageRetention + h.options.TSDBMaxBytes.String()
|
2019-11-02 08:53:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
metrics, err := h.gatherer.Gather()
|
|
|
|
if err != nil {
|
|
|
|
return status, errors.Errorf("error gathering runtime status: %s", err)
|
|
|
|
}
|
|
|
|
for _, mF := range metrics {
|
|
|
|
switch *mF.Name {
|
|
|
|
case "prometheus_tsdb_wal_corruptions_total":
|
|
|
|
status.CorruptionCount = int64(toFloat64(mF))
|
|
|
|
case "prometheus_config_last_reload_successful":
|
|
|
|
status.ReloadConfigSuccess = toFloat64(mF) != 0
|
|
|
|
case "prometheus_config_last_reload_success_timestamp_seconds":
|
2020-03-29 09:35:39 -07:00
|
|
|
status.LastConfigTime = time.Unix(int64(toFloat64(mF)), 0).UTC()
|
2019-11-02 08:53:32 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return status, nil
|
|
|
|
}
|
|
|
|
|
2018-03-21 09:08:37 -07:00
|
|
|
func toFloat64(f *io_prometheus_client.MetricFamily) float64 {
|
|
|
|
m := *f.Metric[0]
|
|
|
|
if m.Gauge != nil {
|
|
|
|
return m.Gauge.GetValue()
|
|
|
|
}
|
|
|
|
if m.Counter != nil {
|
|
|
|
return m.Counter.GetValue()
|
|
|
|
}
|
|
|
|
if m.Untyped != nil {
|
|
|
|
return m.Untyped.GetValue()
|
|
|
|
}
|
|
|
|
return math.NaN()
|
2015-06-15 03:50:53 -07:00
|
|
|
}
|
2014-04-14 16:02:15 -07:00
|
|
|
|
2015-06-16 05:57:30 -07:00
|
|
|
func (h *Handler) version(w http.ResponseWriter, r *http.Request) {
|
|
|
|
dec := json.NewEncoder(w)
|
2016-05-05 04:46:51 -07:00
|
|
|
if err := dec.Encode(h.versionInfo); err != nil {
|
2015-06-16 05:57:30 -07:00
|
|
|
http.Error(w, fmt.Sprintf("error encoding JSON: %s", err), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-15 03:50:53 -07:00
|
|
|
func (h *Handler) quit(w http.ResponseWriter, r *http.Request) {
|
2020-12-02 00:39:54 -08:00
|
|
|
var closed bool
|
|
|
|
h.quitOnce.Do(func() {
|
|
|
|
closed = true
|
2020-11-09 15:09:39 -08:00
|
|
|
close(h.quitCh)
|
2020-12-02 00:39:54 -08:00
|
|
|
fmt.Fprintf(w, "Requesting termination... Goodbye!")
|
|
|
|
})
|
|
|
|
if !closed {
|
|
|
|
fmt.Fprintf(w, "Termination already in progress.")
|
2020-11-09 15:09:39 -08:00
|
|
|
}
|
2014-04-14 16:02:15 -07:00
|
|
|
}
|
|
|
|
|
2015-08-11 00:08:17 -07:00
|
|
|
func (h *Handler) reload(w http.ResponseWriter, r *http.Request) {
|
2016-07-11 07:24:54 -07:00
|
|
|
rc := make(chan error)
|
|
|
|
h.reloadCh <- rc
|
|
|
|
if err := <-rc; err != nil {
|
|
|
|
http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError)
|
|
|
|
}
|
2015-08-11 00:08:17 -07:00
|
|
|
}
|
|
|
|
|
2015-06-30 05:38:01 -07:00
|
|
|
func (h *Handler) consolesPath() string {
|
2015-06-15 03:50:53 -07:00
|
|
|
if _, err := os.Stat(h.options.ConsoleTemplatesPath + "/index.html"); !os.IsNotExist(err) {
|
2015-06-30 05:38:01 -07:00
|
|
|
return h.options.ExternalURL.Path + "/consoles/index.html"
|
2014-10-02 07:44:47 -07:00
|
|
|
}
|
2015-06-15 03:50:53 -07:00
|
|
|
if h.options.UserAssetsPath != "" {
|
|
|
|
if _, err := os.Stat(h.options.UserAssetsPath + "/index.html"); !os.IsNotExist(err) {
|
2015-06-30 05:38:01 -07:00
|
|
|
return h.options.ExternalURL.Path + "/user/index.html"
|
2014-10-02 07:44:47 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2020-01-10 04:56:36 -08:00
|
|
|
func setPathWithPrefix(prefix string) func(handlerName string, handler http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return func(handlerName string, handler http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
handler(w, r.WithContext(httputil.ContextWithPath(r.Context(), prefix+r.URL.Path)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|