1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
package main
import (
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/log"
"github.com/prometheus/common/version"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
var (
configFile = kingpin.Flag(
"config.file",
"Path to configuration file.",
).String()
executablesPath = kingpin.Flag(
"freeipmi.path",
"Path to FreeIPMI executables.",
).Default("/usr/sbin").String()
listenAddress = kingpin.Flag(
"web.listen-address",
"Address to listen on for web interface and telemetry.",
).Default(":9290").String()
sc = &SafeConfig{
C: &Config{},
}
reloadCh chan chan error
)
func remoteIPMIHandler(w http.ResponseWriter, r *http.Request) {
target := r.URL.Query().Get("target")
if target == "" {
http.Error(w, "'target' parameter must be specified", 400)
return
}
// Remote scrape will not work without some kind of config, so be pedantic about it
module := r.URL.Query().Get("module")
if module == "" {
module = "default"
}
if !sc.HasModule(module) {
http.Error(w, fmt.Sprintf("Unknown module %q", module), http.StatusBadRequest)
return
}
log.Debugf("Scraping target '%s' with module '%s'", target, module)
registry := prometheus.NewRegistry()
remoteCollector := collector{target: target, module: module, config: sc}
registry.MustRegister(remoteCollector)
h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
h.ServeHTTP(w, r)
}
func updateConfiguration(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "POST":
rc := make(chan error)
reloadCh <- rc
if err := <-rc; err != nil {
http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError)
}
default:
log.Errorf("Only POST requests allowed for %s", r.URL)
w.Header().Set("Allow", "POST")
http.Error(w, "Only POST requests allowed", http.StatusMethodNotAllowed)
}
}
func main() {
log.AddFlags(kingpin.CommandLine)
kingpin.HelpFlag.Short('h')
kingpin.Version(version.Print("ipmi_exporter"))
kingpin.Parse()
log.Infoln("Starting ipmi_exporter")
// Bail early if the config is bad.
if err := sc.ReloadConfig(*configFile); err != nil {
log.Fatalf("Error parsing config file: %s", err)
}
hup := make(chan os.Signal)
reloadCh = make(chan chan error)
signal.Notify(hup, syscall.SIGHUP)
go func() {
for {
select {
case <-hup:
if err := sc.ReloadConfig(*configFile); err != nil {
log.Errorf("Error reloading config: %s", err)
}
case rc := <-reloadCh:
if err := sc.ReloadConfig(*configFile); err != nil {
log.Errorf("Error reloading config: %s", err)
rc <- err
} else {
rc <- nil
}
}
}
}()
localCollector := collector{target: targetLocal, module: "default", config: sc}
prometheus.MustRegister(&localCollector)
http.Handle("/metrics", promhttp.Handler()) // Regular metrics endpoint for local IPMI metrics.
http.HandleFunc("/ipmi", remoteIPMIHandler) // Endpoint to do IPMI scrapes.
http.HandleFunc("/-/reload", updateConfiguration) // Endpoint to reload configuration.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<html>
<head>
<title>IPMI Exporter</title>
<style>
label{
display:inline-block;
width:75px;
}
form label {
margin: 10px;
}
form input {
margin: 10px;
}
</style>
</head>
<body>
<h1>IPMI Exporter</h1>
<form action="/ipmi">
<label>Target:</label> <input type="text" name="target" placeholder="X.X.X.X" value="1.2.3.4"><br>
<input type="submit" value="Submit">
</form>
<p><a href="/metrics">Local metrics</a></p>
<p><a href="/config">Config</a></p>
</body>
</html>`))
})
log.Infof("Listening on %s", *listenAddress)
err := http.ListenAndServe(*listenAddress, nil)
if err != nil {
log.Fatal(err)
}
}
|