Partition the status tab into items in a dropdown

I got feedback from different sources about rules and targets being
too heavy in the status tab if their are lots of them.

This change also allows for more fine-granular locking.
This commit is contained in:
beorn7 2016-05-13 17:59:59 +02:00
parent 8ec10ae7a5
commit da8cb10b43
10 changed files with 619 additions and 590 deletions

View file

@ -103,13 +103,6 @@ func Main() int {
flags[f.Name] = f.Value.String()
})
status := &web.PrometheusStatus{
TargetPools: targetManager.Pools,
Rules: ruleManager.Rules,
Flags: flags,
Birth: time.Now(),
}
version := &web.PrometheusVersion{
Version: version.Version,
Revision: version.Revision,
@ -119,9 +112,9 @@ func Main() int {
GoVersion: version.GoVersion,
}
webHandler := web.New(memStorage, queryEngine, ruleManager, status, version, &cfg.web)
webHandler := web.New(memStorage, queryEngine, targetManager, ruleManager, version, flags, &cfg.web)
reloadables = append(reloadables, status, targetManager, ruleManager, webHandler, notifier)
reloadables = append(reloadables, targetManager, ruleManager, webHandler, notifier)
if !reloadConfig(cfg.configFile, reloadables...) {
return 1

View file

@ -26,7 +26,7 @@ func TestParseTimestampOrNow(t *testing.T) {
t.Fatalf("err = %s; want nil", err)
}
if !testNow().Equal(ts) {
t.Fatalf("ts = %v; want ts = %v", ts, testNow)
t.Fatalf("ts = %v; want ts = %v", ts, testNow())
}
ts, err = parseTimestampOrNow("1426956073.12345", testNow())

File diff suppressed because one or more lines are too long

View file

@ -39,7 +39,16 @@
{{ end }}
<li><a href="{{ pathPrefix }}/alerts">Alerts</a></li>
<li><a href="{{ pathPrefix }}/graph">Graph</a></li>
<li><a href="{{ pathPrefix }}/status">Status</a></li>
w <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Status <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/status">Runtime &amp; Build Information</a></li>
<li><a href="/flags">Command-Line Flags</a></li>
<li><a href="/config">Configuration</a></li>
<li><a href="/rules">Rules</a></li>
<li><a href="/targets">Targets</a></li>
</ul>
</li>
<li>
<a href="https://prometheus.io" target="_blank">Help</a>
</li>

View file

@ -0,0 +1,8 @@
{{define "head"}}<!-- nix -->{{end}}
{{define "content"}}
<div class="container-fluid">
<h2 id="configuration">Configuration</h2>
<pre>{{.}}</pre>
</div>
{{end}}

View file

@ -0,0 +1,17 @@
{{define "head"}}<!-- nix -->{{end}}
{{define "content"}}
<div class="container-fluid">
<h2 id="startupflags">Command-Line Flags</h2>
<table class="table table-condensed table-bordered table-striped table-hover">
<tbody>
{{range $key, $value := . }}
<tr>
<th scope="row">{{$key}}</th>
<td>{{$value}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}

View file

@ -0,0 +1,8 @@
{{define "head"}}<!-- nix -->{{end}}
{{define "content"}}
<div class="container-fluid">
<h2 id="rules">Rules</h2>
<pre>{{range .Rules}}{{.HTMLSnippet pathPrefix}}<br/>{{end}}</pre>
</div>
{{end}}

View file

@ -7,7 +7,7 @@
<tbody>
<tr>
<th>Uptime</th>
<td>{{.Status.Birth.UTC}}</td>
<td>{{.Birth.UTC}}</td>
</tr>
</tbody>
</table>
@ -41,76 +41,5 @@
</tr>
</tbody>
</table>
<h2 id="configuration">Configuration</h2>
<pre>{{.Status.Config}}</pre>
<h2 id="rules">Rules</h2>
<pre>{{range call .Status.Rules}}{{.HTMLSnippet pathPrefix}}<br/>{{end}}</pre>
<h2 id="targets">Targets</h2>
<table class="table table-condensed table-bordered table-striped table-hover">
{{range $job, $pool := call .Status.TargetPools}}
<thead>
<tr><th colspan="5" class="job_header">{{$job}}</th></tr>
<tr>
<th>Endpoint</th>
<th>State</th>
<th>Labels</th>
<th>Last Scrape</th>
<th>Error</th>
</tr>
</thead>
<tbody>
{{range $pool}}
<tr>
<td>
<a href="{{.URL | globalURL}}">{{.URL.Scheme}}://{{.URL.Host}}{{.URL.Path}}</a><br>
{{range $label, $values := .URL.Query }}
{{range $i, $value := $values}}
<span class="label label-primary">{{$label}}="{{$value}}"</span>
{{end}}
{{end}}
</td>
<td>
<span class="alert alert-{{ .Health | healthToClass }} state_indicator text-uppercase">
{{.Health}}
</span>
</td>
<td>
<span class="cursor-pointer" data-toggle="tooltip" title="" data-html=true data-original-title="<b>Before relabeling:</b>{{range $k, $v := .MetaLabels}}<br>{{$k | html | html}}=&quot;{{$v | html | html}}&quot;{{end}}">
{{$labels := stripLabels .Labels "job" "instance"}}
{{range $label, $value := $labels}}
<span class="label label-primary">{{$label}}="{{$value}}"</span>
{{else}}
<span class="label label-default">none</span>
{{end}}
</span>
</td>
<td>
{{if .LastScrape.IsZero}}Never{{else}}{{since .LastScrape}} ago{{end}}
</td>
<td>
{{if .LastError}}
<span class="alert alert-danger state_indicator">{{.LastError}}</span>
{{end}}
</td>
</tr>
{{end}}
</tbody>
{{end}}
</table>
<h2 id="startupflags">Startup Flags</h2>
<table class="table table-condensed table-bordered table-striped table-hover">
<tbody>
{{range $key, $value := .Status.Flags}}
<tr>
<th scope="row">{{$key}}</th>
<td>{{$value}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}

View file

@ -0,0 +1,58 @@
{{define "head"}}<!-- nix -->{{end}}
{{define "content"}}
<div class="container-fluid">
<h2 id="targets">Targets</h2>
<table class="table table-condensed table-bordered table-striped table-hover">
{{range $job, $pool := .Pools}}
<thead>
<tr><th colspan="5" class="job_header">{{$job}}</th></tr>
<tr>
<th>Endpoint</th>
<th>State</th>
<th>Labels</th>
<th>Last Scrape</th>
<th>Error</th>
</tr>
</thead>
<tbody>
{{range $pool}}
<tr>
<td>
<a href="{{.URL | globalURL}}">{{.URL.Scheme}}://{{.URL.Host}}{{.URL.Path}}</a><br>
{{range $label, $values := .URL.Query }}
{{range $i, $value := $values}}
<span class="label label-primary">{{$label}}="{{$value}}"</span>
{{end}}
{{end}}
</td>
<td>
<span class="alert alert-{{ .Health | healthToClass }} state_indicator text-uppercase">
{{.Health}}
</span>
</td>
<td>
<span class="cursor-pointer" data-toggle="tooltip" title="" data-html=true data-original-title="<b>Before relabeling:</b>{{range $k, $v := .MetaLabels}}<br>{{$k | html | html}}=&quot;{{$v | html | html}}&quot;{{end}}">
{{$labels := stripLabels .Labels "job" "instance"}}
{{range $label, $value := $labels}}
<span class="label label-primary">{{$label}}="{{$value}}"</span>
{{else}}
<span class="label label-default">none</span>
{{end}}
</span>
</td>
<td>
{{if .LastScrape.IsZero}}Never{{else}}{{since .LastScrape}} ago{{end}}
</td>
<td>
{{if .LastError}}
<span class="alert alert-danger state_indicator">{{.LastError}}</span>
{{end}}
</td>
</tr>
{{end}}
</tbody>
{{end}}
</table>
</div>
{{end}}

View file

@ -53,20 +53,23 @@ var localhostRepresentations = []string{"127.0.0.1", "localhost"}
// Handler serves various HTTP endpoints of the Prometheus server
type Handler struct {
ruleManager *rules.Manager
queryEngine *promql.Engine
storage local.Storage
targetManager *retrieval.TargetManager
ruleManager *rules.Manager
queryEngine *promql.Engine
storage local.Storage
apiV1 *v1.API
apiLegacy *legacy.API
router *route.Router
listenErrCh chan error
quitCh chan struct{}
reloadCh chan struct{}
options *Options
statusInfo *PrometheusStatus
versionInfo *PrometheusVersion
router *route.Router
listenErrCh chan error
quitCh chan struct{}
reloadCh chan struct{}
options *Options
configString string
versionInfo *PrometheusVersion
birth time.Time
flagsMap map[string]string
externalLabels model.LabelSet
mtx sync.RWMutex
@ -79,38 +82,12 @@ func (h *Handler) ApplyConfig(conf *config.Config) bool {
defer h.mtx.Unlock()
h.externalLabels = conf.GlobalConfig.ExternalLabels
h.configString = conf.String()
return true
}
// PrometheusStatus contains various information about the status
// of the running Prometheus process.
type PrometheusStatus struct {
Birth time.Time
Flags map[string]string
Config string
// A function that returns the current scrape targets pooled
// by their job name.
TargetPools func() map[string]retrieval.Targets
// A function that returns all loaded rules.
Rules func() []rules.Rule
mu sync.RWMutex
}
// ApplyConfig updates the status state as the new config requires.
// Returns true on success.
func (s *PrometheusStatus) ApplyConfig(conf *config.Config) bool {
s.mu.Lock()
defer s.mu.Unlock()
s.Config = conf.String()
return true
}
// PrometheusVersion contains various build information about Prometheus
// PrometheusVersion contains build information about Prometheus.
type PrometheusVersion struct {
Version string `json:"version"`
Revision string `json:"revision"`
@ -133,7 +110,15 @@ type Options struct {
}
// New initializes a new web Handler.
func New(st local.Storage, qe *promql.Engine, rm *rules.Manager, status *PrometheusStatus, version *PrometheusVersion, o *Options) *Handler {
func New(
st local.Storage,
qe *promql.Engine,
tm *retrieval.TargetManager,
rm *rules.Manager,
version *PrometheusVersion,
flags map[string]string,
o *Options,
) *Handler {
router := route.New()
h := &Handler{
@ -142,12 +127,14 @@ func New(st local.Storage, qe *promql.Engine, rm *rules.Manager, status *Prometh
quitCh: make(chan struct{}),
reloadCh: make(chan struct{}),
options: o,
statusInfo: status,
versionInfo: version,
birth: time.Now(),
flagsMap: flags,
ruleManager: rm,
queryEngine: qe,
storage: st,
targetManager: tm,
ruleManager: rm,
queryEngine: qe,
storage: st,
apiV1: v1.NewAPI(qe, st),
apiLegacy: &legacy.API{
@ -171,10 +158,14 @@ func New(st local.Storage, qe *promql.Engine, rm *rules.Manager, status *Prometh
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
router.Redirect(w, r, "/graph", http.StatusFound)
})
router.Get("/graph", instrf("graph", h.graph))
router.Get("/status", instrf("status", h.status))
router.Get("/alerts", instrf("alerts", h.alerts))
router.Get("/graph", instrf("graph", h.graph))
router.Get("/status", instrf("status", h.status))
router.Get("/flags", instrf("flags", h.flags))
router.Get("/config", instrf("config", h.config))
router.Get("/rules", instrf("rules", h.rules))
router.Get("/targets", instrf("targets", h.targets))
router.Get("/version", instrf("version", h.version))
router.Get("/heap", instrf("heap", dumpHeap))
@ -322,18 +313,34 @@ func (h *Handler) graph(w http.ResponseWriter, r *http.Request) {
}
func (h *Handler) status(w http.ResponseWriter, r *http.Request) {
h.statusInfo.mu.RLock()
defer h.statusInfo.mu.RUnlock()
h.executeTemplate(w, "status.html", struct {
Status *PrometheusStatus
Birth time.Time
Version *PrometheusVersion
}{
Status: h.statusInfo,
Birth: h.birth,
Version: h.versionInfo,
})
}
func (h *Handler) flags(w http.ResponseWriter, r *http.Request) {
h.executeTemplate(w, "flags.html", h.flagsMap)
}
func (h *Handler) config(w http.ResponseWriter, r *http.Request) {
h.mtx.RLock()
defer h.mtx.RUnlock()
h.executeTemplate(w, "config.html", h.configString)
}
func (h *Handler) rules(w http.ResponseWriter, r *http.Request) {
h.executeTemplate(w, "rules.html", h.ruleManager)
}
func (h *Handler) targets(w http.ResponseWriter, r *http.Request) {
h.executeTemplate(w, "targets.html", h.targetManager)
}
func (h *Handler) version(w http.ResponseWriter, r *http.Request) {
dec := json.NewEncoder(w)
if err := dec.Encode(h.versionInfo); err != nil {