package main import ( "context" "flag" "fmt" "html/template" "net/http" "os" "runtime" "time" "blitiri.com.ar/go/chasquid/internal/config" "blitiri.com.ar/go/chasquid/internal/expvarom" "blitiri.com.ar/go/log" "google.golang.org/protobuf/encoding/prototext" // To enable live profiling in the monitoring server. _ "net/http/pprof" ) func launchMonitoringServer(conf *config.Config) { log.Infof("Monitoring HTTP server listening on %s", conf.MonitoringAddress) osHostname, _ := os.Hostname() indexData := struct { Version string GoVersion string SourceDate time.Time StartTime time.Time Config *config.Config Hostname string }{ Version: version, GoVersion: runtime.Version(), SourceDate: sourceDate, StartTime: time.Now(), Config: conf, Hostname: osHostname, } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } if err := monitoringHTMLIndex.Execute(w, indexData); err != nil { log.Infof("monitoring handler error: %v", err) } }) srv := &http.Server{Addr: conf.MonitoringAddress} http.HandleFunc("/exit", exitHandler(srv)) http.HandleFunc("/metrics", expvarom.MetricsHandler) http.HandleFunc("/debug/flags", debugFlagsHandler) http.HandleFunc("/debug/config", debugConfigHandler(conf)) if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("Monitoring server failed: %v", err) } } // Functions available inside the templates. var tmplFuncs = template.FuncMap{ "since": time.Since, "roundDuration": roundDuration, } // Static index for the monitoring website. var monitoringHTMLIndex = template.Must( template.New("index").Funcs(tmplFuncs).Parse( ` {{.Hostname}}: chasquid monitoring

chasquid @{{.Config.Hostname}}

chasquid {{.Version}}
source date {{.SourceDate.Format "2006-01-02 15:04:05 -0700"}}
built with {{.GoVersion}}

started {{.StartTime.Format "Mon, 2006-01-02 15:04:05 -0700"}}
up for {{.StartTime | since | roundDuration}}
os hostname {{.Hostname}}

`)) func exitHandler(srv *http.Server) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Use POST method for exiting", http.StatusMethodNotAllowed) return } log.Infof("Received /exit") http.Error(w, "OK exiting", http.StatusOK) // Launch srv.Shutdown asynchronously, and then exit. // The http documentation says to wait for Shutdown to return before // exiting, to gracefully close all ongoing requests. go func() { if err := srv.Shutdown(context.Background()); err != nil { log.Fatalf("Monitoring server shutdown failed: %v", err) } os.Exit(0) }() } } func debugFlagsHandler(w http.ResponseWriter, r *http.Request) { visited := make(map[string]bool) // Print set flags first, then the rest. flag.Visit(func(f *flag.Flag) { fmt.Fprintf(w, "-%s=%s\n", f.Name, f.Value.String()) visited[f.Name] = true }) fmt.Fprintf(w, "\n") flag.VisitAll(func(f *flag.Flag) { if !visited[f.Name] { fmt.Fprintf(w, "-%s=%s\n", f.Name, f.Value.String()) } }) } func debugConfigHandler(conf *config.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(prototext.Format(conf))) } } func roundDuration(d time.Duration) time.Duration { return d.Round(time.Second) }