diff options
Diffstat (limited to 'src/go/plugin/go.d/modules/lvm')
l--------- | src/go/plugin/go.d/modules/lvm/README.md | 1 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/charts.go | 66 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/collect.go | 131 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/config_schema.json | 35 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/exec.go | 47 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/init.go | 23 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/integrations/lvm_logical_volumes.md | 202 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/lvm.go | 105 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/lvm_test.go | 237 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/metadata.yaml | 115 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/testdata/config.json | 4 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/testdata/config.yaml | 2 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/testdata/lvs-report-no-thin.json | 16 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/lvm/testdata/lvs-report.json | 16 |
14 files changed, 1000 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/lvm/README.md b/src/go/plugin/go.d/modules/lvm/README.md new file mode 120000 index 00000000..9b86695a --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/README.md @@ -0,0 +1 @@ +integrations/lvm_logical_volumes.md
\ No newline at end of file diff --git a/src/go/plugin/go.d/modules/lvm/charts.go b/src/go/plugin/go.d/modules/lvm/charts.go new file mode 100644 index 00000000..8d2f0fa1 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/charts.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lvm + +import ( + "fmt" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" +) + +const ( + prioLVDataPercent = 2920 + iota + prioLVMetadataPercent +) + +var lvThinPoolChartsTmpl = module.Charts{ + lvDataSpaceUtilizationChartTmpl.Copy(), + lvMetadataSpaceUtilizationChartTmpl.Copy(), +} + +var ( + lvDataSpaceUtilizationChartTmpl = module.Chart{ + ID: "lv_%s_vg_%s_lv_data_space_utilization", + Title: "Logical volume space allocated for data", + Units: "percentage", + Fam: "lv space usage", + Ctx: "lvm.lv_data_space_utilization", + Type: module.Area, + Priority: prioLVDataPercent, + Dims: module.Dims{ + {ID: "lv_%s_vg_%s_data_percent", Name: "utilization", Div: 100}, + }, + } + lvMetadataSpaceUtilizationChartTmpl = module.Chart{ + ID: "lv_%s_vg_%s_lv_metadata_space_utilization", + Title: "Logical volume space allocated for metadata", + Units: "percentage", + Fam: "lv space usage", + Ctx: "lvm.lv_metadata_space_utilization", + Type: module.Area, + Priority: prioLVMetadataPercent, + Dims: module.Dims{ + {ID: "lv_%s_vg_%s_metadata_percent", Name: "utilization", Div: 100}, + }, + } +) + +func (l *LVM) addLVMThinPoolCharts(lvName, vgName string) { + charts := lvThinPoolChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, lvName, vgName) + chart.Labels = []module.Label{ + {Key: "lv_name", Value: lvName}, + {Key: "vg_name", Value: vgName}, + {Key: "volume_type", Value: "thin_pool"}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, lvName, vgName) + } + } + + if err := l.Charts().Add(*charts...); err != nil { + l.Warning(err) + } +} diff --git a/src/go/plugin/go.d/modules/lvm/collect.go b/src/go/plugin/go.d/modules/lvm/collect.go new file mode 100644 index 00000000..8f57a1a8 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/collect.go @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lvm + +import ( + "encoding/json" + "fmt" + "strconv" +) + +type lvsReport struct { + Report []struct { + Lv []struct { + VGName string `json:"vg_name"` + LVName string `json:"lv_name"` + LVSize string `json:"lv_size"` + DataPercent string `json:"data_percent"` + MetadataPercent string `json:"metadata_percent"` + LVAttr string `json:"lv_attr"` + } `json:"lv"` + } `json:"report"` +} + +func (l *LVM) collect() (map[string]int64, error) { + bs, err := l.exec.lvsReportJson() + if err != nil { + return nil, err + } + + var report lvsReport + if err = json.Unmarshal(bs, &report); err != nil { + return nil, err + } + + mx := make(map[string]int64) + + for _, r := range report.Report { + for _, lv := range r.Lv { + if lv.VGName == "" || lv.LVName == "" { + continue + } + + if !isThinPool(lv.LVAttr) { + l.Debugf("skipping lv '%s' vg '%s': not a thin pool", lv.LVName, lv.VGName) + continue + } + + key := fmt.Sprintf("lv_%s_vg_%s", lv.LVName, lv.VGName) + if !l.lvmThinPools[key] { + l.addLVMThinPoolCharts(lv.LVName, lv.VGName) + l.lvmThinPools[key] = true + } + if v, ok := parseFloat(lv.DataPercent); ok { + mx[key+"_data_percent"] = int64(v * 100) + } + if v, ok := parseFloat(lv.MetadataPercent); ok { + mx[key+"_metadata_percent"] = int64(v * 100) + } + } + } + + return mx, nil +} + +func isThinPool(lvAttr string) bool { + return getLVType(lvAttr) == "thin_pool" +} + +func getLVType(lvAttr string) string { + if len(lvAttr) == 0 { + return "" + } + + // https://man7.org/linux/man-pages/man8/lvs.8.html#NOTES + switch lvAttr[0] { + case 'C': + return "cache" + case 'm': + return "mirrored" + case 'M': + return "mirrored_without_initial_sync" + case 'o': + return "origin" + case 'O': + return "origin_with_merging_snapshot" + case 'g': + return "integrity" + case 'r': + return "raid" + case 'R': + return "raid_without_initial_sync" + case 's': + return "snapshot" + case 'S': + return "merging_snapshot" + case 'p': + return "pvmove" + case 'v': + return "virtual" + case 'i': + return "mirror_or_raid_image" + case 'I': + return "mirror_or_raid_mage_out_of_sync" + case 'l': + return "log_device" + case 'c': + return "under_conversion" + case 'V': + return "thin_volume" + case 't': + return "thin_pool" + case 'T': + return "thin_pool_data" + case 'd': + return "vdo_pool" + case 'D': + return "vdo_pool_data" + case 'e': + return "raid_or_pool_metadata" + default: + return "" + } +} + +func parseFloat(s string) (float64, bool) { + if s == "-" { + return 0, false + } + v, err := strconv.ParseFloat(s, 64) + return v, err == nil +} diff --git a/src/go/plugin/go.d/modules/lvm/config_schema.json b/src/go/plugin/go.d/modules/lvm/config_schema.json new file mode 100644 index 00000000..1e078807 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/config_schema.json @@ -0,0 +1,35 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LVM collector configuration.", + "type": "object", + "properties": { + "update_every": { + "title": "Update every", + "description": "Data collection interval, measured in seconds.", + "type": "integer", + "minimum": 1, + "default": 10 + }, + "timeout": { + "title": "Timeout", + "description": "Timeout for executing the binary, specified in seconds.", + "type": "number", + "minimum": 0.5, + "default": 2 + } + }, + "additionalProperties": false, + "patternProperties": { + "^name$": {} + } + }, + "uiSchema": { + "uiOptions": { + "fullPage": true + }, + "timeout": { + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)." + } + } +} diff --git a/src/go/plugin/go.d/modules/lvm/exec.go b/src/go/plugin/go.d/modules/lvm/exec.go new file mode 100644 index 00000000..66863a05 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/exec.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lvm + +import ( + "context" + "fmt" + "os/exec" + "time" + + "github.com/netdata/netdata/go/plugins/logger" +) + +func newLVMCLIExec(ndsudoPath string, timeout time.Duration, log *logger.Logger) *lvmCLIExec { + return &lvmCLIExec{ + Logger: log, + ndsudoPath: ndsudoPath, + timeout: timeout, + } +} + +type lvmCLIExec struct { + *logger.Logger + + ndsudoPath string + timeout time.Duration +} + +func (e *lvmCLIExec) lvsReportJson() ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), e.timeout) + defer cancel() + + cmd := exec.CommandContext(ctx, + e.ndsudoPath, + "lvs-report-json", + "--options", + "vg_name,lv_name,lv_size,data_percent,metadata_percent,lv_attr", + ) + e.Debugf("executing '%s'", cmd) + + bs, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("error on '%s': %v", cmd, err) + } + + return bs, nil +} diff --git a/src/go/plugin/go.d/modules/lvm/init.go b/src/go/plugin/go.d/modules/lvm/init.go new file mode 100644 index 00000000..5c4db1ad --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/init.go @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lvm + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/netdata/netdata/go/plugins/pkg/executable" +) + +func (l *LVM) initLVMCLIExec() (lvmCLI, error) { + ndsudoPath := filepath.Join(executable.Directory, "ndsudo") + if _, err := os.Stat(ndsudoPath); err != nil { + return nil, fmt.Errorf("ndsudo executable not found: %v", err) + + } + + lvmExec := newLVMCLIExec(ndsudoPath, l.Timeout.Duration(), l.Logger) + + return lvmExec, nil +} diff --git a/src/go/plugin/go.d/modules/lvm/integrations/lvm_logical_volumes.md b/src/go/plugin/go.d/modules/lvm/integrations/lvm_logical_volumes.md new file mode 100644 index 00000000..1d76c363 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/integrations/lvm_logical_volumes.md @@ -0,0 +1,202 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/lvm/README.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/lvm/metadata.yaml" +sidebar_label: "LVM logical volumes" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/Storage, Mount Points and Filesystems" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# LVM logical volumes + + +<img src="https://netdata.cloud/img/filesystem.svg" width="150"/> + + +Plugin: go.d.plugin +Module: lvm + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +This collector monitors the health of LVM logical volumes. It relies on the [`lvs`](https://man7.org/linux/man-pages/man8/lvs.8.html) CLI tool but avoids directly executing the binary. Instead, it utilizes `ndsudo`, a Netdata helper specifically designed to run privileged commands securely within the Netdata environment. This approach eliminates the need to use `sudo`, improving security and potentially simplifying permission management. + + + + +This collector is supported on all platforms. + +This collector only supports collecting metrics from a single instance of this integration. + + +### Default Behavior + +#### Auto-Detection + +This integration doesn't support auto-detection. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +The default configuration for this integration is not expected to impose a significant performance impact on the system. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per logical volume + +These metrics refer to the LVM logical volume. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| lv_name | Logical volume name | +| vg_name | Volume group name | +| volume_type | Type of the volume | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| lvm.lv_data_space_utilization | utilization | % | +| lvm.lv_metadata_space_utilization | utilization | % | + + + +## Alerts + + +The following alerts are available: + +| Alert name | On metric | Description | +|:------------|:----------|:------------| +| [ lvm_lv_data_space_utilization ](https://github.com/netdata/netdata/blob/master/src/health/health.d/lvm.conf) | lvm.lv_data_space_utilization | LVM logical volume high data space usage (LV ${label:lv_name} VG ${label:vg_name} Type ${label:volume_type}) | +| [ lvm_lv_metadata_space_utilization ](https://github.com/netdata/netdata/blob/master/src/health/health.d/lvm.conf) | lvm.lv_metadata_space_utilization | LVM logical volume high metadata space usage (LV ${label:lv_name} VG ${label:vg_name} Type ${label:volume_type}) | + + +## Setup + +### Prerequisites + +No action required. + +### Configuration + +#### File + +The configuration file name for this integration is `go.d/lvm.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config go.d/lvm.conf +``` +#### Options + +The following options can be defined globally: update_every. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update_every | Data collection frequency. | 10 | no | +| timeout | lvs binary execution timeout. | 2 | no | + +</details> + +#### Examples + +##### Custom update_every + +Allows you to override the default data collection interval. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: lvm + update_every: 5 # Collect logical volume statistics every 5 seconds + +``` +</details> + + + +## Troubleshooting + +### Debug Mode + +**Important**: Debug mode is not supported for data collection jobs created via the UI using the Dyncfg feature. + +To troubleshoot issues with the `lvm` collector, run the `go.d.plugin` with the debug option enabled. The output +should give you clues as to why the collector isn't working. + +- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on + your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`. + + ```bash + cd /usr/libexec/netdata/plugins.d/ + ``` + +- Switch to the `netdata` user. + + ```bash + sudo -u netdata -s + ``` + +- Run the `go.d.plugin` to debug the collector: + + ```bash + ./go.d.plugin -d -m lvm + ``` + +### Getting Logs + +If you're encountering problems with the `lvm` collector, follow these steps to retrieve logs and identify potential issues: + +- **Run the command** specific to your system (systemd, non-systemd, or Docker container). +- **Examine the output** for any warnings or error messages that might indicate issues. These messages should provide clues about the root cause of the problem. + +#### System with systemd + +Use the following command to view logs generated since the last Netdata service restart: + +```bash +journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep lvm +``` + +#### System without systemd + +Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name: + +```bash +grep lvm /var/log/netdata/collector.log +``` + +**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues. + +#### Docker Container + +If your Netdata runs in a Docker container named "netdata" (replace if different), use this command: + +```bash +docker logs netdata 2>&1 | grep lvm +``` + + diff --git a/src/go/plugin/go.d/modules/lvm/lvm.go b/src/go/plugin/go.d/modules/lvm/lvm.go new file mode 100644 index 00000000..c6754e06 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/lvm.go @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lvm + +import ( + _ "embed" + "errors" + "time" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("lvm", module.Creator{ + JobConfigSchema: configSchema, + Defaults: module.Defaults{ + UpdateEvery: 10, + }, + Create: func() module.Module { return New() }, + Config: func() any { return &Config{} }, + }) +} + +func New() *LVM { + return &LVM{ + Config: Config{ + Timeout: web.Duration(time.Second * 2), + }, + charts: &module.Charts{}, + lvmThinPools: make(map[string]bool), + } +} + +type Config struct { + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"` + Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"` +} + +type ( + LVM struct { + module.Base + Config `yaml:",inline" json:""` + + charts *module.Charts + + exec lvmCLI + + lvmThinPools map[string]bool + } + lvmCLI interface { + lvsReportJson() ([]byte, error) + } +) + +func (l *LVM) Configuration() any { + return l.Config +} + +func (l *LVM) Init() error { + lvmExec, err := l.initLVMCLIExec() + if err != nil { + l.Errorf("lvm exec initialization: %v", err) + return err + } + l.exec = lvmExec + + return nil +} + +func (l *LVM) Check() error { + mx, err := l.collect() + if err != nil { + l.Error(err) + return err + } + + if len(mx) == 0 { + return errors.New("no metrics collected") + } + + return nil +} + +func (l *LVM) Charts() *module.Charts { + return l.charts +} + +func (l *LVM) Collect() map[string]int64 { + mx, err := l.collect() + if err != nil { + l.Error(err) + } + + if len(mx) == 0 { + return nil + } + + return mx +} + +func (l *LVM) Cleanup() {} diff --git a/src/go/plugin/go.d/modules/lvm/lvm_test.go b/src/go/plugin/go.d/modules/lvm/lvm_test.go new file mode 100644 index 00000000..a3c07283 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/lvm_test.go @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lvm + +import ( + "errors" + "os" + "testing" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + dataConfigJSON, _ = os.ReadFile("testdata/config.json") + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") + + dataLvsReportJson, _ = os.ReadFile("testdata/lvs-report.json") + dataLvsReportNoThinJson, _ = os.ReadFile("testdata/lvs-report-no-thin.json") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + + "dataLvsReportJson": dataLvsReportJson, + "dataLvsReportNoThinJson": dataLvsReportNoThinJson, + } { + require.NotNil(t, data, name) + + } +} + +func TestLVM_Configuration(t *testing.T) { + module.TestConfigurationSerialize(t, &LVM{}, dataConfigJSON, dataConfigYAML) +} + +func TestLVM_Init(t *testing.T) { + tests := map[string]struct { + config Config + wantFail bool + }{ + "fails if failed to locate ndsudo": { + wantFail: true, + config: New().Config, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + lvm := New() + lvm.Config = test.config + + if test.wantFail { + assert.Error(t, lvm.Init()) + } else { + assert.NoError(t, lvm.Init()) + } + }) + } +} + +func TestLVM_Cleanup(t *testing.T) { + tests := map[string]struct { + prepare func() *LVM + }{ + "not initialized exec": { + prepare: func() *LVM { + return New() + }, + }, + "after check": { + prepare: func() *LVM { + lvm := New() + lvm.exec = prepareMockOK() + _ = lvm.Check() + return lvm + }, + }, + "after collect": { + prepare: func() *LVM { + lvm := New() + lvm.exec = prepareMockOK() + _ = lvm.Collect() + return lvm + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + lvm := test.prepare() + + assert.NotPanics(t, lvm.Cleanup) + }) + } +} + +func TestLVM_Charts(t *testing.T) { + assert.NotNil(t, New().Charts()) +} + +func TestLVM_Check(t *testing.T) { + tests := map[string]struct { + prepareMock func() *mockLvmCliExec + wantFail bool + }{ + "success case": { + prepareMock: prepareMockOK, + wantFail: false, + }, + "no thin volumes": { + prepareMock: prepareMockNoThinVolumes, + wantFail: true, + }, + "error on lvs report call": { + prepareMock: prepareMockErrOnLvsReportJson, + wantFail: true, + }, + "empty response": { + prepareMock: prepareMockEmptyResponse, + wantFail: true, + }, + "unexpected response": { + prepareMock: prepareMockUnexpectedResponse, + wantFail: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + lvm := New() + mock := test.prepareMock() + lvm.exec = mock + + if test.wantFail { + assert.Error(t, lvm.Check()) + } else { + assert.NoError(t, lvm.Check()) + } + }) + } +} + +func TestLVM_Collect(t *testing.T) { + tests := map[string]struct { + prepareMock func() *mockLvmCliExec + wantMetrics map[string]int64 + }{ + "success case": { + prepareMock: prepareMockOK, + wantMetrics: map[string]int64{ + "lv_root_vg_cm-vg_data_percent": 7889, + "lv_root_vg_cm-vg_metadata_percent": 1925, + }, + }, + "no thin volumes": { + prepareMock: prepareMockNoThinVolumes, + wantMetrics: nil, + }, + "error on lvs report call": { + prepareMock: prepareMockErrOnLvsReportJson, + wantMetrics: nil, + }, + "empty response": { + prepareMock: prepareMockEmptyResponse, + wantMetrics: nil, + }, + "unexpected response": { + prepareMock: prepareMockUnexpectedResponse, + wantMetrics: nil, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + lvm := New() + mock := test.prepareMock() + lvm.exec = mock + + mx := lvm.Collect() + + assert.Equal(t, test.wantMetrics, mx) + if len(test.wantMetrics) > 0 { + assert.Len(t, *lvm.Charts(), len(lvThinPoolChartsTmpl)*len(lvm.lvmThinPools)) + } + }) + } +} + +func prepareMockOK() *mockLvmCliExec { + return &mockLvmCliExec{ + lvsReportJsonData: dataLvsReportJson, + } +} + +func prepareMockNoThinVolumes() *mockLvmCliExec { + return &mockLvmCliExec{ + lvsReportJsonData: dataLvsReportNoThinJson, + } +} + +func prepareMockErrOnLvsReportJson() *mockLvmCliExec { + return &mockLvmCliExec{ + errOnLvsReportJson: true, + } +} + +func prepareMockEmptyResponse() *mockLvmCliExec { + return &mockLvmCliExec{} +} + +func prepareMockUnexpectedResponse() *mockLvmCliExec { + return &mockLvmCliExec{ + lvsReportJsonData: []byte(` +Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Nulla malesuada erat id magna mattis, eu viverra tellus rhoncus. +Fusce et felis pulvinar, posuere sem non, porttitor eros. +`), + } +} + +type mockLvmCliExec struct { + errOnLvsReportJson bool + lvsReportJsonData []byte +} + +func (m *mockLvmCliExec) lvsReportJson() ([]byte, error) { + if m.errOnLvsReportJson { + return nil, errors.New("mock.lvsReportJson() error") + } + + return m.lvsReportJsonData, nil +} diff --git a/src/go/plugin/go.d/modules/lvm/metadata.yaml b/src/go/plugin/go.d/modules/lvm/metadata.yaml new file mode 100644 index 00000000..46d03694 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/metadata.yaml @@ -0,0 +1,115 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-lvm + plugin_name: go.d.plugin + module_name: lvm + monitored_instance: + name: LVM logical volumes + link: "" + icon_filename: filesystem.svg + categories: + - data-collection.storage-mount-points-and-filesystems + keywords: + - lvm + - lvs + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + most_popular: false + overview: + data_collection: + metrics_description: > + This collector monitors the health of LVM logical volumes. + It relies on the [`lvs`](https://man7.org/linux/man-pages/man8/lvs.8.html) CLI tool but avoids directly executing the binary. + Instead, it utilizes `ndsudo`, a Netdata helper specifically designed to run privileged commands securely within the Netdata environment. + This approach eliminates the need to use `sudo`, improving security and potentially simplifying permission management. + method_description: "" + supported_platforms: + include: [] + exclude: [] + multi_instance: false + additional_permissions: + description: "" + default_behavior: + auto_detection: + description: "" + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: [] + configuration: + file: + name: go.d/lvm.conf + options: + description: | + The following options can be defined globally: update_every. + folding: + title: Config options + enabled: true + list: + - name: update_every + description: Data collection frequency. + default_value: 10 + required: false + - name: timeout + description: lvs binary execution timeout. + default_value: 2 + required: false + examples: + folding: + title: Config + enabled: true + list: + - name: Custom update_every + description: Allows you to override the default data collection interval. + config: | + jobs: + - name: lvm + update_every: 5 # Collect logical volume statistics every 5 seconds + troubleshooting: + problems: + list: [] + alerts: + - name: lvm_lv_data_space_utilization + metric: lvm.lv_data_space_utilization + info: LVM logical volume high data space usage (LV ${label:lv_name} VG ${label:vg_name} Type ${label:volume_type}) + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/lvm.conf + - name: lvm_lv_metadata_space_utilization + metric: lvm.lv_metadata_space_utilization + info: LVM logical volume high metadata space usage (LV ${label:lv_name} VG ${label:vg_name} Type ${label:volume_type}) + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/lvm.conf + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: logical volume + description: These metrics refer to the LVM logical volume. + labels: + - name: lv_name + description: Logical volume name + - name: vg_name + description: Volume group name + - name: volume_type + description: Type of the volume + metrics: + - name: lvm.lv_data_space_utilization + description: Logical volume space allocated for data + unit: '%' + chart_type: area + dimensions: + - name: utilization + - name: lvm.lv_metadata_space_utilization + description: Logical volume space allocated for metadata + unit: '%' + chart_type: area + dimensions: + - name: utilization diff --git a/src/go/plugin/go.d/modules/lvm/testdata/config.json b/src/go/plugin/go.d/modules/lvm/testdata/config.json new file mode 100644 index 00000000..291ecee3 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/testdata/config.json @@ -0,0 +1,4 @@ +{ + "update_every": 123, + "timeout": 123.123 +} diff --git a/src/go/plugin/go.d/modules/lvm/testdata/config.yaml b/src/go/plugin/go.d/modules/lvm/testdata/config.yaml new file mode 100644 index 00000000..25b0b4c7 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/testdata/config.yaml @@ -0,0 +1,2 @@ +update_every: 123 +timeout: 123.123 diff --git a/src/go/plugin/go.d/modules/lvm/testdata/lvs-report-no-thin.json b/src/go/plugin/go.d/modules/lvm/testdata/lvs-report-no-thin.json new file mode 100644 index 00000000..1fe8ec44 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/testdata/lvs-report-no-thin.json @@ -0,0 +1,16 @@ +{ + "report": [ + { + "lv": [ + { + "vg_name": "cm-vg", + "lv_name": "root", + "lv_size": "214232465408", + "data_percent": "", + "metadata_percent": "", + "lv_attr": "-wi-ao----" + } + ] + } + ] +} diff --git a/src/go/plugin/go.d/modules/lvm/testdata/lvs-report.json b/src/go/plugin/go.d/modules/lvm/testdata/lvs-report.json new file mode 100644 index 00000000..bd04fad7 --- /dev/null +++ b/src/go/plugin/go.d/modules/lvm/testdata/lvs-report.json @@ -0,0 +1,16 @@ +{ + "report": [ + { + "lv": [ + { + "vg_name": "cm-vg", + "lv_name": "root", + "lv_size": "214232465408", + "data_percent": "78.89", + "metadata_percent": "19.25", + "lv_attr": "twi-ao----" + } + ] + } + ] +} |