251 lines
6.1 KiB
Go
251 lines
6.1 KiB
Go
package proxmox
|
|
|
|
import (
|
|
"encoding/json"
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
var sampleConfig = `
|
|
## API connection configuration. The API token was introduced in Proxmox v6.2. Required permissions for user and token: PVEAuditor role on /.
|
|
base_url = "https://localhost:8006/api2/json"
|
|
api_token = "USER@REALM!TOKENID=UUID"
|
|
|
|
## Optional TLS Config
|
|
# tls_ca = "/etc/telegraf/ca.pem"
|
|
# tls_cert = "/etc/telegraf/cert.pem"
|
|
# tls_key = "/etc/telegraf/key.pem"
|
|
## Use TLS but skip chain & host verification
|
|
insecure_skip_verify = false
|
|
|
|
# HTTP response timeout (default: 5s)
|
|
response_timeout = "5s"
|
|
`
|
|
|
|
func (px *Proxmox) SampleConfig() string {
|
|
return sampleConfig
|
|
}
|
|
|
|
func (px *Proxmox) Description() string {
|
|
return "Provides metrics from Proxmox nodes (Proxmox Virtual Environment > 6.2)."
|
|
}
|
|
|
|
func (px *Proxmox) Gather(acc telegraf.Accumulator) error {
|
|
err := getNodeSearchDomain(px)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gatherLxcData(px, acc)
|
|
gatherQemuData(px, acc)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (px *Proxmox) Init() error {
|
|
hostname, err := os.Hostname()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
px.hostname = hostname
|
|
|
|
tlsCfg, err := px.ClientConfig.TLSConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
px.httpClient = &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: tlsCfg,
|
|
},
|
|
Timeout: px.ResponseTimeout.Duration,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
px := Proxmox{
|
|
requestFunction: performRequest,
|
|
}
|
|
|
|
inputs.Add("proxmox", func() telegraf.Input { return &px })
|
|
}
|
|
|
|
func getNodeSearchDomain(px *Proxmox) error {
|
|
apiUrl := "/nodes/" + px.hostname + "/dns"
|
|
jsonData, err := px.requestFunction(px, apiUrl, http.MethodGet, nil)
|
|
|
|
var nodeDns NodeDns
|
|
err = json.Unmarshal(jsonData, &nodeDns)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
px.nodeSearchDomain = nodeDns.Data.Searchdomain
|
|
|
|
return nil
|
|
}
|
|
|
|
func performRequest(px *Proxmox, apiUrl string, method string, data url.Values) ([]byte, error) {
|
|
request, err := http.NewRequest(method, px.BaseURL+apiUrl, strings.NewReader(data.Encode()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request.Header.Add("Authorization", "PVEAPIToken="+px.APIToken)
|
|
|
|
resp, err := px.httpClient.Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
responseBody, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return responseBody, nil
|
|
}
|
|
|
|
func gatherLxcData(px *Proxmox, acc telegraf.Accumulator) {
|
|
gatherVmData(px, acc, LXC)
|
|
}
|
|
|
|
func gatherQemuData(px *Proxmox, acc telegraf.Accumulator) {
|
|
gatherVmData(px, acc, QEMU)
|
|
}
|
|
|
|
func gatherVmData(px *Proxmox, acc telegraf.Accumulator, rt ResourceType) {
|
|
vmStats, err := getVmStats(px, rt)
|
|
if err != nil {
|
|
px.Log.Error("Error getting VM stats: %v", err)
|
|
return
|
|
}
|
|
|
|
// For each VM add metrics to Accumulator
|
|
for _, vmStat := range vmStats.Data {
|
|
vmConfig, err := getVmConfig(px, vmStat.ID, rt)
|
|
if err != nil {
|
|
px.Log.Error("Error getting VM config: %v", err)
|
|
return
|
|
}
|
|
tags := getTags(px, vmStat.Name, vmConfig, rt)
|
|
fields, err := getFields(vmStat)
|
|
if err != nil {
|
|
px.Log.Error("Error getting VM measurements: %v", err)
|
|
return
|
|
}
|
|
acc.AddFields("proxmox", fields, tags)
|
|
}
|
|
}
|
|
|
|
func getVmStats(px *Proxmox, rt ResourceType) (VmStats, error) {
|
|
apiUrl := "/nodes/" + px.hostname + "/" + string(rt)
|
|
jsonData, err := px.requestFunction(px, apiUrl, http.MethodGet, nil)
|
|
if err != nil {
|
|
return VmStats{}, err
|
|
}
|
|
|
|
var vmStats VmStats
|
|
err = json.Unmarshal(jsonData, &vmStats)
|
|
if err != nil {
|
|
return VmStats{}, err
|
|
}
|
|
|
|
return vmStats, nil
|
|
}
|
|
|
|
func getVmConfig(px *Proxmox, vmId string, rt ResourceType) (VmConfig, error) {
|
|
apiUrl := "/nodes/" + px.hostname + "/" + string(rt) + "/" + vmId + "/config"
|
|
jsonData, err := px.requestFunction(px, apiUrl, http.MethodGet, nil)
|
|
if err != nil {
|
|
return VmConfig{}, err
|
|
}
|
|
|
|
var vmConfig VmConfig
|
|
err = json.Unmarshal(jsonData, &vmConfig)
|
|
if err != nil {
|
|
return VmConfig{}, err
|
|
}
|
|
|
|
return vmConfig, nil
|
|
}
|
|
|
|
func getFields(vmStat VmStat) (map[string]interface{}, error) {
|
|
mem_total, mem_used, mem_free, mem_used_percentage := getByteMetrics(vmStat.TotalMem, vmStat.UsedMem)
|
|
swap_total, swap_used, swap_free, swap_used_percentage := getByteMetrics(vmStat.TotalSwap, vmStat.UsedSwap)
|
|
disk_total, disk_used, disk_free, disk_used_percentage := getByteMetrics(vmStat.TotalDisk, vmStat.UsedDisk)
|
|
|
|
return map[string]interface{}{
|
|
"status": vmStat.Status,
|
|
"uptime": jsonNumberToInt64(vmStat.Uptime),
|
|
"cpuload": jsonNumberToFloat64(vmStat.CpuLoad),
|
|
"mem_used": mem_used,
|
|
"mem_total": mem_total,
|
|
"mem_free": mem_free,
|
|
"mem_used_percentage": mem_used_percentage,
|
|
"swap_used": swap_used,
|
|
"swap_total": swap_total,
|
|
"swap_free": swap_free,
|
|
"swap_used_percentage": swap_used_percentage,
|
|
"disk_used": disk_used,
|
|
"disk_total": disk_total,
|
|
"disk_free": disk_free,
|
|
"disk_used_percentage": disk_used_percentage,
|
|
}, nil
|
|
}
|
|
|
|
func getByteMetrics(total json.Number, used json.Number) (int64, int64, int64, float64) {
|
|
int64Total := jsonNumberToInt64(total)
|
|
int64Used := jsonNumberToInt64(used)
|
|
int64Free := int64Total - int64Used
|
|
usedPercentage := 0.0
|
|
if int64Total != 0 {
|
|
usedPercentage = float64(int64Used) * 100 / float64(int64Total)
|
|
}
|
|
|
|
return int64Total, int64Used, int64Free, usedPercentage
|
|
}
|
|
|
|
func jsonNumberToInt64(value json.Number) int64 {
|
|
int64Value, err := value.Int64()
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
return int64Value
|
|
}
|
|
|
|
func jsonNumberToFloat64(value json.Number) float64 {
|
|
float64Value, err := value.Float64()
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
return float64Value
|
|
}
|
|
|
|
func getTags(px *Proxmox, name string, vmConfig VmConfig, rt ResourceType) map[string]string {
|
|
domain := vmConfig.Data.Searchdomain
|
|
if len(domain) == 0 {
|
|
domain = px.nodeSearchDomain
|
|
}
|
|
|
|
hostname := vmConfig.Data.Hostname
|
|
if len(hostname) == 0 {
|
|
hostname = name
|
|
}
|
|
fqdn := hostname + "." + domain
|
|
|
|
return map[string]string{
|
|
"node_fqdn": px.hostname + "." + px.nodeSearchDomain,
|
|
"vm_name": name,
|
|
"vm_fqdn": fqdn,
|
|
"vm_type": string(rt),
|
|
}
|
|
}
|