blob: 84f7f92b1787785faa5192f3c08956fc33ea5490 [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// Copyright 2018 Serge Bazanski
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Based off github.com/youtube/doorman/blob/master/go/status/status.go
16
17// Copyright 2016 Google, Inc.
18//
19// Licensed under the Apache License, Version 2.0 (the "License");
20// you may not use this file except in compliance with the License.
21// You may obtain a copy of the License at
22//
23// http://www.apache.org/licenses/LICENSE-2.0
24//
25// Unless required by applicable law or agreed to in writing, software
26// distributed under the License is distributed on an "AS IS" BASIS,
27// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28// See the License for the specific language governing permissions and
29// limitations under the License.
30
31package statusz
32
33import (
34 "bytes"
35 "context"
36 "crypto/sha256"
37 "fmt"
38 "html"
39 "html/template"
40 "io"
41 "net/http"
42 "os"
43 "os/user"
44 "path/filepath"
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +020045 "strconv"
Serge Bazanskicc25bdf2018-10-25 14:02:58 +020046 "sync"
47 "time"
48
49 "github.com/golang/glog"
Serge Bazanskicc25bdf2018-10-25 14:02:58 +020050)
51
52var (
53 binaryName = filepath.Base(os.Args[0])
54 binaryHash string
55 hostname string
56 username string
57 serverStart = time.Now()
58
59 lock sync.Mutex
60 sections []section
61 tmpl = template.Must(reparse(nil))
62 funcs = make(template.FuncMap)
63
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +020064 GitCommit = "unknown"
65 GitVersion = "unknown"
66 Builder = "unknown"
67 BuildTimestamp = "0"
68 // mirko populates this
69 PublicAddress = "#"
70
Serge Bazanskicc25bdf2018-10-25 14:02:58 +020071 DefaultMux = true
72)
73
74type section struct {
75 Banner string
76 Fragment string
77 F func() interface{}
78}
79
80var statusHTML = `<!DOCTYPE html>
81<html>
82<head>
83<title>Status for {{.BinaryName}}</title>
84<style>
85body {
Serge Bazanskicc25bdf2018-10-25 14:02:58 +020086background: #fff;
87}
88h1 {
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +020089font-family: sans-serif;
Serge Bazanskicc25bdf2018-10-25 14:02:58 +020090clear: both;
91width: 100%;
92text-align: center;
93font-size: 120%;
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +020094padding-top: 0.3em;
95padding-bottom: 0.3em;
Serge Bazanskicc25bdf2018-10-25 14:02:58 +020096background: #eeeeff;
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +020097margin-top: 1em;
Serge Bazanskicc25bdf2018-10-25 14:02:58 +020098}
99.lefthand {
100float: left;
101width: 80%;
102}
103.righthand {
104text-align: right;
105}
106</style>
107</head>
108<h1>Status for {{.BinaryName}}</h1>
109<div>
110<div class=lefthand>
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +0200111Started: {{.StartTime}} -- up {{.Up}}<br>
112Built on {{.BuildTime}}<br>
113Built at {{.Builder}}<br>
114Built from git checkout <a href="https://gerrit.hackerspace.pl/plugins/gitiles/hscloud/+/{{.GitCommit}}">{{.GitVersion}}</a><br>
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200115SHA256 {{.BinaryHash}}<br>
116</div>
117<div class=righthand>
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +0200118Running as {{.Username}} on <a href="{{.PublicAddress}}">{{.Hostname}}</a><br>
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200119Load {{.LoadAvg}}<br>
120View <a href=/debug/status>status</a>,
Sergiusz Bazanski400ac7a2020-01-23 14:17:30 +0100121 <a href=/debug/requests>requests</a>,
122 <a href=/debug/pprof>profile</a>
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200123</div>
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +0200124<div style="clear: both;"> </div>
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200125</div>`
126
127func reparse(sections []section) (*template.Template, error) {
128 var buf bytes.Buffer
129
130 io.WriteString(&buf, `{{define "status"}}`)
131 io.WriteString(&buf, statusHTML)
132
133 for i, sec := range sections {
134 fmt.Fprintf(&buf, "<h1>%s</h1>\n", html.EscapeString(sec.Banner))
135 fmt.Fprintf(&buf, "{{$sec := index .Sections %d}}\n", i)
136 fmt.Fprintf(&buf, `{{template "sec-%d" call $sec.F}}`+"\n", i)
137 }
138 fmt.Fprintf(&buf, `</html>`)
139 io.WriteString(&buf, "{{end}}\n")
140
141 for i, sec := range sections {
142 fmt.Fprintf(&buf, `{{define "sec-%d"}}%s{{end}}\n`, i, sec.Fragment)
143 }
144 return template.New("").Funcs(funcs).Parse(buf.String())
145}
146
147func StatusHandler(w http.ResponseWriter, r *http.Request) {
148 lock.Lock()
149 defer lock.Unlock()
150
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +0200151 buildTime := time.Unix(0, 0)
152 if buildTimeNum, err := strconv.Atoi(BuildTimestamp); err == nil {
153 buildTime = time.Unix(int64(buildTimeNum), 0)
154 }
155
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200156 data := struct {
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +0200157 Sections []section
158 BinaryName string
159 BinaryHash string
160 GitVersion string
161 GitCommit string
162 Builder string
163 BuildTime string
164 Hostname string
165 Username string
166 StartTime string
167 Up string
168 LoadAvg string
169 PublicAddress string
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200170 }{
Sergiusz Bazanski8fe651b2019-07-21 23:50:05 +0200171 Sections: sections,
172 BinaryName: binaryName,
173 BinaryHash: binaryHash,
174 GitVersion: GitVersion,
175 GitCommit: GitCommit,
176 Builder: Builder,
177 BuildTime: fmt.Sprintf("%s (%d)", buildTime.Format(time.RFC1123), buildTime.Unix()),
178 Hostname: hostname,
179 Username: username,
180 StartTime: serverStart.Format(time.RFC1123),
181 Up: time.Since(serverStart).String(),
182 LoadAvg: loadAverage(),
183 PublicAddress: PublicAddress,
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200184 }
185
186 if err := tmpl.ExecuteTemplate(w, "status", data); err != nil {
187 glog.Errorf("servenv: couldn't execute template: %v", err)
188 }
189}
190
191func init() {
192 var err error
193 hostname, err = os.Hostname()
194 if err != nil {
195 glog.Fatalf("os.Hostname: %v", err)
196 }
197
198 user, err := user.Current()
199 if err != nil {
200 glog.Fatalf("user.Current: %v", err)
201 }
202 username = fmt.Sprintf("%s (%s)", user.Username, user.Uid)
203
Serge Bazanski8fab2be2018-10-25 23:37:37 +0200204 exec, err := os.Executable()
205 if err == nil {
206 f, err := os.Open(exec)
207 if err == nil {
208 h := sha256.New()
209 if _, err := io.Copy(h, f); err != nil {
210 glog.Fatalf("io.Copy: %v", err)
211 }
212 binaryHash = fmt.Sprintf("%x", h.Sum(nil))
213 } else {
214 glog.Errorf("Could not get SHA256 of binary: os.Open(%q): %v", exec, err)
215 binaryHash = "could not read executable"
216 }
217 } else {
218 glog.Errorf("Could not get SHA256 of binary: os.Executable(): %v", err)
219 binaryHash = "could not get executable"
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200220 }
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200221
222 if DefaultMux {
223 http.HandleFunc("/debug/status", StatusHandler)
224 }
225}
226
227// AddStatusPart adds a new section to status. frag is used as a
228// subtemplate of the template used to render /debug/status, and will
229// be executed using the value of invoking f at the time of the
230// /debug/status request. frag is parsed and executed with the
231// html/template package. Functions registered with AddStatusFuncs
232// may be used in the template.
233func AddStatusPart(banner, frag string, f func(context.Context) interface{}) {
234 lock.Lock()
235 defer lock.Unlock()
236
237 secs := append(sections, section{
238 Banner: banner,
239 Fragment: frag,
240 F: func() interface{} { return f(context.Background()) },
241 })
242
243 var err error
244 tmpl, err = reparse(secs)
245 if err != nil {
246 secs[len(secs)-1] = section{
247 Banner: banner,
248 Fragment: "<code>bad status template: {{.}}</code>",
249 F: func() interface{} { return err },
250 }
251 }
252 tmpl, _ = reparse(secs)
253 sections = secs
254}
255
256// AddStatusSection registers a function that generates extra
257// information for /debug/status. If banner is not empty, it will be
258// used as a header before the information. If more complex output
259// than a simple string is required use AddStatusPart instead.
260func AddStatusSection(banner string, f func(context.Context) string) {
261 AddStatusPart(banner, `{{.}}`, func(ctx context.Context) interface{} { return f(ctx) })
262}