Signed-off-by: Vasiliy Tolstov <v.tolstov@sdstack.com>
This commit is contained in:
Vasiliy Tolstov 2018-10-03 09:41:20 +03:00
commit 8fc004ab36
5 changed files with 617 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/libvirt_exporter

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2018 sdstack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# Prometheus libvirt exporter
Supported metrics
```
libvirt_domain_block_stats_read_bytes_total{domain="...",source_file="...",target_device="..."}
libvirt_domain_block_stats_read_requests_total{domain="...",source_file="...",target_device="..."}
libvirt_domain_block_stats_write_bytes_total{domain="...",source_file="...",target_device="..."}
libvirt_domain_block_stats_write_requests_total{domain="...",source_file="...",target_device="..."}
libvirt_domain_info_cpu_time_seconds_total{domain="..."}
libvirt_domain_info_maximum_memory_bytes{domain="..."}
libvirt_domain_info_memory_usage_bytes{domain="..."}
libvirt_domain_info_virtual_cpus{domain="..."}
libvirt_domain_interface_stats_receive_bytes_total{domain="...",source_bridge="...",target_device="..."}
libvirt_domain_interface_stats_receive_drops_total{domain="...",source_bridge="...",target_device="..."}
libvirt_domain_interface_stats_receive_errors_total{domain="...",source_bridge="...",target_device="..."}
libvirt_domain_interface_stats_receive_packets_total{domain="...",source_bridge="...",target_device="..."}
libvirt_domain_interface_stats_transmit_bytes_total{domain="...",source_bridge="...",target_device="..."}
libvirt_domain_interface_stats_transmit_drops_total{domain="...",source_bridge="...",target_device="..."}
libvirt_domain_interface_stats_transmit_errors_total{domain="...",source_bridge="...",target_device="..."}
libvirt_domain_interface_stats_transmit_packets_total{domain="...",source_bridge="...",target_device="..."}
libvirt_up
```

534
common.go Normal file
View File

@ -0,0 +1,534 @@
package promlibvirt
import (
"flag"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"sdstack.com/sdstack/compute"
"github.com/prometheus/client_golang/prometheus"
libvirt_plain "github.com/sdstack/go-libvirt-plain"
libvirt_dbus "github.com/sdstack/go-libvirt-dbus"
)
var (
// exporter metrics
libvirtUpDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "", "up"),
"Whether scraping libvirt's metrics was successful.",
nil,
nil)
// memory metrics
libvirtDomainMemoryMaximumDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_memory", "maximum_bytes"),
"Maximum allowed memory of the domain, in bytes.",
[]string{"domain"},
nil)
libvirtDomainMemoryCurrentDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_memory", "current_bytes"),
"Memory usage of the domain, in bytes.",
[]string{"domain"},
nil)
libvirtDomainMemoryResidentDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_memory", "resident_bytes"),
"Memory usage of the domain, in bytes.",
[]string{"domain"},
nil)
libvirtDomainMemoryLastUpdateDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_memory", "last_update_stats"),
"Last update of memory stats, in seconds.",
[]string{"domain"},
nil)
// cpu metrics
libvirtDomainCpuTimeDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_cpu", "time_seconds_total"),
"Amount of CPU time used by the domain, in seconds.",
[]string{"domain"},
nil)
libvirtDomainCpuSystemDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_cpu", "system_seconds_total"),
"Amount of CPU time used by the domain, in seconds.",
[]string{"domain"},
nil)
libvirtDomainCpuUserDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_cpu", "user_seconds_total"),
"Amount of CPU time used by the domain, in seconds.",
[]string{"domain"},
nil)
// vcpu metrics
libvirtDomainVcpuMaximumDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_vcpu", "maximum"),
"Number of maximum virtual CPUs for the domain.",
[]string{"domain"},
nil)
libvirtDomainVcpuCurrentDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_vcpu", "current"),
"Number of current virtual CPUs for the domain.",
[]string{"domain"},
nil)
// block metrics
libvirtDomainBlockAllocBytesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "alloc_bytes_total"),
"Number of bytes allocated for device, in bytes.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockCapBytesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "cap_bytes_total"),
"Number of bytes capacity for device, in bytes.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockPhysBytesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "phy_bytes_total"),
"Number of bytes physical for device, in bytes.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockRdBytesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "read_bytes_total"),
"Number of bytes read from device, in bytes.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockRdReqsDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "read_requests_total"),
"Number of read requests from device.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockRdTimesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "read_seconds_total"),
"Amount of time spent reading from a block device, in seconds.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockWrBytesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "write_bytes_total"),
"Number of bytes written from a block device, in bytes.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockWrReqsDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "write_requests_total"),
"Number of write requests from a block device.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockWrTimesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "write_seconds_total"),
"Amount of time spent writing from a block device, in seconds.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockFlReqsDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "flush_requests_total"),
"Number of flush requests from a block device.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainBlockFlTimesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_block", "flush_seconds_total"),
"Amount of time spent flushing of a block device, in seconds.",
[]string{"domain", "source", "target"},
nil)
// network metrcis
libvirtDomainNetRxBytesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_net", "receive_bytes_total"),
"Number of bytes received on a network interface, in bytes.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainNetRxPktsDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_net", "receive_packets_total"),
"Number of packets received on a network interface.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainNetRxErrsDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_net", "receive_errors_total"),
"Number of packet receive errors on a network interface.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainNetRxDropDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_net", "receive_drops_total"),
"Number of packet receive drops on a network interface.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainNetTxBytesDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_net", "transmit_bytes_total"),
"Number of bytes transmitted on a network interface, in bytes.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainNetTxPktsDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_net", "transmit_packets_total"),
"Number of packets transmitted on a network interface.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainNetTxErrsDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_net", "transmit_errors_total"),
"Number of packet transmit errors on a network interface.",
[]string{"domain", "source", "target"},
nil)
libvirtDomainNetTxDropDesc = prometheus.NewDesc(
prometheus.BuildFQName("libvirt", "domain_net", "transmit_drops_total"),
"Number of packet transmit drops on a network interface.",
[]string{"domain", "source", "target"},
nil)
)
// CollectDomain extracts Prometheus metrics from a libvirt domain.
func CollectStats(ch chan<- prometheus.Metric, stats map[string]map[string]interface{}) error {
for dname, items := range stats {
for ikey, ival := range items {
idx := strings.Index(ikey, ".")
idxl := strings.LastIndex(ikey, ".")
key := ikey[:idx]
switch key {
default:
fmt.Printf("zz %s %s %s\n", dname, ikey, ival)
/*
case "block":
case "net":
case "cpu":
case "block":
ch <- prometheus.MustNewConstMetric(
libvirtDomainVcpuCurrentDesc,
prometheus.GaugeValue,
float64(ival.(uint32)),
dname, items[ikey[:idxl]+".path", items[ikey[:idxl]+".name")
*/
case "vcpu":
switch ikey[idx+1:] {
default:
fmt.Printf("xx %s %s %s\n", dname, ikey, ival)
case "maximum":
ch <- prometheus.MustNewConstMetric(
libvirtDomainVcpuMaximumDesc,
prometheus.GaugeValue,
float64(ival.(uint32)),
dname)
case "current":
ch <- prometheus.MustNewConstMetric(
libvirtDomainVcpuCurrentDesc,
prometheus.GaugeValue,
float64(ival.(uint32)),
dname)
}
case "cpu":
switch ikey[idx+1:] {
case "user":
ch <- prometheus.MustNewConstMetric(
libvirtDomainCpuUserDesc,
prometheus.CounterValue,
float64(ival.(uint64))/1e9,
dname)
case "system":
ch <- prometheus.MustNewConstMetric(
libvirtDomainCpuSystemDesc,
prometheus.CounterValue,
float64(ival.(uint64))/1e9,
dname)
case "time":
ch <- prometheus.MustNewConstMetric(
libvirtDomainCpuTimeDesc,
prometheus.CounterValue,
float64(ival.(uint64))/1e9,
dname)
}
case "balloon":
switch ikey[idx+1:] {
default:
panic(fmt.Sprintf("xxx %s %s %s\n", dname, ikey, ival))
case "last-update":
ch <- prometheus.MustNewConstMetric(
libvirtDomainMemoryLastUpdateDesc,
prometheus.GaugeValue,
float64(ival.(uint64)),
dname)
case "maximum":
ch <- prometheus.MustNewConstMetric(
libvirtDomainMemoryMaximumDesc,
prometheus.GaugeValue,
float64(ival.(uint64))*1024,
dname)
case "current":
ch <- prometheus.MustNewConstMetric(
libvirtDomainMemoryCurrentDesc,
prometheus.GaugeValue,
float64(ival.(uint64))*1024,
dname)
case "rss":
ch <- prometheus.MustNewConstMetric(
libvirtDomainMemoryResidentDesc,
prometheus.GaugeValue,
float64(ival.(uint64))*1024,
dname)
}
}
/*
if blockStats.RdBytesSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainBlockRdBytesDesc,
prometheus.CounterValue,
float64(blockStats.RdBytes),
domainName,
disk.Source.File.File,
disk.Target.Dev)
}
if blockStats.RdReqSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainBlockRdReqDesc,
prometheus.CounterValue,
float64(blockStats.RdReq),
domainName,
disk.Source.File.File,
disk.Target.Dev)
}
if blockStats.RdTotalTimesSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainBlockRdTotalTimesDesc,
prometheus.CounterValue,
float64(blockStats.RdTotalTimes)/1e9,
domainName,
disk.Source.File.File,
disk.Target.Dev)
}
if blockStats.WrBytesSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainBlockWrBytesDesc,
prometheus.CounterValue,
float64(blockStats.WrBytes),
domainName,
disk.Source.File.File,
disk.Target.Dev)
}
if blockStats.WrReqSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainBlockWrReqDesc,
prometheus.CounterValue,
float64(blockStats.WrReq),
domainName,
disk.Source.File.File,
disk.Target.Dev)
}
if blockStats.WrTotalTimesSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainBlockWrTotalTimesDesc,
prometheus.CounterValue,
float64(blockStats.WrTotalTimes)/1e9,
domainName,
disk.Source.File.File,
disk.Target.Dev)
}
if blockStats.FlushReqSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainBlockFlushReqDesc,
prometheus.CounterValue,
float64(blockStats.FlushReq),
domainName,
disk.Source.File.File,
disk.Target.Dev)
}
if blockStats.FlushTotalTimesSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainBlockFlushTotalTimesDesc,
prometheus.CounterValue,
float64(blockStats.FlushTotalTimes)/1e9,
domainName,
disk.Source.File.File,
disk.Target.Dev)
}
if interfaceStats.RxBytesSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainInterfaceRxBytesDesc,
prometheus.CounterValue,
float64(interfaceStats.RxBytes),
domainName,
iface.Source.Bridge,
iface.Target.Dev)
}
if interfaceStats.RxPacketsSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainInterfaceRxPacketsDesc,
prometheus.CounterValue,
float64(interfaceStats.RxPackets),
domainName,
iface.Source.Bridge,
iface.Target.Dev)
}
if interfaceStats.RxErrsSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainInterfaceRxErrsDesc,
prometheus.CounterValue,
float64(interfaceStats.RxErrs),
domainName,
iface.Source.Bridge,
iface.Target.Dev)
}
if interfaceStats.RxDropSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainInterfaceRxDropDesc,
prometheus.CounterValue,
float64(interfaceStats.RxDrop),
domainName,
iface.Source.Bridge,
iface.Target.Dev)
}
if interfaceStats.TxBytesSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainInterfaceTxBytesDesc,
prometheus.CounterValue,
float64(interfaceStats.TxBytes),
domainName,
iface.Source.Bridge,
iface.Target.Device)
}
if interfaceStats.TxPacketsSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainInterfaceTxPacketsDesc,
prometheus.CounterValue,
float64(interfaceStats.TxPackets),
domainName,
iface.Source.Bridge,
iface.Target.Device)
}
if interfaceStats.TxErrsSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainInterfaceTxErrsDesc,
prometheus.CounterValue,
float64(interfaceStats.TxErrs),
domainName,
iface.Source.Bridge,
iface.Target.Device)
}
if interfaceStats.TxDropSet {
ch <- prometheus.MustNewConstMetric(
libvirtDomainInterfaceTxDropDesc,
prometheus.CounterValue,
float64(interfaceStats.TxDrop),
domainName,
iface.Source.Bridge,
iface.Target.Device)
}
*/
}
}
return nil
}
// CollectFromLibvirt obtains Prometheus metrics from all domains in a
// libvirt setup.
func CollectFromLibvirt(ch chan<- prometheus.Metric, uri string) error {
var err error
var hyper string
var driver string
var proto string
var stats map[string]map[string]interface{}
u, err := url.Parse(uri)
if err != nil {
return err
}
// qemu+tcp native
// plain+qemu+tcp
fields := strings.Fields(u.Scheme, "+")
switch len(fields) {
case 3:
driver = fields[0]
hyper = fields[1]
proto = fields[2]
case 2:
driver = "auto"
hyper = fields[0]
proto = fields[1]
default:
driver = "auto"
hyper =
}
switch u.Scheme {
default:
return fmt.Errorf("invalid driver: %s", u.Scheme)
case "dbus":
if idx > 0 {
}
lv := libvirt_dbus.NewConn(DriverQEMU)
err := lv.Connect("")
if err != nil {
return err
}
stats, err = lv.ConnectGetAllDomainStats(0, 0) //536870912
if err != nil {
return err
}
}
return CollectStats(ch, stats)
}
// LibvirtExporter implements a Prometheus exporter for libvirt state.
type LibvirtExporter struct {
uri net.URL
}
// Describe returns metadata for all Prometheus metrics that may be exported.
func (e *LibvirtExporter) Describe(ch chan<- *prometheus.Desc) {
ch <- libvirtUpDesc
ch <- libvirtDomainMemoryMaximumDesc
ch <- libvirtDomainMemoryCurrentDesc
ch <- libvirtDomainMemoryResidentDesc
ch <- libvirtDomainMemoryLastUpdateDesc
ch <- libvirtDomainCpuTimeDesc
ch <- libvirtDomainCpuSystemDesc
ch <- libvirtDomainCpuUserDesc
ch <- libvirtDomainVcpuMaximumDesc
ch <- libvirtDomainVcpuCurrentDesc
ch <- libvirtDomainBlockRdBytesDesc
ch <- libvirtDomainBlockRdReqsDesc
ch <- libvirtDomainBlockRdTimesDesc
ch <- libvirtDomainBlockWrBytesDesc
ch <- libvirtDomainBlockWrReqsDesc
ch <- libvirtDomainBlockWrTimesDesc
ch <- libvirtDomainBlockFlReqsDesc
ch <- libvirtDomainBlockFlTimesDesc
}
// Collect scrapes Prometheus metrics from libvirt.
func (e *LibvirtExporter) Collect(ch chan<- prometheus.Metric) {
err := CollectFromLibvirt(ch, e.uri)
if err == nil {
ch <- prometheus.MustNewConstMetric(
libvirtUpDesc,
prometheus.GaugeValue,
1.0)
} else {
log.Printf("Failed to scrape metrics: %s", err)
ch <- prometheus.MustNewConstMetric(
libvirtUpDesc,
prometheus.GaugeValue,
0.0)
}
}
func NewLibvirtExporter(uri string) (*LibvirtExporter, error) {
u, err := url.Parse(uri)
if err != nil {
return nil, err
}
return &LibvirtExporter{uri: u}, nil
}

View File

@ -0,0 +1,38 @@
package main
import (
"flag"
"log"
"net/http"
"github.com/prometheus/client_golang/prometheus"
promlibvirt "sdstack.com/sdstack/prometheus-libvirt"
)
func main() {
var (
listenAddress = flag.String("web.listen-address", ":9177", "Address to listen on for web interface and telemetry.")
metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
libvirtURI = flag.String("libvirt.uri", "qemu:///system", "Libvirt URI from which to extract metrics.")
)
flag.Parse()
exporter, err := promlibvirt.NewLibvirtExporter(*libvirtURI)
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>Libvirt Exporter</title></head>
<body>
<h1>Libvirt Exporter</h1>
<p><a href='` + *metricsPath + `'>Metrics</a></p>
</body>
</html>`))
})
log.Fatal(http.ListenAndServe(*listenAddress, nil))
}