571 lines
17 KiB
Go
571 lines
17 KiB
Go
package libvirt
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
|
|
golibvirt "github.com/digitalocean/go-libvirt"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
)
|
|
|
|
var (
|
|
cpuCacheMonitorRegexp = regexp.MustCompile(`^cache\.monitor\..+?\.(name|vcpus|bank_count)$`)
|
|
cpuCacheMonitorBankRegexp = regexp.MustCompile(`^cache\.monitor\..+?\.bank\..+?\.(id|bytes)$`)
|
|
memoryBandwidthMonitorRegexp = regexp.MustCompile(`^bandwidth\.monitor\..+?\.(name|vcpus|node_count)$`)
|
|
memoryBandwidthMonitorNodeRegexp = regexp.MustCompile(`^bandwidth\.monitor\..+?\.node\..+?\.(id|bytes_local|bytes_total)$`)
|
|
)
|
|
|
|
func (l *Libvirt) addMetrics(stats []golibvirt.DomainStatsRecord, vcpuInfos map[string][]vcpuAffinity, acc telegraf.Accumulator) {
|
|
domainsMetrics := translateMetrics(stats)
|
|
|
|
for domainName, metrics := range domainsMetrics {
|
|
for metricType, values := range metrics {
|
|
switch metricType {
|
|
case "state":
|
|
addStateMetrics(values, domainName, acc)
|
|
case "cpu":
|
|
addCPUMetrics(values, domainName, acc)
|
|
case "balloon":
|
|
addBalloonMetrics(values, domainName, acc)
|
|
case "vcpu":
|
|
l.addVcpuMetrics(values, domainName, vcpuInfos[domainName], acc)
|
|
case "net":
|
|
addInterfaceMetrics(values, domainName, acc)
|
|
case "perf":
|
|
addPerfMetrics(values, domainName, acc)
|
|
case "block":
|
|
addBlockMetrics(values, domainName, acc)
|
|
case "iothread":
|
|
addIothreadMetrics(values, domainName, acc)
|
|
case "memory":
|
|
addMemoryMetrics(values, domainName, acc)
|
|
case "dirtyrate":
|
|
addDirtyrateMetrics(values, domainName, acc)
|
|
}
|
|
}
|
|
}
|
|
|
|
if l.vcpuMappingEnabled {
|
|
for domainName, vcpuInfo := range vcpuInfos {
|
|
var tags = make(map[string]string)
|
|
var fields = make(map[string]interface{})
|
|
|
|
for _, vcpu := range vcpuInfo {
|
|
tags["domain_name"] = domainName
|
|
tags["vcpu_id"] = vcpu.vcpuID
|
|
fields["cpu_id"] = vcpu.coresAffinity
|
|
acc.AddFields("libvirt_cpu_affinity", fields, tags)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func translateMetrics(stats []golibvirt.DomainStatsRecord) map[string]map[string]map[string]golibvirt.TypedParamValue {
|
|
metrics := make(map[string]map[string]map[string]golibvirt.TypedParamValue)
|
|
for _, stat := range stats {
|
|
if stat.Params != nil {
|
|
if metrics[stat.Dom.Name] == nil {
|
|
metrics[stat.Dom.Name] = make(map[string]map[string]golibvirt.TypedParamValue)
|
|
}
|
|
|
|
for _, params := range stat.Params {
|
|
statGroup := strings.Split(params.Field, ".")[0]
|
|
if metrics[stat.Dom.Name][statGroup] == nil {
|
|
metrics[stat.Dom.Name][statGroup] = make(map[string]golibvirt.TypedParamValue)
|
|
}
|
|
|
|
metrics[stat.Dom.Name][statGroup][strings.TrimPrefix(params.Field, statGroup+".")] = params.Value
|
|
}
|
|
}
|
|
}
|
|
|
|
return metrics
|
|
}
|
|
|
|
func addStateMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, acc telegraf.Accumulator) {
|
|
var stateFields = make(map[string]interface{})
|
|
var stateTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
switch key {
|
|
case "state", "reason":
|
|
stateFields[key] = metric.I
|
|
}
|
|
}
|
|
|
|
if len(stateFields) > 0 {
|
|
acc.AddFields("libvirt_state", stateFields, stateTags)
|
|
}
|
|
}
|
|
|
|
func addCPUMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, acc telegraf.Accumulator) {
|
|
var cpuFields = make(map[string]interface{})
|
|
var cpuCacheMonitorTotalFields = make(map[string]interface{})
|
|
|
|
var cpuCacheMonitorData = make(map[string]map[string]interface{})
|
|
var cpuCacheMonitorBankData = make(map[string]map[string]map[string]interface{})
|
|
|
|
var cpuTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
switch key {
|
|
case "time", "user", "system":
|
|
cpuFields[key] = metric.I
|
|
case "haltpoll.success.time", "haltpoll.fail.time":
|
|
cpuFields[strings.ReplaceAll(key, ".", "_")] = metric.I
|
|
case "cache.monitor.count":
|
|
cpuCacheMonitorTotalFields["count"] = metric.I
|
|
default:
|
|
if strings.Contains(key, "bank.count") {
|
|
key = strings.ReplaceAll(key, "bank.count", "bank_count")
|
|
}
|
|
|
|
cpuStat := strings.Split(key, ".")
|
|
if len(cpuStat) == 4 && cpuCacheMonitorRegexp.MatchString(key) {
|
|
cacheMonitorID := cpuStat[2]
|
|
cpuCacheMonitorFields, ok := cpuCacheMonitorData[cacheMonitorID]
|
|
if !ok {
|
|
cpuCacheMonitorFields = make(map[string]interface{})
|
|
cpuCacheMonitorData[cacheMonitorID] = cpuCacheMonitorFields
|
|
}
|
|
|
|
cpuCacheMonitorFields[cpuStat[3]] = metric.I
|
|
} else if len(cpuStat) == 6 && cpuCacheMonitorBankRegexp.MatchString(key) {
|
|
cacheMonitorID := cpuStat[2]
|
|
bankIndex := cpuStat[4]
|
|
|
|
bankData, ok := cpuCacheMonitorBankData[cacheMonitorID]
|
|
if !ok {
|
|
bankData = make(map[string]map[string]interface{})
|
|
cpuCacheMonitorBankData[cacheMonitorID] = bankData
|
|
}
|
|
|
|
bankFields, ok := cpuCacheMonitorBankData[cacheMonitorID][bankIndex]
|
|
if !ok {
|
|
bankFields = make(map[string]interface{})
|
|
bankData[bankIndex] = bankFields
|
|
}
|
|
|
|
bankFields[cpuStat[5]] = metric.I
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(cpuFields) > 0 {
|
|
acc.AddFields("libvirt_cpu", cpuFields, cpuTags)
|
|
}
|
|
|
|
if len(cpuCacheMonitorTotalFields) > 0 {
|
|
acc.AddFields("libvirt_cpu_cache_monitor_total", cpuCacheMonitorTotalFields, cpuTags)
|
|
}
|
|
|
|
for cpuID, cpuCacheMonitorFields := range cpuCacheMonitorData {
|
|
if len(cpuCacheMonitorFields) > 0 {
|
|
cpuCacheMonitorTags := map[string]string{
|
|
"domain_name": domainName,
|
|
"cache_monitor_id": cpuID,
|
|
}
|
|
acc.AddFields("libvirt_cpu_cache_monitor", cpuCacheMonitorFields, cpuCacheMonitorTags)
|
|
}
|
|
}
|
|
|
|
for cacheMonitorID, bankData := range cpuCacheMonitorBankData {
|
|
for bankIndex, bankFields := range bankData {
|
|
if len(bankFields) > 0 {
|
|
bankTags := map[string]string{
|
|
"domain_name": domainName,
|
|
"cache_monitor_id": cacheMonitorID,
|
|
"bank_index": bankIndex,
|
|
}
|
|
acc.AddFields("libvirt_cpu_cache_monitor_bank", bankFields, bankTags)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func addBalloonMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, acc telegraf.Accumulator) {
|
|
var balloonFields = make(map[string]interface{})
|
|
var balloonTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
switch key {
|
|
case "current", "maximum", "swap_in", "swap_out", "major_fault", "minor_fault", "unused", "available",
|
|
"rss", "usable", "disk_caches", "hugetlb_pgalloc", "hugetlb_pgfail":
|
|
balloonFields[key] = metric.I
|
|
case "last-update":
|
|
balloonFields["last_update"] = metric.I
|
|
}
|
|
}
|
|
|
|
if len(balloonFields) > 0 {
|
|
acc.AddFields("libvirt_balloon", balloonFields, balloonTags)
|
|
}
|
|
}
|
|
|
|
func (l *Libvirt) addVcpuMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, vcpuInfos []vcpuAffinity, acc telegraf.Accumulator) {
|
|
var vcpuTotalFields = make(map[string]interface{})
|
|
var vcpuData = make(map[string]map[string]interface{})
|
|
|
|
var vcpuTotalTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
switch key {
|
|
case "current", "maximum":
|
|
vcpuTotalFields[key] = metric.I
|
|
default:
|
|
vcpuStat := strings.Split(key, ".")
|
|
if len(vcpuStat) != 2 {
|
|
continue
|
|
}
|
|
vcpuID := vcpuStat[0]
|
|
fieldName := vcpuStat[1]
|
|
vcpuFields, ok := vcpuData[vcpuID]
|
|
if !ok {
|
|
vcpuFields = make(map[string]interface{})
|
|
vcpuData[vcpuID] = vcpuFields
|
|
}
|
|
|
|
switch fieldName {
|
|
case "halted":
|
|
haltedIntegerValue := 0
|
|
if metric.I == "yes" {
|
|
haltedIntegerValue = 1
|
|
}
|
|
|
|
vcpuFields["halted_i"] = haltedIntegerValue
|
|
fallthrough
|
|
case "state", "time", "wait", "delay":
|
|
vcpuFields[fieldName] = metric.I
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(vcpuTotalFields) > 0 {
|
|
acc.AddFields("libvirt_vcpu_total", vcpuTotalFields, vcpuTotalTags)
|
|
}
|
|
|
|
for vcpuID, vcpuFields := range vcpuData {
|
|
if len(vcpuFields) > 0 {
|
|
vcpuTags := map[string]string{
|
|
"domain_name": domainName,
|
|
"vcpu_id": vcpuID,
|
|
}
|
|
|
|
if pCPUID := l.getCurrentPCPUForVCPU(vcpuID, vcpuInfos); pCPUID >= 0 {
|
|
vcpuFields["cpu_id"] = pCPUID
|
|
}
|
|
|
|
acc.AddFields("libvirt_vcpu", vcpuFields, vcpuTags)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (l *Libvirt) getCurrentPCPUForVCPU(vcpuID string, vcpuInfos []vcpuAffinity) int32 {
|
|
if !l.shouldGetCurrentPCPU() {
|
|
return -1
|
|
}
|
|
|
|
for _, vcpuInfo := range vcpuInfos {
|
|
if vcpuInfo.vcpuID == vcpuID {
|
|
return vcpuInfo.currentPCPUID
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
func addInterfaceMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, acc telegraf.Accumulator) {
|
|
var netTotalFields = make(map[string]interface{})
|
|
var netData = make(map[string]map[string]interface{})
|
|
|
|
var netTotalTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
if key == "count" {
|
|
netTotalFields[key] = metric.I
|
|
} else {
|
|
netStat := strings.SplitN(key, ".", 2)
|
|
if len(netStat) < 2 {
|
|
continue
|
|
}
|
|
|
|
netID := netStat[0]
|
|
netFields, ok := netData[netID]
|
|
if !ok {
|
|
netFields = make(map[string]interface{})
|
|
netData[netID] = netFields
|
|
}
|
|
|
|
fieldName := strings.ReplaceAll(netStat[1], ".", "_")
|
|
switch fieldName {
|
|
case "name", "rx_bytes", "rx_pkts", "rx_errs", "rx_drop", "tx_bytes", "tx_pkts", "tx_errs", "tx_drop":
|
|
netFields[fieldName] = metric.I
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(netTotalFields) > 0 {
|
|
acc.AddFields("libvirt_net_total", netTotalFields, netTotalTags)
|
|
}
|
|
|
|
for netID, netFields := range netData {
|
|
if len(netFields) > 0 {
|
|
netTags := map[string]string{
|
|
"domain_name": domainName,
|
|
"interface_id": netID,
|
|
}
|
|
acc.AddFields("libvirt_net", netFields, netTags)
|
|
}
|
|
}
|
|
}
|
|
|
|
func addPerfMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, acc telegraf.Accumulator) {
|
|
var perfFields = make(map[string]interface{})
|
|
var perfTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
switch key {
|
|
case "cmt", "mbmt", "mbml", "cpu_cycles", "instructions", "cache_references", "cache_misses",
|
|
"branch_instructions", "branch_misses", "bus_cycles", "stalled_cycles_frontend", "stalled_cycles_backend",
|
|
"ref_cpu_cycles", "cpu_clock", "task_clock", "page_faults", "context_switches",
|
|
"cpu_migrations", "page_faults_min", "page_faults_maj", "alignment_faults", "emulation_faults":
|
|
perfFields[key] = metric.I
|
|
}
|
|
}
|
|
|
|
if len(perfFields) > 0 {
|
|
acc.AddFields("libvirt_perf", perfFields, perfTags)
|
|
}
|
|
}
|
|
|
|
func addBlockMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, acc telegraf.Accumulator) {
|
|
var blockTotalFields = make(map[string]interface{})
|
|
var blockData = make(map[string]map[string]interface{})
|
|
|
|
var blockTotalTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
if key == "count" {
|
|
blockTotalFields["count"] = metric.I
|
|
} else {
|
|
blockStat := strings.SplitN(key, ".", 2)
|
|
if len(blockStat) < 2 {
|
|
continue
|
|
}
|
|
|
|
blockID := blockStat[0]
|
|
blockFields, ok := blockData[blockID]
|
|
if !ok {
|
|
blockFields = make(map[string]interface{})
|
|
blockData[blockID] = blockFields
|
|
}
|
|
|
|
fieldName := strings.ReplaceAll(blockStat[1], ".", "_")
|
|
switch fieldName {
|
|
case "name", "backingIndex", "path", "rd_reqs", "rd_bytes", "rd_times", "wr_reqs", "wr_bytes", "wr_times",
|
|
"fl_reqs", "fl_times", "errors", "allocation", "capacity", "physical", "threshold":
|
|
blockFields[fieldName] = metric.I
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(blockTotalFields) > 0 {
|
|
acc.AddFields("libvirt_block_total", blockTotalFields, blockTotalTags)
|
|
}
|
|
|
|
for blockID, blockFields := range blockData {
|
|
if len(blockFields) > 0 {
|
|
blockTags := map[string]string{
|
|
"domain_name": domainName,
|
|
"block_id": blockID,
|
|
}
|
|
acc.AddFields("libvirt_block", blockFields, blockTags)
|
|
}
|
|
}
|
|
}
|
|
|
|
func addIothreadMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, acc telegraf.Accumulator) {
|
|
var iothreadTotalFields = make(map[string]interface{})
|
|
var iothreadData = make(map[string]map[string]interface{})
|
|
|
|
var iothreadTotalTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
if key == "count" {
|
|
iothreadTotalFields["count"] = metric.I
|
|
} else {
|
|
iothreadStat := strings.Split(key, ".")
|
|
if len(iothreadStat) != 2 {
|
|
continue
|
|
}
|
|
|
|
iothreadID := iothreadStat[0]
|
|
iothreadFields, ok := iothreadData[iothreadID]
|
|
if !ok {
|
|
iothreadFields = make(map[string]interface{})
|
|
iothreadData[iothreadID] = iothreadFields
|
|
}
|
|
|
|
fieldName := strings.ReplaceAll(iothreadStat[1], "-", "_")
|
|
switch fieldName {
|
|
case "poll_max_ns", "poll_grow", "poll_shrink":
|
|
iothreadFields[fieldName] = metric.I
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(iothreadTotalFields) > 0 {
|
|
acc.AddFields("libvirt_iothread_total", iothreadTotalFields, iothreadTotalTags)
|
|
}
|
|
|
|
for iothreadID, iothreadFields := range iothreadData {
|
|
if len(iothreadFields) > 0 {
|
|
iothreadTags := map[string]string{
|
|
"domain_name": domainName,
|
|
"iothread_id": iothreadID,
|
|
}
|
|
acc.AddFields("libvirt_iothread", iothreadFields, iothreadTags)
|
|
}
|
|
}
|
|
}
|
|
|
|
func addMemoryMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, acc telegraf.Accumulator) {
|
|
var memoryBandwidthMonitorTotalFields = make(map[string]interface{})
|
|
|
|
var memoryBandwidthMonitorData = make(map[string]map[string]interface{})
|
|
var memoryBandwidthMonitorNodeData = make(map[string]map[string]map[string]interface{})
|
|
|
|
var memoryBandwidthMonitorTotalTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
switch key {
|
|
case "bandwidth.monitor.count":
|
|
memoryBandwidthMonitorTotalFields["count"] = metric.I
|
|
default:
|
|
if strings.Contains(key, "node.count") {
|
|
key = strings.ReplaceAll(key, "node.count", "node_count")
|
|
} else if strings.Contains(key, "bytes.local") {
|
|
key = strings.ReplaceAll(key, "bytes.local", "bytes_local")
|
|
} else if strings.Contains(key, "bytes.total") {
|
|
key = strings.ReplaceAll(key, "bytes.total", "bytes_total")
|
|
}
|
|
|
|
memoryStat := strings.Split(key, ".")
|
|
if len(memoryStat) == 4 && memoryBandwidthMonitorRegexp.MatchString(key) {
|
|
memoryBandwidthMonitorID := memoryStat[2]
|
|
memoryBandwidthMonitorFields, ok := memoryBandwidthMonitorData[memoryBandwidthMonitorID]
|
|
if !ok {
|
|
memoryBandwidthMonitorFields = make(map[string]interface{})
|
|
memoryBandwidthMonitorData[memoryBandwidthMonitorID] = memoryBandwidthMonitorFields
|
|
}
|
|
|
|
memoryBandwidthMonitorFields[memoryStat[3]] = metric.I
|
|
} else if len(memoryStat) == 6 && memoryBandwidthMonitorNodeRegexp.MatchString(key) {
|
|
memoryBandwidthMonitorID := memoryStat[2]
|
|
controllerIndex := memoryStat[4]
|
|
|
|
nodeData, ok := memoryBandwidthMonitorNodeData[memoryBandwidthMonitorID]
|
|
if !ok {
|
|
nodeData = make(map[string]map[string]interface{})
|
|
memoryBandwidthMonitorNodeData[memoryBandwidthMonitorID] = nodeData
|
|
}
|
|
|
|
nodeFields, ok := memoryBandwidthMonitorNodeData[memoryBandwidthMonitorID][controllerIndex]
|
|
if !ok {
|
|
nodeFields = make(map[string]interface{})
|
|
nodeData[controllerIndex] = nodeFields
|
|
}
|
|
|
|
nodeFields[memoryStat[5]] = metric.I
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(memoryBandwidthMonitorTotalFields) > 0 {
|
|
acc.AddFields("libvirt_memory_bandwidth_monitor_total", memoryBandwidthMonitorTotalFields, memoryBandwidthMonitorTotalTags)
|
|
}
|
|
|
|
for memoryBandwidthMonitorID, memoryFields := range memoryBandwidthMonitorData {
|
|
if len(memoryFields) > 0 {
|
|
tags := map[string]string{
|
|
"domain_name": domainName,
|
|
"memory_bandwidth_monitor_id": memoryBandwidthMonitorID,
|
|
}
|
|
acc.AddFields("libvirt_memory_bandwidth_monitor", memoryFields, tags)
|
|
}
|
|
}
|
|
|
|
for memoryBandwidthMonitorID, nodeData := range memoryBandwidthMonitorNodeData {
|
|
for controllerIndex, nodeFields := range nodeData {
|
|
if len(nodeFields) > 0 {
|
|
tags := map[string]string{
|
|
"domain_name": domainName,
|
|
"memory_bandwidth_monitor_id": memoryBandwidthMonitorID,
|
|
"controller_index": controllerIndex,
|
|
}
|
|
acc.AddFields("libvirt_memory_bandwidth_monitor_node", nodeFields, tags)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func addDirtyrateMetrics(metrics map[string]golibvirt.TypedParamValue, domainName string, acc telegraf.Accumulator) {
|
|
var dirtyrateFields = make(map[string]interface{})
|
|
var dirtyrateVcpuData = make(map[string]map[string]interface{})
|
|
|
|
var dirtyrateTags = map[string]string{
|
|
"domain_name": domainName,
|
|
}
|
|
|
|
for key, metric := range metrics {
|
|
switch key {
|
|
case "calc_status", "calc_start_time", "calc_period",
|
|
"megabytes_per_second", "calc_mode":
|
|
dirtyrateFields[key] = metric.I
|
|
default:
|
|
dirtyrateStat := strings.Split(key, ".")
|
|
if len(dirtyrateStat) == 3 && dirtyrateStat[0] == "vcpu" && dirtyrateStat[2] == "megabytes_per_second" {
|
|
vcpuID := dirtyrateStat[1]
|
|
dirtyRateFields, ok := dirtyrateVcpuData[vcpuID]
|
|
if !ok {
|
|
dirtyRateFields = make(map[string]interface{})
|
|
dirtyrateVcpuData[vcpuID] = dirtyRateFields
|
|
}
|
|
dirtyRateFields[dirtyrateStat[2]] = metric.I
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(dirtyrateFields) > 0 {
|
|
acc.AddFields("libvirt_dirtyrate", dirtyrateFields, dirtyrateTags)
|
|
}
|
|
|
|
for vcpuID, dirtyRateFields := range dirtyrateVcpuData {
|
|
if len(dirtyRateFields) > 0 {
|
|
dirtyRateTags := map[string]string{
|
|
"domain_name": domainName,
|
|
"vcpu_id": vcpuID,
|
|
}
|
|
acc.AddFields("libvirt_dirtyrate_vcpu", dirtyRateFields, dirtyRateTags)
|
|
}
|
|
}
|
|
}
|