Check in an initial version of the OpenVPN metrics exporter.
This commit is contained in:
commit
fe73b8c8bc
4 files changed
+318
No files matched your search
@@ -0,0 +1,12 @@
|
||||
OpenVPN STATISTICS
|
||||
Updated,Tue Mar 21 10:39:09 2017
|
||||
TUN/TAP read bytes,153789941
|
||||
TUN/TAP write bytes,308764078
|
||||
TCP/UDP read bytes,292806201
|
||||
TCP/UDP write bytes,197558969
|
||||
Auth read bytes,308854782
|
||||
pre-compress bytes,45388190
|
||||
post-compress bytes,45446864
|
||||
pre-decompress bytes,162596168
|
||||
post-decompress bytes,216965355
|
||||
END
|
||||
@@ -0,0 +1,16 @@
|
||||
TITLE,OpenVPN 2.3.2 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [eurephia] [MH] [IPv6] built on Dec 2 2014
|
||||
TIME,Tue Mar 21 10:39:14 2017,1490089154
|
||||
HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username
|
||||
CLIENT_LIST,redacted1,0.0.0.0:19021,0.0.0.0,693438277,228390856,Thu Mar 16 17:09:03 2017,1489680543,UNDEF
|
||||
CLIENT_LIST,redacted2,0.0.0.0:60536,0.0.0.0,2925752,3145665,Thu Mar 16 17:08:57 2017,1489680537,UNDEF
|
||||
CLIENT_LIST,redacted3,0.0.0.0:28331,0.0.0.0,57316467,611736741,Thu Mar 16 17:08:57 2017,1489680537,UNDEF
|
||||
CLIENT_LIST,redacted4,0.0.0.0:52335,0.0.0.0,24289622392,70914674697,Fri Mar 17 11:16:29 2017,1489745789,UNDEF
|
||||
CLIENT_LIST,redacted5,0.0.0.0:51865,0.0.0.0,277017840,1544465106,Thu Mar 16 17:09:01 2017,1489680541,UNDEF
|
||||
HEADER,ROUTING_TABLE,Virtual Address,Common Name,Real Address,Last Ref,Last Ref (time_t)
|
||||
ROUTING_TABLE,0.0.0.0,redacted1,0.0.0.0:19021,Tue Mar 21 10:26:48 2017,1490088408
|
||||
ROUTING_TABLE,0.0.0.0,redacted5,0.0.0.0:51865,Tue Mar 21 10:38:26 2017,1490089106
|
||||
ROUTING_TABLE,0.0.0.0,redacted3,0.0.0.0:28331,Tue Mar 21 10:39:06 2017,1490089146
|
||||
ROUTING_TABLE,0.0.0.0,redacted4,0.0.0.0:52335,Tue Mar 21 10:39:13 2017,1490089153
|
||||
ROUTING_TABLE,0.0.0.0,redacted2,0.0.0.0:60536,Thu Mar 16 17:08:58 2017,1489680538
|
||||
GLOBAL_STATS,Max bcast/mcast queue length,0
|
||||
END
|
||||
@@ -0,0 +1,16 @@
|
||||
TITLE OpenVPN 2.3.2 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [eurephia] [MH] [IPv6] built on Dec 2 2014
|
||||
TIME Tue Mar 21 10:39:14 2017 1490089154
|
||||
HEADER CLIENT_LIST Common Name Real Address Virtual Address Bytes Received Bytes Sent Connected Since Connected Since (time_t) Username
|
||||
CLIENT_LIST redacted1 0.0.0.0:19021 0.0.0.0 693438277 228390856 Thu Mar 16 17:09:03 2017 1489680543 UNDEF
|
||||
CLIENT_LIST redacted2 0.0.0.0:60536 0.0.0.0 2925752 3145665 Thu Mar 16 17:08:57 2017 1489680537 UNDEF
|
||||
CLIENT_LIST redacted3 0.0.0.0:28331 0.0.0.0 57316467 611736741 Thu Mar 16 17:08:57 2017 1489680537 UNDEF
|
||||
CLIENT_LIST redacted4 0.0.0.0:52335 0.0.0.0 24289622392 70914674697 Fri Mar 17 11:16:29 2017 1489745789 UNDEF
|
||||
CLIENT_LIST redacted5 0.0.0.0:51865 0.0.0.0 277017840 1544465106 Thu Mar 16 17:09:01 2017 1489680541 UNDEF
|
||||
HEADER ROUTING_TABLE Virtual Address Common Name Real Address Last Ref Last Ref (time_t)
|
||||
ROUTING_TABLE 0.0.0.0 redacted1 0.0.0.0:19021 Tue Mar 21 10:26:48 2017 1490088408
|
||||
ROUTING_TABLE 0.0.0.0 redacted5 0.0.0.0:51865 Tue Mar 21 10:38:26 2017 1490089106
|
||||
ROUTING_TABLE 0.0.0.0 redacted3 0.0.0.0:28331 Tue Mar 21 10:39:06 2017 1490089146
|
||||
ROUTING_TABLE 0.0.0.0 redacted4 0.0.0.0:52335 Tue Mar 21 10:39:13 2017 1490089153
|
||||
ROUTING_TABLE 0.0.0.0 redacted2 0.0.0.0:60536 Thu Mar 16 17:08:58 2017 1489680538
|
||||
GLOBAL_STATS Max bcast/mcast queue length 0
|
||||
END
|
||||
@@ -0,0 +1,274 @@
|
||||
// Copyright 2017 Kumina, https://kumina.nl/
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
// Metrics exported both for client and server statistics.
|
||||
openvpnUpDesc = prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "", "up"),
|
||||
"Whether scraping OpenVPN's metrics was successful.",
|
||||
[]string{"status_path"}, nil)
|
||||
openvpnStatusUpdateTimeDesc = prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "", "status_update_time_seconds"),
|
||||
"UNIX timestamp at which the OpenVPN statistics were updated.",
|
||||
[]string{"status_path"}, nil)
|
||||
|
||||
// Metrics specific to OpenVPN servers.
|
||||
openvpnServerClientsDesc = prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "server", "clients"),
|
||||
"Number of clients connected to the OpenVPN server.",
|
||||
[]string{"status_path"}, nil)
|
||||
openvpnServerRoutesDesc = prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "server", "routes"),
|
||||
"Number of routes propagated by the OpenVPN server.",
|
||||
[]string{"status_path"}, nil)
|
||||
|
||||
// Metrics specific to OpenVPN clients.
|
||||
openvpnClientDescs = map[string]*prometheus.Desc{
|
||||
"TUN/TAP read bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "client", "tun_tap_read_bytes_total"),
|
||||
"Total amount of TUN/TAP traffic read, in bytes.",
|
||||
[]string{"status_path"}, nil),
|
||||
"TUN/TAP write bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "client", "tun_tap_write_bytes_total"),
|
||||
"Total amount of TUN/TAP traffic written, in bytes.",
|
||||
[]string{"status_path"}, nil),
|
||||
"TCP/UDP read bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "client", "tcp_udp_read_bytes_total"),
|
||||
"Total amount of TCP/UDP traffic read, in bytes.",
|
||||
[]string{"status_path"}, nil),
|
||||
"TCP/UDP write bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "client", "tcp_udp_write_bytes_total"),
|
||||
"Total amount of TCP/UDP traffic written, in bytes.",
|
||||
[]string{"status_path"}, nil),
|
||||
"Auth read bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "client", "auth_read_bytes_total"),
|
||||
"Total amount of authentication traffic read, in bytes.",
|
||||
[]string{"status_path"}, nil),
|
||||
"pre-compress bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "client", "pre_compress_bytes_total"),
|
||||
"Total amount of data before compression, in bytes.",
|
||||
[]string{"status_path"}, nil),
|
||||
"post-compress bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "client", "post_compress_bytes_total"),
|
||||
"Total amount of data after compression, in bytes.",
|
||||
[]string{"status_path"}, nil),
|
||||
"pre-decompress bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "client", "pre_decompress_bytes_total"),
|
||||
"Total amount of data before decompression, in bytes.",
|
||||
[]string{"status_path"}, nil),
|
||||
"post-decompress bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "client", "post_decompress_bytes_total"),
|
||||
"Total amount of data after decompression, in bytes.",
|
||||
[]string{"status_path"}, nil),
|
||||
}
|
||||
)
|
||||
|
||||
// Converts OpenVPN status information into Prometheus metrics. This
|
||||
// function automatically detects whether the file contains server or
|
||||
// client metrics. For server metrics, it also distinguishes between the
|
||||
// version 2 and 3 file formats.
|
||||
func CollectStatusFromReader(statusPath string, file io.Reader, ch chan<- prometheus.Metric) error {
|
||||
reader := bufio.NewReader(file)
|
||||
buf, _ := reader.Peek(18)
|
||||
if bytes.HasPrefix(buf, []byte("TITLE,")) {
|
||||
// Server statistics, using format version 2.
|
||||
return CollectServerStatusFromReader(statusPath, reader, ch, ",")
|
||||
} else if bytes.HasPrefix(buf, []byte("TITLE\t")) {
|
||||
// Server statistics, using format version 3. The only
|
||||
// difference compared to version 2 is that it uses tabs
|
||||
// instead of spaces.
|
||||
return CollectServerStatusFromReader(statusPath, reader, ch, "\t")
|
||||
} else if bytes.HasPrefix(buf, []byte("OpenVPN STATISTICS")) {
|
||||
// Client statistics.
|
||||
return CollectClientStatusFromReader(statusPath, reader, ch)
|
||||
} else {
|
||||
return fmt.Errorf("Unexpected file contents: %q", buf)
|
||||
}
|
||||
}
|
||||
|
||||
// Converts OpenVPN server status information into Prometheus metrics.
|
||||
func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<- prometheus.Metric, separator string) error {
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
clients := 0
|
||||
routes := 0
|
||||
for scanner.Scan() {
|
||||
fields := strings.Split(scanner.Text(), separator)
|
||||
if fields[0] == "CLIENT_LIST" {
|
||||
// Per-client stats.
|
||||
clients++
|
||||
} else if fields[0] == "END" && len(fields) == 1 {
|
||||
// Stats footer.
|
||||
} else if fields[0] == "GLOBAL_STATS" {
|
||||
// Global server statistics.
|
||||
} else if fields[0] == "HEADER" && len(fields) > 2 {
|
||||
// Column names for CLIENT_LIST and ROUTING_TABLE.
|
||||
} else if fields[0] == "ROUTING_TABLE" {
|
||||
// Per-route stats.
|
||||
routes++
|
||||
} else if fields[0] == "TIME" && len(fields) == 3 {
|
||||
// Time at which the statistics were updated.
|
||||
time, err := strconv.ParseFloat(fields[2], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
openvpnStatusUpdateTimeDesc,
|
||||
prometheus.GaugeValue,
|
||||
time,
|
||||
statusPath)
|
||||
} else if fields[0] == "TITLE" && len(fields) == 2 {
|
||||
// OpenVPN version number.
|
||||
} else {
|
||||
return fmt.Errorf("Unsupported key: %q", fields[0])
|
||||
}
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
openvpnServerClientsDesc,
|
||||
prometheus.GaugeValue,
|
||||
float64(clients),
|
||||
statusPath)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
openvpnServerRoutesDesc,
|
||||
prometheus.GaugeValue,
|
||||
float64(routes),
|
||||
statusPath)
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
// Converts OpenVPN client status information into Prometheus metrics.
|
||||
func CollectClientStatusFromReader(statusPath string, file io.Reader, ch chan<- prometheus.Metric) error {
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Split(scanner.Text(), ",")
|
||||
if fields[0] == "END" && len(fields) == 1 {
|
||||
// Stats footer.
|
||||
} else if fields[0] == "OpenVPN STATISTICS" && len(fields) == 1 {
|
||||
// Stats header.
|
||||
} else if fields[0] == "Updated" && len(fields) == 2 {
|
||||
// Time at which the statistics were updated.
|
||||
time, err := time.Parse("Mon Jan 2 15:04:05 2006", fields[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
openvpnStatusUpdateTimeDesc,
|
||||
prometheus.GaugeValue,
|
||||
float64(time.Unix()),
|
||||
statusPath)
|
||||
} else if desc, ok := openvpnClientDescs[fields[0]]; ok && len(fields) == 2 {
|
||||
// Traffic counters.
|
||||
value, err := strconv.ParseFloat(fields[1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.CounterValue,
|
||||
value,
|
||||
statusPath)
|
||||
} else {
|
||||
return fmt.Errorf("Unsupported key: %q", fields[0])
|
||||
}
|
||||
}
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func CollectStatusFromFile(statusPath string, ch chan<- prometheus.Metric) error {
|
||||
conn, err := os.Open(statusPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CollectStatusFromReader(statusPath, conn, ch)
|
||||
}
|
||||
|
||||
type OpenVPNExporter struct {
|
||||
statusPaths []string
|
||||
}
|
||||
|
||||
func NewOpenVPNExporter(statusPaths []string) (*OpenVPNExporter, error) {
|
||||
return &OpenVPNExporter{
|
||||
statusPaths: statusPaths,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *OpenVPNExporter) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- openvpnUpDesc
|
||||
}
|
||||
|
||||
func (e *OpenVPNExporter) Collect(ch chan<- prometheus.Metric) {
|
||||
for _, statusPath := range e.statusPaths {
|
||||
err := CollectStatusFromFile(statusPath, ch)
|
||||
if err == nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
openvpnUpDesc,
|
||||
prometheus.GaugeValue,
|
||||
1.0,
|
||||
statusPath)
|
||||
} else {
|
||||
log.Printf("Failed to scrape showq socket: %s", err)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
openvpnUpDesc,
|
||||
prometheus.GaugeValue,
|
||||
0.0,
|
||||
statusPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
listenAddress = flag.String("web.listen-address", ":9154", "Address to listen on for web interface and telemetry.")
|
||||
metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
|
||||
openvpnStatusPaths = flag.String("openvpn.status_paths", "examples/client.status,examples/server2.status,examples/server3.status", "Paths at which OpenVPN places its status files.")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
exporter, err := NewOpenVPNExporter(strings.Split(*openvpnStatusPaths, ","))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
prometheus.MustRegister(exporter)
|
||||
|
||||
http.Handle(*metricsPath, prometheus.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`
|
||||
<html>
|
||||
<head><title>OpenVPN Exporter</title></head>
|
||||
<body>
|
||||
<h1>OpenVPN Exporter</h1>
|
||||
<p><a href='` + *metricsPath + `'>Metrics</a></p>
|
||||
</body>
|
||||
</html>`))
|
||||
})
|
||||
log.Fatal(http.ListenAndServe(*listenAddress, nil))
|
||||
}
|
||||
Reference in new issue
Block a user