Replace client and route counters by per-entry metrics.
This allows us to graph either per-connection or the total bandwidth of the VPN server. Fixes #2
This commit is contained in:
1 parent
5518c1e316
commit
01168ec98f
1 file changed
+99
-27
+99
-27
@@ -29,6 +29,17 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OpenvpnServerHeader struct {
|
||||||
|
LabelColumns []string
|
||||||
|
Metrics []OpenvpnServerHeaderField
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenvpnServerHeaderField struct {
|
||||||
|
Column string
|
||||||
|
Desc *prometheus.Desc
|
||||||
|
ValueType prometheus.ValueType
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Metrics exported both for client and server statistics.
|
// Metrics exported both for client and server statistics.
|
||||||
openvpnUpDesc = prometheus.NewDesc(
|
openvpnUpDesc = prometheus.NewDesc(
|
||||||
@@ -41,14 +52,52 @@ var (
|
|||||||
[]string{"status_path"}, nil)
|
[]string{"status_path"}, nil)
|
||||||
|
|
||||||
// Metrics specific to OpenVPN servers.
|
// Metrics specific to OpenVPN servers.
|
||||||
openvpnServerClientsDesc = prometheus.NewDesc(
|
openvpnServerHeaders = map[string]OpenvpnServerHeader{
|
||||||
prometheus.BuildFQName("openvpn", "server", "clients"),
|
"CLIENT_LIST": OpenvpnServerHeader{
|
||||||
"Number of clients connected to the OpenVPN server.",
|
LabelColumns: []string{
|
||||||
[]string{"status_path"}, nil)
|
"Common Name",
|
||||||
openvpnServerRoutesDesc = prometheus.NewDesc(
|
"Connected Since (time_t)",
|
||||||
prometheus.BuildFQName("openvpn", "server", "routes"),
|
"Real Address",
|
||||||
"Number of routes propagated by the OpenVPN server.",
|
"Username",
|
||||||
[]string{"status_path"}, nil)
|
"Virtual Address",
|
||||||
|
},
|
||||||
|
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"),
|
||||||
|
"Amount of data sent 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ROUTING_TABLE": OpenvpnServerHeader{
|
||||||
|
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"),
|
||||||
|
"Time at which a route was last referenced, in seconds.",
|
||||||
|
[]string{"status_path", "common_name", "real_address", "virtual_address"}, nil),
|
||||||
|
ValueType: prometheus.GaugeValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// Metrics specific to OpenVPN clients.
|
// Metrics specific to OpenVPN clients.
|
||||||
openvpnClientDescs = map[string]*prometheus.Desc{
|
openvpnClientDescs = map[string]*prometheus.Desc{
|
||||||
@@ -118,22 +167,16 @@ func CollectStatusFromReader(statusPath string, file io.Reader, ch chan<- promet
|
|||||||
func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<- prometheus.Metric, separator string) error {
|
func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<- prometheus.Metric, separator string) error {
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
scanner.Split(bufio.ScanLines)
|
scanner.Split(bufio.ScanLines)
|
||||||
clients := 0
|
headersFound := map[string][]string{}
|
||||||
routes := 0
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
fields := strings.Split(scanner.Text(), separator)
|
fields := strings.Split(scanner.Text(), separator)
|
||||||
if fields[0] == "CLIENT_LIST" {
|
if fields[0] == "END" && len(fields) == 1 {
|
||||||
// Per-client stats.
|
|
||||||
clients++
|
|
||||||
} else if fields[0] == "END" && len(fields) == 1 {
|
|
||||||
// Stats footer.
|
// Stats footer.
|
||||||
} else if fields[0] == "GLOBAL_STATS" {
|
} else if fields[0] == "GLOBAL_STATS" {
|
||||||
// Global server statistics.
|
// Global server statistics.
|
||||||
} else if fields[0] == "HEADER" && len(fields) > 2 {
|
} else if fields[0] == "HEADER" && len(fields) > 2 {
|
||||||
// Column names for CLIENT_LIST and ROUTING_TABLE.
|
// Column names for CLIENT_LIST and ROUTING_TABLE.
|
||||||
} else if fields[0] == "ROUTING_TABLE" {
|
headersFound[fields[1]] = fields[2:]
|
||||||
// Per-route stats.
|
|
||||||
routes++
|
|
||||||
} else if fields[0] == "TIME" && len(fields) == 3 {
|
} else if fields[0] == "TIME" && len(fields) == 3 {
|
||||||
// Time at which the statistics were updated.
|
// Time at which the statistics were updated.
|
||||||
time, err := strconv.ParseFloat(fields[2], 64)
|
time, err := strconv.ParseFloat(fields[2], 64)
|
||||||
@@ -147,20 +190,49 @@ func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<-
|
|||||||
statusPath)
|
statusPath)
|
||||||
} else if fields[0] == "TITLE" && len(fields) == 2 {
|
} else if fields[0] == "TITLE" && len(fields) == 2 {
|
||||||
// OpenVPN version number.
|
// OpenVPN version number.
|
||||||
|
} else if header, ok := openvpnServerHeaders[fields[0]]; ok {
|
||||||
|
// Entry that depends on a preceding HEADERS directive.
|
||||||
|
columnNames, ok := headersFound[fields[0]]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s should be preceded by HEADERS", fields[0])
|
||||||
|
}
|
||||||
|
if len(fields) != len(columnNames)+1 {
|
||||||
|
return fmt.Errorf("HEADER for %s describes a different number of columns", fields[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store entry values in a map indexed by column name.
|
||||||
|
columnValues := map[string]string{}
|
||||||
|
for _, column := range header.LabelColumns {
|
||||||
|
columnValues[column] = ""
|
||||||
|
}
|
||||||
|
for i, column := range columnNames {
|
||||||
|
columnValues[column] = fields[i+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract columns that should act as entry labels.
|
||||||
|
labels := []string{statusPath}
|
||||||
|
for _, column := range header.LabelColumns {
|
||||||
|
labels = append(labels, columnValues[column])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export relevant columns as individual metrics.
|
||||||
|
for _, metric := range header.Metrics {
|
||||||
|
if columnValue, ok := columnValues[metric.Column]; ok {
|
||||||
|
value, err := strconv.ParseFloat(columnValue, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
metric.Desc,
|
||||||
|
metric.ValueType,
|
||||||
|
value,
|
||||||
|
labels...)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Unsupported key: %q", fields[0])
|
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()
|
return scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user