/
warren.go
183 lines (164 loc) · 4.78 KB
/
warren.go
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Warren is a program to act as part of a monitoring system on a home network.
// It exports data for external programs to acquire and log to timeseries
// databases. Currently, Warren exports data in a way that is intended for
// scraping by Prometheus - http://prometheus.io/.
package main
import (
"flag"
"fmt"
"log"
"net/http"
"os"
"syscall"
"time"
"github.com/BurntSushi/toml"
"github.com/huin/warren/cc"
"github.com/huin/warren/httpexport"
"github.com/huin/warren/linux"
"github.com/huin/warren/streammatch"
"github.com/huin/warren/systemd"
promm "github.com/prometheus/client_golang/prometheus"
)
var (
configFile = flag.String("config", "", "Path to configuration file")
)
type Config struct {
// Ignore unknown TOML keys found during parsing configuration.
// (unknown keys will be an error in future, for now they are simply logged)
IgnoreUnknownKeys bool `toml:"ignore_unknown_keys"`
LogPath string
Prometheus PrometheusConfig
CurrentCost []cc.Config
File []streammatch.FileCfg
Proc []streammatch.ProcCfg
System *linux.Config
Systemd *systemd.Config
HTTPExport []httpexport.Config
}
type PrometheusConfig struct {
HandlerPath string
// TODO: Deprecate ServeAddr and move into Config - it's not really specific
// to the Prometheus part of things.
ServeAddr string
}
func initLogging(logpath string) error {
if logpath == "" {
// Leave default logging as STDERR.
return nil
}
f, err := os.OpenFile(logpath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, syscall.S_IWUSR|syscall.S_IRUSR)
if err != nil {
return fmt.Errorf("failed to open log file: %v", err)
}
log.SetOutput(f)
return nil
}
func readConfig(filename string) (*Config, error) {
config := new(Config)
md, err := toml.DecodeFile(filename, &config)
if err != nil {
return nil, err
}
keys := md.Undecoded()
if !config.IgnoreUnknownKeys && len(keys) > 0 {
log.Printf("Found %d unknown keys in configuration file %q. This will be a fatal error in future, set `ignore_unknown_keys = true` to prevent this message or errors.", len(keys))
for _, key := range keys {
log.Printf("Unknown key: %q", key)
}
}
return config, nil
}
func monitorLoop(name string, fn func() error) {
for {
if err := fn(); err != nil {
log.Printf("%s monitoring error (restarting): %v", name, err)
} else {
log.Printf("%s returned without error (restarting)", name)
}
restartCounter.With(promm.Labels{"name": name}).Inc()
// Avoid tightlooping on recurring failure.
time.Sleep(5 * time.Second)
}
}
var restartCounter *promm.CounterVec
func init() {
restartCounter = promm.NewCounterVec(
promm.CounterOpts{
Namespace: "warren", Name: "running_monitor_restarts_total",
Help: "Number of times a running monitor has restarted. (count)",
},
[]string{"name"},
)
promm.MustRegister(restartCounter)
}
func main() {
flag.Parse()
if *configFile == "" {
log.Fatal("--config is required with a filename")
}
config, err := readConfig(*configFile)
if err != nil {
log.Fatal("Failed to read configuration: ", err)
}
initLogging(config.LogPath)
if len(config.CurrentCost) > 0 {
log.Printf("Starting %d CurrentCost collectors", len(config.CurrentCost))
}
for i, cfg := range config.CurrentCost {
c, err := cc.New(cfg)
if err != nil {
log.Fatalf("Error in CurrentCost[%d]: %v", i, err)
}
promm.MustRegister(c)
go monitorLoop("currentcost", c.Run)
}
if len(config.File) > 0 {
log.Printf("Starting %d File collectors", len(config.File))
}
for i, cfg := range config.File {
fc, err := streammatch.NewFileCollector(cfg)
if err != nil {
log.Fatalf("Error in File[%d]: %v", i, err)
}
promm.MustRegister(fc)
}
if len(config.Proc) > 0 {
log.Printf("Starting %d Proc collectors", len(config.Proc))
}
for i, cfg := range config.Proc {
c, err := streammatch.NewProcCollector(cfg)
if err != nil {
log.Fatalf("Error in Proc[%d]: %v", i, err)
}
promm.MustRegister(c)
}
if config.System != nil {
log.Print("Starting local system monitoring")
c, err := linux.New(*config.System)
if err != nil {
log.Fatalf("Error in System: %v", err)
}
promm.MustRegister(c)
}
if config.Systemd != nil {
log.Print("Starting local systemd monitoring")
c, err := systemd.New(*config.Systemd)
if err != nil {
log.Fatalf("Error in Systemd: %v", err)
}
promm.MustRegister(c)
}
if len(config.HTTPExport) > 0 {
log.Printf("Starting %d HTTPExport collectors", len(config.HTTPExport))
}
for i, hec := range config.HTTPExport {
c, err := httpexport.New(hec)
if err != nil {
log.Fatalf("Error in HTTPExport[%d]: %v", i, err)
}
promm.MustRegister(c)
}
log.Print("Starting Prometheus metrics handler")
http.Handle(config.Prometheus.HandlerPath, promm.Handler())
http.ListenAndServe(config.Prometheus.ServeAddr, nil)
}