1
0
Fork 0
telegraf/plugins/inputs/lvm/lvm.go

289 lines
6.5 KiB
Go
Raw Normal View History

//go:generate ../../../tools/readme_config_includer/generator
package lvm
import (
_ "embed"
"encoding/json"
"fmt"
"os/exec"
"strconv"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
var (
execCommand = exec.Command
)
type LVM struct {
UseSudo bool `toml:"use_sudo"`
PVSBinary string `toml:"pvs_binary"`
VGSBinary string `toml:"vgs_binary"`
LVSBinary string `toml:"lvs_binary"`
}
func (*LVM) SampleConfig() string {
return sampleConfig
}
func (lvm *LVM) Gather(acc telegraf.Accumulator) error {
if err := lvm.gatherPhysicalVolumes(acc); err != nil {
return err
} else if err := lvm.gatherVolumeGroups(acc); err != nil {
return err
} else if err := lvm.gatherLogicalVolumes(acc); err != nil {
return err
}
return nil
}
func (lvm *LVM) gatherPhysicalVolumes(acc telegraf.Accumulator) error {
args := []string{
"--reportformat", "json", "--units", "b", "--nosuffix",
"-o", "pv_name,vg_name,pv_size,pv_free,pv_used",
}
out, err := lvm.runCmd(lvm.PVSBinary, args)
if err != nil {
return err
}
var report pvsReport
err = json.Unmarshal(out, &report)
if err != nil {
return fmt.Errorf("failed to unmarshal physical volume JSON: %w", err)
}
if len(report.Report) > 0 {
for _, pv := range report.Report[0].Pv {
tags := map[string]string{
"path": pv.Name,
"vol_group": pv.VolGroup,
}
size, err := strconv.ParseUint(pv.Size, 10, 64)
if err != nil {
return err
}
free, err := strconv.ParseUint(pv.Free, 10, 64)
if err != nil {
return err
}
used, err := strconv.ParseUint(pv.Used, 10, 64)
if err != nil {
return err
}
usedPercent := float64(used) / float64(size) * 100
fields := map[string]interface{}{
"size": size,
"free": free,
"used": used,
"used_percent": usedPercent,
}
acc.AddFields("lvm_physical_vol", fields, tags)
}
}
return nil
}
func (lvm *LVM) gatherVolumeGroups(acc telegraf.Accumulator) error {
args := []string{
"--reportformat", "json", "--units", "b", "--nosuffix",
"-o", "vg_name,pv_count,lv_count,snap_count,vg_size,vg_free",
}
out, err := lvm.runCmd(lvm.VGSBinary, args)
if err != nil {
return err
}
var report vgsReport
err = json.Unmarshal(out, &report)
if err != nil {
return fmt.Errorf("failed to unmarshal vol group JSON: %w", err)
}
if len(report.Report) > 0 {
for _, vg := range report.Report[0].Vg {
tags := map[string]string{
"name": vg.Name,
}
size, err := strconv.ParseUint(vg.Size, 10, 64)
if err != nil {
return err
}
free, err := strconv.ParseUint(vg.Free, 10, 64)
if err != nil {
return err
}
pvCount, err := strconv.ParseUint(vg.PvCount, 10, 64)
if err != nil {
return err
}
lvCount, err := strconv.ParseUint(vg.LvCount, 10, 64)
if err != nil {
return err
}
snapCount, err := strconv.ParseUint(vg.SnapCount, 10, 64)
if err != nil {
return err
}
usedPercent := (float64(size) - float64(free)) / float64(size) * 100
fields := map[string]interface{}{
"size": size,
"free": free,
"used_percent": usedPercent,
"physical_volume_count": pvCount,
"logical_volume_count": lvCount,
"snapshot_count": snapCount,
}
acc.AddFields("lvm_vol_group", fields, tags)
}
}
return nil
}
func (lvm *LVM) gatherLogicalVolumes(acc telegraf.Accumulator) error {
args := []string{
"--reportformat", "json", "--units", "b", "--nosuffix",
"-o", "lv_name,vg_name,lv_size,data_percent,metadata_percent",
}
out, err := lvm.runCmd(lvm.LVSBinary, args)
if err != nil {
return err
}
var report lvsReport
err = json.Unmarshal(out, &report)
if err != nil {
return fmt.Errorf("failed to unmarshal logical vol JSON: %w", err)
}
if len(report.Report) > 0 {
for _, lv := range report.Report[0].Lv {
tags := map[string]string{
"name": lv.Name,
"vol_group": lv.VolGroup,
}
size, err := strconv.ParseUint(lv.Size, 10, 64)
if err != nil {
return err
}
// Does not apply to all logical volumes, set default value
if lv.DataPercent == "" {
lv.DataPercent = "0.0"
}
dataPercent, err := strconv.ParseFloat(lv.DataPercent, 32)
if err != nil {
return err
}
// Does not apply to all logical volumes, set default value
if lv.MetadataPercent == "" {
lv.MetadataPercent = "0.0"
}
metadataPercent, err := strconv.ParseFloat(lv.MetadataPercent, 32)
if err != nil {
return err
}
fields := map[string]interface{}{
"size": size,
"data_percent": dataPercent,
"metadata_percent": metadataPercent,
}
acc.AddFields("lvm_logical_vol", fields, tags)
}
}
return nil
}
func (lvm *LVM) runCmd(cmd string, args []string) ([]byte, error) {
execCmd := execCommand(cmd, args...)
if lvm.UseSudo {
execCmd = execCommand("sudo", append([]string{"-n", cmd}, args...)...)
}
out, err := internal.StdOutputTimeout(execCmd, 5*time.Second)
if err != nil {
return nil, fmt.Errorf(
"failed to run command %s: %w - %s", strings.Join(execCmd.Args, " "), err, string(out),
)
}
return out, nil
}
// Represents info about physical volume command, pvs, output
type pvsReport struct {
Report []struct {
Pv []struct {
Name string `json:"pv_name"`
VolGroup string `json:"vg_name"`
Size string `json:"pv_size"`
Free string `json:"pv_free"`
Used string `json:"pv_used"`
} `json:"pv"`
} `json:"report"`
}
// Represents info about volume group command, vgs, output
type vgsReport struct {
Report []struct {
Vg []struct {
Name string `json:"vg_name"`
Size string `json:"vg_size"`
Free string `json:"vg_free"`
LvCount string `json:"lv_count"`
PvCount string `json:"pv_count"`
SnapCount string `json:"snap_count"`
} `json:"vg"`
} `json:"report"`
}
// Represents info about logical volume command, lvs, output
type lvsReport struct {
Report []struct {
Lv []struct {
Name string `json:"lv_name"`
VolGroup string `json:"vg_name"`
Size string `json:"lv_size"`
DataPercent string `json:"data_percent"`
MetadataPercent string `json:"metadata_percent"`
} `json:"lv"`
} `json:"report"`
}
func init() {
inputs.Add("lvm", func() telegraf.Input {
return &LVM{
PVSBinary: "/usr/sbin/pvs",
VGSBinary: "/usr/sbin/vgs",
LVSBinary: "/usr/sbin/lvs",
}
})
}