From d7ea0397d42cea1221a60ff95006466f55c99acc Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Tue, 23 Jun 2015 17:46:50 +0200 Subject: [PATCH 1/2] Use templates.TemplateExpander for all page templates. This will allow us to use all of our shiny templating functions in the main page templates as well. --- web/web.go | 69 +++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/web/web.go b/web/web.go index 844d0cda3..3059835c5 100644 --- a/web/web.go +++ b/web/web.go @@ -27,8 +27,8 @@ import ( "sync" "time" - template_std "html/template" pprof_runtime "runtime/pprof" + template_text "text/template" clientmodel "github.com/prometheus/client_golang/model" "github.com/prometheus/client_golang/prometheus" @@ -320,11 +320,26 @@ func (h *Handler) getConsoles() string { return "" } -func (h *Handler) getTemplate(name string) (*template_std.Template, error) { - t := template_std.New("_base") - var err error +func (h *Handler) getTemplate(name string) (string, error) { + baseTmpl, err := h.getTemplateFile("_base") + if err != nil { + return "", fmt.Errorf("Error reading base template: %s", err) + } + pageTmpl, err := h.getTemplateFile(name) + if err != nil { + return "", fmt.Errorf("Error reading page template %s: %s", name, err) + } + return baseTmpl + pageTmpl, nil +} - t.Funcs(template_std.FuncMap{ +func (h *Handler) executeTemplate(w http.ResponseWriter, name string, data interface{}) { + text, err := h.getTemplate(name) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + tmpl := template.NewTemplateExpander(text, name, data, clientmodel.Now(), h.queryEngine, h.options.PathPrefix) + tmpl.Funcs(template_text.FuncMap{ "since": time.Since, "getConsoles": h.getConsoles, "pathPrefix": func() string { return h.options.PathPrefix }, @@ -352,40 +367,26 @@ func (h *Handler) getTemplate(name string) (*template_std.Template, error) { return "danger" } }, + "alertStateToClass": func(as rules.AlertState) string { + switch as { + case rules.StateInactive: + return "success" + case rules.StatePending: + return "warning" + case rules.StateFiring: + return "danger" + default: + panic("unknown alert state") + } + }, }) - file, err := h.getTemplateFile("_base") + result, err := tmpl.ExpandHTML(nil) if err != nil { - log.Errorln("Could not read base template:", err) - return nil, err - } - t, err = t.Parse(file) - if err != nil { - log.Errorln("Could not parse base template:", err) - } - - file, err = h.getTemplateFile(name) - if err != nil { - log.Error("Could not read template %s: %s", name, err) - return nil, err - } - t, err = t.Parse(file) - if err != nil { - log.Errorf("Could not parse template %s: %s", name, err) - } - return t, err -} - -func (h *Handler) executeTemplate(w http.ResponseWriter, name string, data interface{}) { - tpl, err := h.getTemplate(name) - if err != nil { - log.Error("Error preparing layout template: ", err) + http.Error(w, err.Error(), http.StatusInternalServerError) return } - err = tpl.Execute(w, data) - if err != nil { - log.Error("Error executing template: ", err) - } + io.WriteString(w, result) } func dumpHeap(w http.ResponseWriter, r *http.Request) { From d868264bb883835cbbf7f4c3c17c0442e1d6f576 Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Tue, 23 Jun 2015 17:46:57 +0200 Subject: [PATCH 2/2] Improve UI of /alerts page. Changes to the UI: - "Active Since" timestamps are now human-readable. - Alerting rules are now pretty-printed better. - Labels are no longer just strings, but alert bubbles (like we do on the status page for base labels). - Alert states and target health states are now capitalized in the presentation layer rather than at the source. --- retrieval/target.go | 6 ++--- rules/alerting.go | 35 +++++++++++++++++++++--------- template/template.go | 15 +++++++++++++ template/template_test.go | 9 ++++++-- web/blob/static/css/alerts.css | 16 -------------- web/blob/static/css/prometheus.css | 3 ++- web/blob/templates/alerts.html | 20 ++++++++++------- web/blob/templates/status.html | 2 +- 8 files changed, 65 insertions(+), 41 deletions(-) diff --git a/retrieval/target.go b/retrieval/target.go index c51f5048c..ac52eb412 100644 --- a/retrieval/target.go +++ b/retrieval/target.go @@ -73,11 +73,11 @@ type TargetHealth int func (t TargetHealth) String() string { switch t { case HealthUnknown: - return "UNKNOWN" + return "unknown" case HealthGood: - return "HEALTHY" + return "healthy" case HealthBad: - return "UNHEALTHY" + return "unhealthy" } panic("unknown state") } diff --git a/rules/alerting.go b/rules/alerting.go index 54514db9b..49655740d 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -209,23 +209,38 @@ func (rule *AlertingRule) eval(timestamp clientmodel.Timestamp, engine *promql.E } func (rule *AlertingRule) String() string { - return fmt.Sprintf("ALERT %s IF %s FOR %s WITH %s", rule.name, rule.vector, strutil.DurationToString(rule.holdDuration), rule.labels) + s := fmt.Sprintf("ALERT %s", rule.name) + s += fmt.Sprintf("\n\tIF %s", rule.vector) + if rule.holdDuration > 0 { + s += fmt.Sprintf("\n\tFOR %s", strutil.DurationToString(rule.holdDuration)) + } + if len(rule.labels) > 0 { + s += fmt.Sprintf("\n\tWITH %s", rule.labels) + } + s += fmt.Sprintf("\n\tSUMMARY %q", rule.summary) + s += fmt.Sprintf("\n\tDESCRIPTION %q", rule.description) + return s } -// HTMLSnippet returns an HTML snippet representing this alerting rule. +// HTMLSnippet returns an HTML snippet representing this alerting rule. The +// resulting snippet is expected to be presented in a
 element, so that
+// line breaks and other returned whitespace is respected.
 func (rule *AlertingRule) HTMLSnippet(pathPrefix string) template.HTML {
 	alertMetric := clientmodel.Metric{
 		clientmodel.MetricNameLabel: alertMetricName,
 		alertNameLabel:              clientmodel.LabelValue(rule.name),
 	}
-	return template.HTML(fmt.Sprintf(
-		`ALERT %s IF %s FOR %s WITH %s`,
-		pathPrefix+strutil.GraphLinkForExpression(alertMetric.String()),
-		rule.name,
-		pathPrefix+strutil.GraphLinkForExpression(rule.vector.String()),
-		rule.vector,
-		strutil.DurationToString(rule.holdDuration),
-		rule.labels))
+	s := fmt.Sprintf("ALERT %s", pathPrefix+strutil.GraphLinkForExpression(alertMetric.String()), rule.name)
+	s += fmt.Sprintf("\n  IF %s", pathPrefix+strutil.GraphLinkForExpression(rule.vector.String()), rule.vector)
+	if rule.holdDuration > 0 {
+		s += fmt.Sprintf("\n  FOR %s", strutil.DurationToString(rule.holdDuration))
+	}
+	if len(rule.labels) > 0 {
+		s += fmt.Sprintf("\n  WITH %s", rule.labels)
+	}
+	s += fmt.Sprintf("\n  SUMMARY %q", rule.summary)
+	s += fmt.Sprintf("\n  DESCRIPTION %q", rule.description)
+	return template.HTML(s)
 }
 
 // State returns the "maximum" state: firing > pending > inactive.
diff --git a/template/template.go b/template/template.go
index e7060cc4e..7b7cdd267 100644
--- a/template/template.go
+++ b/template/template.go
@@ -238,6 +238,13 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c
 				}
 				return fmt.Sprintf("%.4g%ss", v, prefix)
 			},
+			"humanizeTimestamp": func(v float64) string {
+				if math.IsNaN(v) || math.IsInf(v, 0) {
+					return fmt.Sprintf("%.4g", v)
+				}
+				t := clientmodel.TimestampFromUnixNano(int64(v * 1000000000)).Time()
+				return fmt.Sprint(t)
+			},
 			"pathPrefix": func() string {
 				return pathPrefix
 			},
@@ -245,6 +252,14 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c
 	}
 }
 
+// Funcs adds the functions in fm to the templateExpander's function map.
+// Existing functions will be overwritten in case of conflict.
+func (te templateExpander) Funcs(fm text_template.FuncMap) {
+	for k, v := range fm {
+		te.funcMap[k] = v
+	}
+}
+
 // Expand a template.
 func (te templateExpander) Expand() (result string, resultErr error) {
 	// It'd better to have no alert description than to kill the whole process
diff --git a/template/template_test.go b/template/template_test.go
index b7e1b54c0..b3a92bc53 100644
--- a/template/template_test.go
+++ b/template/template_test.go
@@ -135,9 +135,14 @@ func TestTemplateExpansion(t *testing.T) {
 		},
 		{
 			// Humanize* Inf and NaN.
-			text:   "{{ range . }}{{ humanize . }}:{{ humanize1024 . }}:{{ humanizeDuration . }}:{{ end }}",
+			text:   "{{ range . }}{{ humanize . }}:{{ humanize1024 . }}:{{ humanizeDuration . }}:{{humanizeTimestamp .}}:{{ end }}",
 			input:  []float64{math.Inf(1), math.Inf(-1), math.NaN()},
-			output: "+Inf:+Inf:+Inf:-Inf:-Inf:-Inf:NaN:NaN:NaN:",
+			output: "+Inf:+Inf:+Inf:+Inf:-Inf:-Inf:-Inf:-Inf:NaN:NaN:NaN:NaN:",
+		},
+		{
+			// HumanizeTimestamp - clientmodel.SampleValue input.
+			text:   "{{ 1435065584.128 | humanizeTimestamp }}",
+			output: "2015-06-23 15:19:44.128 +0200 CEST",
 		},
 		{
 			// Title.
diff --git a/web/blob/static/css/alerts.css b/web/blob/static/css/alerts.css
index f7061c4d8..798fc3691 100644
--- a/web/blob/static/css/alerts.css
+++ b/web/blob/static/css/alerts.css
@@ -5,19 +5,3 @@
 .alert_details {
   display: none;
 }
-
-.silence_children_link {
-  margin-left: 5px;
-}
-
-.alert_rule {
-  padding: 5px;
-  color: #333;
-  background-color: #ddd;
-  font-family: monospace;
-  font-weight: normal;
-}
-
-.alert_description {
-  padding: 8px 0 8px 0;
-}
diff --git a/web/blob/static/css/prometheus.css b/web/blob/static/css/prometheus.css
index 623fd743c..c44c65ec1 100644
--- a/web/blob/static/css/prometheus.css
+++ b/web/blob/static/css/prometheus.css
@@ -10,8 +10,9 @@ th.job_header {
   padding-bottom: 10px;
 }
 
-.target_status_alert {
+.state_indicator {
   padding: 0 4px 0 4px;
+  text-transform: uppercase;
 }
 
 .literal_output td {
diff --git a/web/blob/templates/alerts.html b/web/blob/templates/alerts.html
index fc374e00d..bc9015dde 100644
--- a/web/blob/templates/alerts.html
+++ b/web/blob/templates/alerts.html
@@ -16,9 +16,9 @@
       
       
         
-          
- {{.HTMLSnippet pathPrefix}} - Silence All Children… +
+
{{.HTMLSnippet pathPrefix}}
+ Silence all instances of this alert…
{{if $activeAlerts}} @@ -30,12 +30,16 @@ {{range $activeAlerts}} - - - - + + + + - {{end}}
Silence
{{.Labels}}{{.State}}{{.ActiveSince}}
+ {{range $label, $value := .Labels}} + {{$label}}="{{$value}}" + {{end}} + {{.State}}{{.ActiveSince.Time}} {{.Value}}Silence… + Silence…
diff --git a/web/blob/templates/status.html b/web/blob/templates/status.html index becba8f6c..69678a32d 100644 --- a/web/blob/templates/status.html +++ b/web/blob/templates/status.html @@ -55,7 +55,7 @@ {{end}} - + {{.Status.Health}}