From 8fc004ab36aa2b06b02a1c7b2672dd4c74a0a795 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Wed, 3 Oct 2018 09:41:20 +0300 Subject: [PATCH] initial Signed-off-by: Vasiliy Tolstov --- .gitignore | 1 + LICENSE | 22 ++ README.md | 22 ++ common.go | 534 ++++++++++++++++++++++++++++ prometheus-libvirt-exporter/main.go | 38 ++ 5 files changed, 617 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 common.go create mode 100644 prometheus-libvirt-exporter/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba04b03 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/libvirt_exporter diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..287904f --- /dev/null +++ b/LICENSE @@ -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. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d9a0a3 --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/common.go b/common.go new file mode 100644 index 0000000..90238f2 --- /dev/null +++ b/common.go @@ -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 +} diff --git a/prometheus-libvirt-exporter/main.go b/prometheus-libvirt-exporter/main.go new file mode 100644 index 0000000..1eef210 --- /dev/null +++ b/prometheus-libvirt-exporter/main.go @@ -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(` + + Libvirt Exporter + +

Libvirt Exporter

+

Metrics

+ + `)) + }) + log.Fatal(http.ListenAndServe(*listenAddress, nil)) +}