debug.go 4.81 KB
Newer Older
1
package tabacco
ale's avatar
ale committed
2 3 4

import (
	"html/template"
ale's avatar
ale committed
5
	"log"
ale's avatar
ale committed
6
	"net/http"
ale's avatar
ale committed
7 8 9 10
	"net/http/pprof"
	"time"

	"github.com/prometheus/client_golang/prometheus/promhttp"
ale's avatar
ale committed
11 12 13 14 15 16 17 18 19 20
)

var (
	headerTpl = `<!doctype html>
<html lang="en">
  <head>
    <title>Tabacco - Debug</title>
    <style type="text/css">
body { background: white; font-family: "Helvetica", sans-serif; }
.table th { text-align: left; font-weight: bold; }
ale's avatar
ale committed
21
.table td { text-align: left; padding-right: 10px; }
ale's avatar
ale committed
22
.table thead tr { border-bottom: 2px solid #333; }
ale's avatar
ale committed
23 24 25
.error { color: #900; }
.ok { color: #090; }
.id { color: #999; }
ale's avatar
ale committed
26 27 28 29 30
    </style>
  </head>
  <body>
`

31 32 33 34
	indexDebugTpl = `{{template "header"}}

<h1>tabacco</h1>

35 36 37
<p><a href="/debug/jobs">job status</a></p>
<p><a href="/debug/sched">schedule</a></p>
<p><a href="/metrics">metrics</a></p>
38 39 40 41

{{template "footer"}}
`

ale's avatar
ale committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
	stateManagerDebugTpl = `{{template "header"}}
{{define "job_status"}}
<table class="table">
  <thead>
    <tr>
      <th>ID</th>
      <th>Name</th>
      <th>Status</th>
      <th>Started At</th>
      <th>Completed At</th>
      <th>Error</th>
    </tr>
  </thead>
  <tbody>
    {{range .}}
    <tr>
ale's avatar
ale committed
58 59
      <td class="id">{{.ID}}</td>
      <td>{{.Name}}</td>
ale's avatar
ale committed
60
      <td>{{.Status}}</td>
ale's avatar
ale committed
61 62 63
      <td>{{if not .StartedAt.IsZero}}{{timefmt .StartedAt}}{{end}}</td>
      <td>{{if not .CompletedAt.IsZero}}{{timefmt .CompletedAt}}{{end}}</td>
      <td>{{if .Err}}<span class="error">{{.Err}}</span>{{else if not .CompletedAt.IsZero}}<span class="ok">ok</span>{{end}}</td>
ale's avatar
ale committed
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    </tr>
    {{end}}
  </tbody>
</table>
{{end}}

<h1>Jobs</h1>

<h3>Running ({{.NumRunning}})</h3>
{{template "job_status" .Running}}

<h3>Pending ({{.NumPending}})</h3>
{{template "job_status" .Pending}}

<h3>Done ({{.NumDone}})</h3>
{{template "job_status" .Done}}

{{template "footer"}}
`

	schedulerDebugTpl = `{{template "header"}}
{{define "schedule_status"}}
<table class="table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Schedule</th>
      <th>Last Run</th>
      <th>Next Run</th>
    </tr>
  </thead>
  <tbody>
    {{range .}}
    <tr>
ale's avatar
ale committed
98 99 100 101 102
      <td>{{.Name}}</td>
      <td>{{.Schedule}}</td>
      <td>{{if not .Prev.IsZero}}{{timefmt .Prev}}{{end}}</td>
      <td>{{if not .Next.IsZero}}{{timefmt .Next}}{{end}}</td>
      <td>{{if .LastError}}<span class="error">{{.LastError}}</span>{{else if not .Prev.IsZero}}<span class="ok">ok</span>{{end}}</td>
ale's avatar
ale committed
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
    </tr>
    {{end}}
  </tbody>
</table>
{{end}}

<h1>Schedule</h1>
{{template "schedule_status" .Schedule}}

{{template "footer"}}
`

	footerTpl = `
  </body>
</html>`

	debugTpl *template.Template
)

ale's avatar
ale committed
122 123 124 125
func timefmt(t time.Time) string {
	return t.Format(time.Stamp)
}

ale's avatar
ale committed
126
func init() {
ale's avatar
ale committed
127 128 129
	debugTpl = template.New("").Funcs(template.FuncMap{
		"timefmt": timefmt,
	})
ale's avatar
ale committed
130 131
	template.Must(debugTpl.New("header").Parse(headerTpl))
	template.Must(debugTpl.New("footer").Parse(footerTpl))
132
	template.Must(debugTpl.New("index").Parse(indexDebugTpl))
ale's avatar
ale committed
133 134 135 136
	template.Must(debugTpl.New("state_manager_debug_page").Parse(stateManagerDebugTpl))
	template.Must(debugTpl.New("scheduler_debug_page").Parse(schedulerDebugTpl))
}

137
// Job status debug handler.
138 139
func (a *Agent) handleStateManagerDebug(w http.ResponseWriter, r *http.Request) {
	pending, running, done := a.mgr.GetStatus()
ale's avatar
ale committed
140 141

	w.Header().Set("Content-Type", "text/html")
ale's avatar
ale committed
142
	if err := debugTpl.Lookup("state_manager_debug_page").Execute(w, map[string]interface{}{
ale's avatar
ale committed
143 144 145 146 147 148
		"Pending":    pending,
		"NumPending": len(pending),
		"Running":    running,
		"NumRunning": len(running),
		"Done":       done,
		"NumDone":    len(done),
ale's avatar
ale committed
149 150 151
	}); err != nil {
		log.Printf("state_manager_debug_page: %v", err)
	}
ale's avatar
ale committed
152 153
}

154
// Scheduler debug handler.
155
func (a *Agent) handleSchedulerDebug(w http.ResponseWriter, r *http.Request) {
ale's avatar
ale committed
156
	w.Header().Set("Content-Type", "text/html")
ale's avatar
ale committed
157
	if err := debugTpl.Lookup("scheduler_debug_page").Execute(w, map[string]interface{}{
158
		"Schedule": a.sched.GetStatus(),
ale's avatar
ale committed
159 160 161
	}); err != nil {
		log.Printf("scheduler_debug_page: %v", err)
	}
ale's avatar
ale committed
162
}
163 164 165 166 167 168 169 170 171

// Agent debug handler page, with links to the other two.
func (a *Agent) handleDebugPage(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}

	w.Header().Set("Content-Type", "text/html")
ale's avatar
ale committed
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
	debugTpl.Lookup("index").Execute(w, nil) // nolint
}

// StartHTTPServer starts a HTTP server that exports Prometheus
// metrics and debug information.
func (a *Agent) StartHTTPServer(addr string) {
	h := http.NewServeMux()
	h.HandleFunc("/debug/jobs", a.handleStateManagerDebug)
	h.HandleFunc("/debug/sched", a.handleSchedulerDebug)

	// Add all the pprof handlers, they're useful for debugging.
	h.HandleFunc("/debug/pprof/", pprof.Index)
	h.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	h.HandleFunc("/debug/pprof/profile", pprof.Profile)
	h.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	h.HandleFunc("/debug/pprof/trace", pprof.Trace)

	h.Handle("/metrics", promhttp.Handler())
	h.HandleFunc("/", a.handleDebugPage)
191
	go http.ListenAndServe(addr, h) // nolint
192
}