add openvpn_connected_clients

+ dockerfile + minor go review
This commit is contained in:
nico authored and Ed Schouten committed 2017-11-22 09:38:26 +01:00
1 parent 26d010b2f0
commit 8221d6daf9
4 files changed
+124 -32

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
# BUILD:
# docker build --force-rm=true -t openvpn_exporter .
# RUN:
# docker run -it -p 9176:9176 -v /path/to/openvpn_server.status:/etc/openvpn_exporter/server.status openvpn_exporter
FROM golang as builder
RUN mkdir /app
RUN mkdir /go/src/app
ADD . /go/src/app
WORKDIR /go/src/app
# Go dep
RUN go get -d ./...
# Build a standalone binary
RUN set -ex && \
CGO_ENABLED=0 go build \
-tags netgo \
-o /app/openvpn_exporter \
-v -a \
-ldflags '-extldflags "-static"' && \
ls
# Create the second stage with a basic image.
# this will drop any previous
# stages (defined as `FROM <some_image> as <some_name>`)
# allowing us to start with a fat build image and end up with
# a very small runtime image.
FROM busybox
# add compiled binary
COPY --from=builder /app/openvpn_exporter /openvpn_exporter
# add a default file to be processed
ADD examples/server2.status /etc/openvpn_exporter/server.status
# run
EXPOSE 9176
CMD ["/openvpn_exporter", "-openvpn.status_paths", "/etc/openvpn_exporter/server.status"]
+49 -2
View File
@@ -18,7 +18,9 @@ exported over TCP port 9176.
Please refer to this utility's `main()` function for a full list of
supported command line flags.
## Client statistics
## Exposed metrics example
### Client statistics
For clients status files, the exporter generates metrics that may look
like this:
@@ -37,7 +39,7 @@ openvpn_status_update_time_seconds{status_path="..."} 1.490092749e+09
openvpn_up{status_path="..."} 1
```
## Server statistics
### Server statistics
For server status files (both version 2 and 3), the exporter generates
metrics that may look like this:
@@ -48,4 +50,49 @@ openvpn_server_client_sent_bytes_total{common_name="...",connection_time="...",r
openvpn_server_route_last_reference_time_seconds{common_name="...",real_address="...",status_path="...",virtual_address="..."} 1.493018841e+09
openvpn_status_update_time_seconds{status_path="..."} 1.490089154e+09
openvpn_up{status_path="..."} 1
openvpn_server_connected_clients 1
```
## Usage
Usage of openvpn_exporter:
```
-openvpn.status_paths string
Paths at which OpenVPN places its status files. (default "examples/client.status,examples/server2.status,examples/server3.status")
-web.listen-address string
Address to listen on for web interface and telemetry. (default ":9176")
-web.telemetry-path string
Path under which to expose metrics. (default "/metrics")
```
E.g:
```
openvpn_exporter -openvpn.status_paths /etc/openvpn/openvpn-status.log
```
## Docker
Build the image:
```
docker build --force-rm=true -t openvpn_exporter .
```
The final image is around 8MB. A temporary image has been downloaded(Golang) to make the final one. Once built, this temporary image become orphan, you can delete it:
```
docker rmi -f $(docker images | grep "<none>" | awk "{print \$3}")
```
To use with docker you must mount your status file to `/etc/openvpn_exporter/server.status`.
```
docker run -it -p 9176:9176 -v /path/to/openvpn_server.status:/etc/openvpn_exporter/server.status openvpn_exporter
```
Metrics should be available on your host IP: http://<host_ip>:9176/metrics. E.g: http://10.39.9.94:9176/metrics
## Get a standalone executable binary
Use the docker image to copy the built binary into a mounted volume
```bash
docker run -it -v /local/empty/folder:/volume openvpn_exporter cp openvpn_exporter /volume
```
-17
View File
@@ -1,17 +0,0 @@
#!/bin/sh
docker run -i -v `pwd`:/openvpn_exporter alpine:edge /bin/sh << 'EOF'
set -ex
# Install prerequisites for the build process.
apk update
apk add ca-certificates git go libc-dev
update-ca-certificates
# Build the openvpn_exporter.
cd /openvpn_exporter
export GOPATH=/gopath
go get -d ./...
go build --ldflags '-extldflags "-static"'
strip openvpn_exporter
EOF
+33 -13
View File
@@ -27,6 +27,7 @@ import (
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type OpenvpnServerHeader struct {
@@ -52,8 +53,12 @@ var (
[]string{"status_path"}, nil)
// Metrics specific to OpenVPN servers.
openvpnConnectedClientsDesc = prometheus.NewDesc(
prometheus.BuildFQName("openvpn", "", "openvpn_server_connected_clients"),
"Number Of Connected Clients", nil, nil)
openvpnServerHeaders = map[string]OpenvpnServerHeader{
"CLIENT_LIST": OpenvpnServerHeader{
"CLIENT_LIST": {
LabelColumns: []string{
"Common Name",
"Connected Since (time_t)",
@@ -62,7 +67,7 @@ var (
"Username",
},
Metrics: []OpenvpnServerHeaderField{
OpenvpnServerHeaderField{
{
Column: "Bytes Received",
Desc: prometheus.NewDesc(
prometheus.BuildFQName("openvpn", "server", "client_received_bytes_total"),
@@ -70,7 +75,7 @@ var (
[]string{"status_path", "common_name", "connection_time", "real_address", "virtual_address", "username"}, nil),
ValueType: prometheus.CounterValue,
},
OpenvpnServerHeaderField{
{
Column: "Bytes Sent",
Desc: prometheus.NewDesc(
prometheus.BuildFQName("openvpn", "server", "client_sent_bytes_total"),
@@ -80,14 +85,14 @@ var (
},
},
},
"ROUTING_TABLE": OpenvpnServerHeader{
"ROUTING_TABLE": {
LabelColumns: []string{
"Common Name",
"Real Address",
"Virtual Address",
},
Metrics: []OpenvpnServerHeaderField{
OpenvpnServerHeaderField{
{
Column: "Last Ref (time_t)",
Desc: prometheus.NewDesc(
prometheus.BuildFQName("openvpn", "server", "route_last_reference_time_seconds"),
@@ -159,7 +164,7 @@ func CollectStatusFromReader(statusPath string, file io.Reader, ch chan<- promet
// Client statistics.
return CollectClientStatusFromReader(statusPath, reader, ch)
} else {
return fmt.Errorf("Unexpected file contents: %q", buf)
return fmt.Errorf("unexpected file contents: %q", buf)
}
}
@@ -168,6 +173,9 @@ func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<-
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
headersFound := map[string][]string{}
// counter of connected client
numberConnectedClient := 0
for scanner.Scan() {
fields := strings.Split(scanner.Text(), separator)
if fields[0] == "END" && len(fields) == 1 {
@@ -179,17 +187,19 @@ func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<-
headersFound[fields[1]] = fields[2:]
} else if fields[0] == "TIME" && len(fields) == 3 {
// Time at which the statistics were updated.
time, err := strconv.ParseFloat(fields[2], 64)
timeStartStats, err := strconv.ParseFloat(fields[2], 64)
if err != nil {
return err
}
ch <- prometheus.MustNewConstMetric(
openvpnStatusUpdateTimeDesc,
prometheus.GaugeValue,
time,
timeStartStats,
statusPath)
} else if fields[0] == "TITLE" && len(fields) == 2 {
// OpenVPN version number.
} else if fields[0] == "CLIENT_LIST"{
numberConnectedClient ++
} else if header, ok := openvpnServerHeaders[fields[0]]; ok {
// Entry that depends on a preceding HEADERS directive.
columnNames, ok := headersFound[fields[0]]
@@ -230,9 +240,14 @@ func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<-
}
}
} else {
return fmt.Errorf("Unsupported key: %q", fields[0])
return fmt.Errorf("unsupported key: %q", fields[0])
}
}
// add the number of connected client
ch <- prometheus.MustNewConstMetric(
openvpnConnectedClientsDesc,
prometheus.GaugeValue,
float64(numberConnectedClient))
return scanner.Err()
}
@@ -249,14 +264,14 @@ func CollectClientStatusFromReader(statusPath string, file io.Reader, ch chan<-
} else if fields[0] == "Updated" && len(fields) == 2 {
// Time at which the statistics were updated.
location, _ := time.LoadLocation("Local")
time, err := time.ParseInLocation("Mon Jan 2 15:04:05 2006", fields[1], location)
timeParser, err := time.ParseInLocation("Mon Jan 2 15:04:05 2006", fields[1], location)
if err != nil {
return err
}
ch <- prometheus.MustNewConstMetric(
openvpnStatusUpdateTimeDesc,
prometheus.GaugeValue,
float64(time.Unix()),
float64(timeParser.Unix()),
statusPath)
} else if desc, ok := openvpnClientDescs[fields[0]]; ok && len(fields) == 2 {
// Traffic counters.
@@ -270,7 +285,7 @@ func CollectClientStatusFromReader(statusPath string, file io.Reader, ch chan<-
value,
statusPath)
} else {
return fmt.Errorf("Unsupported key: %q", fields[0])
return fmt.Errorf("unsupported key: %q", fields[0])
}
}
return scanner.Err()
@@ -327,13 +342,18 @@ func main() {
)
flag.Parse()
log.Printf("Starting OpenVPN Exporter\n")
log.Printf("Listen address: %v\n", *listenAddress)
log.Printf("Metrics path: %v\n", *metricsPath)
log.Printf("openvpn.status_path: %v\n", *openvpnStatusPaths)
exporter, err := NewOpenVPNExporter(strings.Split(*openvpnStatusPaths, ","))
if err != nil {
panic(err)
}
prometheus.MustRegister(exporter)
http.Handle(*metricsPath, prometheus.Handler())
http.Handle(*metricsPath, promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`
<html>