Skip to content
This repository has been archived by the owner on Aug 17, 2021. It is now read-only.

Commit

Permalink
add openvpn_connected_clients
Browse files Browse the repository at this point in the history
+ dockerfile + minor go review
  • Loading branch information
Sispheor authored and EdSchouten committed Nov 22, 2017
1 parent 26d010b commit 8221d6d
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 32 deletions.
42 changes: 42 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
51 changes: 49 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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 changes: 0 additions & 17 deletions build_static.sh

This file was deleted.

46 changes: 33 additions & 13 deletions openvpn_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

type OpenvpnServerHeader struct {
Expand All @@ -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)",
Expand All @@ -62,15 +67,15 @@ var (
"Username",
},
Metrics: []OpenvpnServerHeaderField{
OpenvpnServerHeaderField{
{
Column: "Bytes Received",
Desc: prometheus.NewDesc(
prometheus.BuildFQName("openvpn", "server", "client_received_bytes_total"),
"Amount of data received over a connection on the VPN server, in bytes.",
[]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"),
Expand All @@ -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"),
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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 {
Expand All @@ -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]]
Expand Down Expand Up @@ -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()
}

Expand All @@ -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.
Expand All @@ -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()
Expand Down Expand Up @@ -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>
Expand Down

0 comments on commit 8221d6d

Please sign in to comment.