@cryptotaxi247 / netdata-1 / commits / 2a1f4e8ee

add simple collector to monitor lvm thin volumes space usage (#17394)

* add lvs to ndsudo * simple collector to monitor lvm thin volumes space usage * allow comma in ndsudo params * fixes * enable by default * add this pool check for now

Ilya Mashchenko committed Apr 12, 2024 at 20:42 UTC 2a1f4e8ee7ba4cd89a0a05d55029db953c2fc23b
19 files changed +809 -5
src/collectors/plugins.d/ndsudo.c
+10 -2
@@ -13,7 +13,15 @@ struct command {
13 const char *params;
14 const char *search[MAX_SEARCH];
15 } allowed_commands[] = {
16 - {
16 + {
17 + .name = "lvs-report-json",
18 + .params = "--reportformat json --units b --nosuffix -o {{options}}",
19 + .search = {
20 + [0] = "lvs",
21 + [1] = NULL,
22 + },
23 + },
24 + {
25 .name = "igt-json",
26 .params = "-J -s {{interval}}",
27 .search = {
@@ -117,7 +125,7 @@ bool check_string(const char *str, size_t index, char *err, size_t err_size) {
125 if(!((c >= 'A' && c <= 'Z') ||
126 (c >= 'a' && c <= 'z') ||
127 (c >= '0' && c <= '9') ||
120 - c == ' ' || c == '_' || c == '-' || c == '/' || c == '.')) {
128 + c == ' ' || c == '_' || c == '-' || c == '/' || c == '.' || c == ',')) {
129 snprintf(err, err_size, "command line argument No %zu includes invalid character '%c'", index, c);
130 return false;
131 }
src/go/collectors/go.d.plugin/README.md
+1
@@ -85,6 +85,7 @@ see the appropriate collector readme.
85 | [lighttpd](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/lighttpd) | Lighttpd |
86 | [logind](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/logind) | systemd-logind |
87 | [logstash](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/logstash) | Logstash |
88 +| [lvm](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/lvm) | LVM logical volumes |
89 | [mongoDB](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/mongodb) | MongoDB |
90 | [mysql](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/mysql) | MySQL |
91 | [nginx](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/nginx) | NGINX |
src/go/collectors/go.d.plugin/config/go.d.conf
+1
@@ -47,6 +47,7 @@ modules:
47 # lighttpd: yes
48 # logind: yes
49 # logstash: yes
50 +# lvm: yes
51 # mongodb: yes
52 # mysql: yes
53 # nginx: yes
src/go/collectors/go.d.plugin/config/go.d/lvm.conf new
+5
@@ -0,0 +1,5 @@
1 +## All available configuration options, their descriptions and default values:
2 +## https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/lvm#readme
3 +
4 +jobs:
5 + - name: lvm
src/go/collectors/go.d.plugin/modules/init.go
+1
@@ -38,6 +38,7 @@ import (
38 _ "github.com/netdata/netdata/go/go.d.plugin/modules/lighttpd"
39 _ "github.com/netdata/netdata/go/go.d.plugin/modules/logind"
40 _ "github.com/netdata/netdata/go/go.d.plugin/modules/logstash"
41 + _ "github.com/netdata/netdata/go/go.d.plugin/modules/lvm"
42 _ "github.com/netdata/netdata/go/go.d.plugin/modules/mongodb"
43 _ "github.com/netdata/netdata/go/go.d.plugin/modules/mysql"
44 _ "github.com/netdata/netdata/go/go.d.plugin/modules/nginx"
src/go/collectors/go.d.plugin/modules/lvm/charts.go new
+66
@@ -0,0 +1,66 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package lvm
4 +
5 +import (
6 + "fmt"
7 +
8 + "github.com/netdata/netdata/go/go.d.plugin/agent/module"
9 +)
10 +
11 +const (
12 + prioLVDataPercent = 2920 + iota
13 + prioLVMetadataPercent
14 +)
15 +
16 +var lvThinPoolChartsTmpl = module.Charts{
17 + lvDataSpaceUtilizationChartTmpl.Copy(),
18 + lvMetadataSpaceUtilizationChartTmpl.Copy(),
19 +}
20 +
21 +var (
22 + lvDataSpaceUtilizationChartTmpl = module.Chart{
23 + ID: "lv_%s_vg_%s_lv_data_space_utilization",
24 + Title: "Logical volume space allocated for data",
25 + Units: "percentage",
26 + Fam: "lv space usage",
27 + Ctx: "lvm.lv_data_space_utilization",
28 + Type: module.Area,
29 + Priority: prioLVDataPercent,
30 + Dims: module.Dims{
31 + {ID: "lv_%s_vg_%s_data_percent", Name: "utilization", Div: 100},
32 + },
33 + }
34 + lvMetadataSpaceUtilizationChartTmpl = module.Chart{
35 + ID: "lv_%s_vg_%s_lv_metadata_space_utilization",
36 + Title: "Logical volume space allocated for metadata",
37 + Units: "percentage",
38 + Fam: "lv space usage",
39 + Ctx: "lvm.lv_metadata_space_utilization",
40 + Type: module.Area,
41 + Priority: prioLVMetadataPercent,
42 + Dims: module.Dims{
43 + {ID: "lv_%s_vg_%s_metadata_percent", Name: "utilization", Div: 100},
44 + },
45 + }
46 +)
47 +
48 +func (l *LVM) addLVMThinPoolCharts(lvName, vgName string) {
49 + charts := lvThinPoolChartsTmpl.Copy()
50 +
51 + for _, chart := range *charts {
52 + chart.ID = fmt.Sprintf(chart.ID, lvName, vgName)
53 + chart.Labels = []module.Label{
54 + {Key: "lv_name", Value: lvName},
55 + {Key: "vg_name", Value: vgName},
56 + {Key: "volume_type", Value: "thin_pool"},
57 + }
58 + for _, dim := range chart.Dims {
59 + dim.ID = fmt.Sprintf(dim.ID, lvName, vgName)
60 + }
61 + }
62 +
63 + if err := l.Charts().Add(*charts...); err != nil {
64 + l.Warning(err)
65 + }
66 +}
src/go/collectors/go.d.plugin/modules/lvm/collect.go new
+131
@@ -0,0 +1,131 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package lvm
4 +
5 +import (
6 + "encoding/json"
7 + "fmt"
8 + "strconv"
9 +)
10 +
11 +type lvsReport struct {
12 + Report []struct {
13 + Lv []struct {
14 + VGName string `json:"vg_name"`
15 + LVName string `json:"lv_name"`
16 + LVSize string `json:"lv_size"`
17 + DataPercent string `json:"data_percent"`
18 + MetadataPercent string `json:"metadata_percent"`
19 + LVAttr string `json:"lv_attr"`
20 + } `json:"lv"`
21 + } `json:"report"`
22 +}
23 +
24 +func (l *LVM) collect() (map[string]int64, error) {
25 + bs, err := l.exec.lvsReportJson()
26 + if err != nil {
27 + return nil, err
28 + }
29 +
30 + var report lvsReport
31 + if err = json.Unmarshal(bs, &report); err != nil {
32 + return nil, err
33 + }
34 +
35 + mx := make(map[string]int64)
36 +
37 + for _, r := range report.Report {
38 + for _, lv := range r.Lv {
39 + if lv.VGName == "" || lv.LVName == "" {
40 + continue
41 + }
42 +
43 + if !isThinPool(lv.LVAttr) {
44 + l.Debugf("skipping lv '%s' vg '%s': not a thin pool", lv.LVName, lv.VGName)
45 + continue
46 + }
47 +
48 + key := fmt.Sprintf("lv_%s_vg_%s", lv.LVName, lv.VGName)
49 + if !l.lvmThinPools[key] {
50 + l.addLVMThinPoolCharts(lv.LVName, lv.VGName)
51 + l.lvmThinPools[key] = true
52 + }
53 + if v, ok := parseFloat(lv.DataPercent); ok {
54 + mx[key+"_data_percent"] = int64(v * 100)
55 + }
56 + if v, ok := parseFloat(lv.MetadataPercent); ok {
57 + mx[key+"_metadata_percent"] = int64(v * 100)
58 + }
59 + }
60 + }
61 +
62 + return mx, nil
63 +}
64 +
65 +func isThinPool(lvAttr string) bool {
66 + return getLVType(lvAttr) == "thin_pool"
67 +}
68 +
69 +func getLVType(lvAttr string) string {
70 + if len(lvAttr) == 0 {
71 + return ""
72 + }
73 +
74 + // https://man7.org/linux/man-pages/man8/lvs.8.html#NOTES
75 + switch lvAttr[0] {
76 + case 'C':
77 + return "cache"
78 + case 'm':
79 + return "mirrored"
80 + case 'M':
81 + return "mirrored_without_initial_sync"
82 + case 'o':
83 + return "origin"
84 + case 'O':
85 + return "origin_with_merging_snapshot"
86 + case 'g':
87 + return "integrity"
88 + case 'r':
89 + return "raid"
90 + case 'R':
91 + return "raid_without_initial_sync"
92 + case 's':
93 + return "snapshot"
94 + case 'S':
95 + return "merging_snapshot"
96 + case 'p':
97 + return "pvmove"
98 + case 'v':
99 + return "virtual"
100 + case 'i':
101 + return "mirror_or_raid_image"
102 + case 'I':
103 + return "mirror_or_raid_mage_out_of_sync"
104 + case 'l':
105 + return "log_device"
106 + case 'c':
107 + return "under_conversion"
108 + case 'V':
109 + return "thin_volume"
110 + case 't':
111 + return "thin_pool"
112 + case 'T':
113 + return "thin_pool_data"
114 + case 'd':
115 + return "vdo_pool"
116 + case 'D':
117 + return "vdo_pool_data"
118 + case 'e':
119 + return "raid_or_pool_metadata"
120 + default:
121 + return ""
122 + }
123 +}
124 +
125 +func parseFloat(s string) (float64, bool) {
126 + if s == "-" {
127 + return 0, false
128 + }
129 + v, err := strconv.ParseFloat(s, 64)
130 + return v, err == nil
131 +}
src/go/collectors/go.d.plugin/modules/lvm/config_schema.json new
+35
@@ -0,0 +1,35 @@
1 +{
2 + "jsonSchema": {
3 + "$schema": "http://json-schema.org/draft-07/schema#",
4 + "title": "LVM collector configuration.",
5 + "type": "object",
6 + "properties": {
7 + "update_every": {
8 + "title": "Update every",
9 + "description": "Data collection interval, measured in seconds.",
10 + "type": "integer",
11 + "minimum": 1,
12 + "default": 10
13 + },
14 + "timeout": {
15 + "title": "Timeout",
16 + "description": "Timeout for executing the binary, specified in seconds.",
17 + "type": "number",
18 + "minimum": 0.5,
19 + "default": 2
20 + }
21 + },
22 + "additionalProperties": false,
23 + "patternProperties": {
24 + "^name$": {}
25 + }
26 + },
27 + "uiSchema": {
28 + "uiOptions": {
29 + "fullPage": true
30 + },
31 + "timeout": {
32 + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
33 + }
34 + }
35 +}
src/go/collectors/go.d.plugin/modules/lvm/exec.go new
+47
@@ -0,0 +1,47 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package lvm
4 +
5 +import (
6 + "context"
7 + "fmt"
8 + "os/exec"
9 + "time"
10 +
11 + "github.com/netdata/netdata/go/go.d.plugin/logger"
12 +)
13 +
14 +func newLVMCLIExec(ndsudoPath string, timeout time.Duration, log *logger.Logger) *lvmCLIExec {
15 + return &lvmCLIExec{
16 + Logger: log,
17 + ndsudoPath: ndsudoPath,
18 + timeout: timeout,
19 + }
20 +}
21 +
22 +type lvmCLIExec struct {
23 + *logger.Logger
24 +
25 + ndsudoPath string
26 + timeout time.Duration
27 +}
28 +
29 +func (e *lvmCLIExec) lvsReportJson() ([]byte, error) {
30 + ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
31 + defer cancel()
32 +
33 + cmd := exec.CommandContext(ctx,
34 + e.ndsudoPath,
35 + "lvs-report-json",
36 + "--options",
37 + "vg_name,lv_name,lv_size,data_percent,metadata_percent,lv_attr",
38 + )
39 + e.Debugf("executing '%s'", cmd)
40 +
41 + bs, err := cmd.Output()
42 + if err != nil {
43 + return nil, fmt.Errorf("error on '%s': %v", cmd, err)
44 + }
45 +
46 + return bs, nil
47 +}
src/go/collectors/go.d.plugin/modules/lvm/init.go new
+23
@@ -0,0 +1,23 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package lvm
4 +
5 +import (
6 + "fmt"
7 + "os"
8 + "path/filepath"
9 +
10 + "github.com/netdata/netdata/go/go.d.plugin/agent/executable"
11 +)
12 +
13 +func (l *LVM) initLVMCLIExec() (lvmCLI, error) {
14 + ndsudoPath := filepath.Join(executable.Directory, "ndsudo")
15 + if _, err := os.Stat(ndsudoPath); err != nil {
16 + return nil, fmt.Errorf("ndsudo executable not found: %v", err)
17 +
18 + }
19 +
20 + lvmExec := newLVMCLIExec(ndsudoPath, l.Timeout.Duration(), l.Logger)
21 +
22 + return lvmExec, nil
23 +}
src/go/collectors/go.d.plugin/modules/lvm/lvm.go new
+104
@@ -0,0 +1,104 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package lvm
4 +
5 +import (
6 + _ "embed"
7 + "errors"
8 + "time"
9 +
10 + "github.com/netdata/netdata/go/go.d.plugin/agent/module"
11 + "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
12 +)
13 +
14 +//go:embed "config_schema.json"
15 +var configSchema string
16 +
17 +func init() {
18 + module.Register("lvm", module.Creator{
19 + JobConfigSchema: configSchema,
20 + Defaults: module.Defaults{
21 + UpdateEvery: 10,
22 + },
23 + Create: func() module.Module { return New() },
24 + })
25 +}
26 +
27 +func New() *LVM {
28 + return &LVM{
29 + Config: Config{
30 + Timeout: web.Duration(time.Second * 2),
31 + },
32 + charts: &module.Charts{},
33 + lvmThinPools: make(map[string]bool),
34 + }
35 +}
36 +
37 +type Config struct {
38 + UpdateEvery int `yaml:"update_every" json:"update_every"`
39 + Timeout web.Duration `yaml:"timeout" json:"timeout"`
40 +}
41 +
42 +type (
43 + LVM struct {
44 + module.Base
45 + Config `yaml:",inline" json:""`
46 +
47 + charts *module.Charts
48 +
49 + exec lvmCLI
50 +
51 + lvmThinPools map[string]bool
52 + }
53 + lvmCLI interface {
54 + lvsReportJson() ([]byte, error)
55 + }
56 +)
57 +
58 +func (l *LVM) Configuration() any {
59 + return l.Config
60 +}
61 +
62 +func (l *LVM) Init() error {
63 + lvmExec, err := l.initLVMCLIExec()
64 + if err != nil {
65 + l.Errorf("lvm exec initialization: %v", err)
66 + return err
67 + }
68 + l.exec = lvmExec
69 +
70 + return nil
71 +}
72 +
73 +func (l *LVM) Check() error {
74 + mx, err := l.collect()
75 + if err != nil {
76 + l.Error(err)
77 + return err
78 + }
79 +
80 + if len(mx) == 0 {
81 + return errors.New("no metrics collected")
82 + }
83 +
84 + return nil
85 +}
86 +
87 +func (l *LVM) Charts() *module.Charts {
88 + return l.charts
89 +}
90 +
91 +func (l *LVM) Collect() map[string]int64 {
92 + mx, err := l.collect()
93 + if err != nil {
94 + l.Error(err)
95 + }
96 +
97 + if len(mx) == 0 {
98 + return nil
99 + }
100 +
101 + return mx
102 +}
103 +
104 +func (l *LVM) Cleanup() {}
src/go/collectors/go.d.plugin/modules/lvm/lvm_test.go new
+239
@@ -0,0 +1,239 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package lvm
4 +
5 +import (
6 + "errors"
7 + "os"
8 + "testing"
9 +
10 + "github.com/netdata/netdata/go/go.d.plugin/agent/module"
11 +
12 + "github.com/stretchr/testify/assert"
13 + "github.com/stretchr/testify/require"
14 +)
15 +
16 +var (
17 + dataConfigJSON, _ = os.ReadFile("testdata/config.json")
18 + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
19 +
20 + dataLvsReportJson, _ = os.ReadFile("testdata/lvs-report.json")
21 + dataLvsReportNoThinJson, _ = os.ReadFile("testdata/lvs-report-no-thin.json")
22 +)
23 +
24 +func Test_testDataIsValid(t *testing.T) {
25 + for name, data := range map[string][]byte{
26 + "dataConfigJSON": dataConfigJSON,
27 + "dataConfigYAML": dataConfigYAML,
28 +
29 + "dataLvsReportJson": dataLvsReportJson,
30 + "dataLvsReportNoThinJson": dataLvsReportNoThinJson,
31 + } {
32 + require.NotNil(t, data, name)
33 +
34 + }
35 +}
36 +
37 +func TestLVM_Configuration(t *testing.T) {
38 + module.TestConfigurationSerialize(t, &LVM{}, dataConfigJSON, dataConfigYAML)
39 +}
40 +
41 +func TestLVM_Init(t *testing.T) {
42 + tests := map[string]struct {
43 + config Config
44 + wantFail bool
45 + }{
46 + "fails if failed to locate ndsudo": {
47 + wantFail: true,
48 + config: New().Config,
49 + },
50 + }
51 +
52 + for name, test := range tests {
53 + t.Run(name, func(t *testing.T) {
54 + lvm := New()
55 + lvm.Config = test.config
56 +
57 + if test.wantFail {
58 + assert.Error(t, lvm.Init())
59 + } else {
60 + assert.NoError(t, lvm.Init())
61 + }
62 + })
63 + }
64 +
65 +}
66 +
67 +func TestLVM_Cleanup(t *testing.T) {
68 + tests := map[string]struct {
69 + prepare func() *LVM
70 + }{
71 + "not initialized exec": {
72 + prepare: func() *LVM {
73 + return New()
74 + },
75 + },
76 + "after check": {
77 + prepare: func() *LVM {
78 + lvm := New()
79 + lvm.exec = prepareMockOK()
80 + _ = lvm.Check()
81 + return lvm
82 + },
83 + },
84 + "after collect": {
85 + prepare: func() *LVM {
86 + lvm := New()
87 + lvm.exec = prepareMockOK()
88 + _ = lvm.Collect()
89 + return lvm
90 + },
91 + },
92 + }
93 +
94 + for name, test := range tests {
95 + t.Run(name, func(t *testing.T) {
96 + lvm := test.prepare()
97 +
98 + assert.NotPanics(t, lvm.Cleanup)
99 + })
100 + }
101 +}
102 +
103 +func TestLVM_Charts(t *testing.T) {
104 + assert.NotNil(t, New().Charts())
105 +}
106 +
107 +func TestLVM_Check(t *testing.T) {
108 + tests := map[string]struct {
109 + prepareMock func() *mockLvmCliExec
110 + wantFail bool
111 + }{
112 + "success case": {
113 + prepareMock: prepareMockOK,
114 + wantFail: false,
115 + },
116 + "no thin volumes": {
117 + prepareMock: prepareMockNoThinVolumes,
118 + wantFail: true,
119 + },
120 + "error on lvs report call": {
121 + prepareMock: prepareMockErrOnLvsReportJson,
122 + wantFail: true,
123 + },
124 + "empty response": {
125 + prepareMock: prepareMockEmptyResponse,
126 + wantFail: true,
127 + },
128 + "unexpected response": {
129 + prepareMock: prepareMockUnexpectedResponse,
130 + wantFail: true,
131 + },
132 + }
133 +
134 + for name, test := range tests {
135 + t.Run(name, func(t *testing.T) {
136 + lvm := New()
137 + mock := test.prepareMock()
138 + lvm.exec = mock
139 +
140 + if test.wantFail {
141 + assert.Error(t, lvm.Check())
142 + } else {
143 + assert.NoError(t, lvm.Check())
144 + }
145 + })
146 + }
147 +}
148 +
149 +func TestLVM_Collect(t *testing.T) {
150 + tests := map[string]struct {
151 + prepareMock func() *mockLvmCliExec
152 + wantMetrics map[string]int64
153 + }{
154 + "success case": {
155 + prepareMock: prepareMockOK,
156 + wantMetrics: map[string]int64{
157 + "lv_root_vg_cm-vg_data_percent": 7889,
158 + "lv_root_vg_cm-vg_metadata_percent": 1925,
159 + },
160 + },
161 + "no thin volumes": {
162 + prepareMock: prepareMockNoThinVolumes,
163 + wantMetrics: nil,
164 + },
165 + "error on lvs report call": {
166 + prepareMock: prepareMockErrOnLvsReportJson,
167 + wantMetrics: nil,
168 + },
169 + "empty response": {
170 + prepareMock: prepareMockEmptyResponse,
171 + wantMetrics: nil,
172 + },
173 + "unexpected response": {
174 + prepareMock: prepareMockUnexpectedResponse,
175 + wantMetrics: nil,
176 + },
177 + }
178 +
179 + for name, test := range tests {
180 + t.Run(name, func(t *testing.T) {
181 + lvm := New()
182 + mock := test.prepareMock()
183 + lvm.exec = mock
184 +
185 + mx := lvm.Collect()
186 +
187 + assert.Equal(t, test.wantMetrics, mx)
188 + if len(test.wantMetrics) > 0 {
189 + assert.Len(t, *lvm.Charts(), len(lvThinPoolChartsTmpl)*len(lvm.lvmThinPools))
190 + }
191 + })
192 + }
193 +
194 +}
195 +
196 +func prepareMockOK() *mockLvmCliExec {
197 + return &mockLvmCliExec{
198 + lvsReportJsonData: dataLvsReportJson,
199 + }
200 +}
201 +
202 +func prepareMockNoThinVolumes() *mockLvmCliExec {
203 + return &mockLvmCliExec{
204 + lvsReportJsonData: dataLvsReportNoThinJson,
205 + }
206 +}
207 +
208 +func prepareMockErrOnLvsReportJson() *mockLvmCliExec {
209 + return &mockLvmCliExec{
210 + errOnLvsReportJson: true,
211 + }
212 +}
213 +
214 +func prepareMockEmptyResponse() *mockLvmCliExec {
215 + return &mockLvmCliExec{}
216 +}
217 +
218 +func prepareMockUnexpectedResponse() *mockLvmCliExec {
219 + return &mockLvmCliExec{
220 + lvsReportJsonData: []byte(`
221 +Lorem ipsum dolor sit amet, consectetur adipiscing elit.
222 +Nulla malesuada erat id magna mattis, eu viverra tellus rhoncus.
223 +Fusce et felis pulvinar, posuere sem non, porttitor eros.
224 +`),
225 + }
226 +}
227 +
228 +type mockLvmCliExec struct {
229 + errOnLvsReportJson bool
230 + lvsReportJsonData []byte
231 +}
232 +
233 +func (m *mockLvmCliExec) lvsReportJson() ([]byte, error) {
234 + if m.errOnLvsReportJson {
235 + return nil, errors.New("mock.lvsReportJson() error")
236 + }
237 +
238 + return m.lvsReportJsonData, nil
239 +}
src/go/collectors/go.d.plugin/modules/lvm/metadata.yaml new
+107
@@ -0,0 +1,107 @@
1 +plugin_name: go.d.plugin
2 +modules:
3 + - meta:
4 + id: collector-go.d.plugin-lvm
5 + plugin_name: go.d.plugin
6 + module_name: lvm
7 + monitored_instance:
8 + name: LVM logical volumes
9 + link: ""
10 + icon_filename: filesystem.svg
11 + categories:
12 + - data-collection.storage-mount-points-and-filesystems
13 + keywords:
14 + - lvm
15 + - lvs
16 + related_resources:
17 + integrations:
18 + list: []
19 + info_provided_to_referring_integrations:
20 + description: ""
21 + most_popular: false
22 + overview:
23 + data_collection:
24 + metrics_description: >
25 + This collector monitors the health of LVM logical volumes.
26 + It relies on the [`lvs`](https://man7.org/linux/man-pages/man8/lvs.8.html) CLI tool but avoids directly executing the binary.
27 + Instead, it utilizes `ndsudo`, a Netdata helper specifically designed to run privileged commands securely within the Netdata environment.
28 + This approach eliminates the need to use `sudo`, improving security and potentially simplifying permission management.
29 + method_description: ""
30 + supported_platforms:
31 + include: []
32 + exclude: []
33 + multi_instance: false
34 + additional_permissions:
35 + description: ""
36 + default_behavior:
37 + auto_detection:
38 + description: ""
39 + limits:
40 + description: ""
41 + performance_impact:
42 + description: ""
43 + setup:
44 + prerequisites:
45 + list: []
46 + configuration:
47 + file:
48 + name: go.d/lvm.conf
49 + options:
50 + description: |
51 + The following options can be defined globally: update_every.
52 + folding:
53 + title: Config options
54 + enabled: true
55 + list:
56 + - name: update_every
57 + description: Data collection frequency.
58 + default_value: 10
59 + required: false
60 + - name: timeout
61 + description: lvs binary execution timeout.
62 + default_value: 2
63 + required: false
64 + examples:
65 + folding:
66 + title: Config
67 + enabled: true
68 + list:
69 + - name: Custom update_every
70 + description: Allows you to override the default data collection interval.
71 + config: |
72 + jobs:
73 + - name: lvm
74 + update_every: 5 # Collect logical volume statistics every 5 seconds
75 + troubleshooting:
76 + problems:
77 + list: []
78 + alerts: []
79 + metrics:
80 + folding:
81 + title: Metrics
82 + enabled: false
83 + description: ""
84 + availability: []
85 + scopes:
86 + - name: logical volume
87 + description: These metrics refer to the LVM logical volume.
88 + labels:
89 + - name: lv_name
90 + description: Logical volume name
91 + - name: vg_name
92 + description: Volume group name
93 + - name: volume_type
94 + description: Type of the volume
95 + metrics:
96 + - name: lvm.lv_data_space_utilization
97 + description: Logical volume space allocated for data
98 + unit: '%'
99 + chart_type: area
100 + dimensions:
101 + - name: utilization
102 + - name: lvm.lv_metadata_space_utilization
103 + description: Logical volume space allocated for metadata
104 + unit: '%'
105 + chart_type: area
106 + dimensions:
107 + - name: utilization
src/go/collectors/go.d.plugin/modules/lvm/testdata/config.json new
+4
@@ -0,0 +1,4 @@
1 +{
2 + "update_every": 123,
3 + "timeout": 123.123
4 +}
src/go/collectors/go.d.plugin/modules/lvm/testdata/config.yaml new
+2
@@ -0,0 +1,2 @@
1 +update_every: 123
2 +timeout: 123.123
src/go/collectors/go.d.plugin/modules/lvm/testdata/lvs-report-no-thin.json new
+16
@@ -0,0 +1,16 @@
1 +{
2 + "report": [
3 + {
4 + "lv": [
5 + {
6 + "vg_name": "cm-vg",
7 + "lv_name": "root",
8 + "lv_size": "214232465408",
9 + "data_percent": "",
10 + "metadata_percent": "",
11 + "lv_attr": "-wi-ao----"
12 + }
13 + ]
14 + }
15 + ]
16 +}
src/go/collectors/go.d.plugin/modules/lvm/testdata/lvs-report.json new
+16
@@ -0,0 +1,16 @@
1 +{
2 + "report": [
3 + {
4 + "lv": [
5 + {
6 + "vg_name": "cm-vg",
7 + "lv_name": "root",
8 + "lv_size": "214232465408",
9 + "data_percent": "78.89",
10 + "metadata_percent": "19.25",
11 + "lv_attr": "twi-ao----"
12 + }
13 + ]
14 + }
15 + ]
16 +}
src/go/collectors/go.d.plugin/modules/nvme/testdata/config.json
+1 -2
@@ -1,5 +1,4 @@
1 {
2 "update_every": 123,
3 - "timeout": 123.123,
4 - "binary_path": "ok"
3 + "timeout": 123.123
4 }
src/go/collectors/go.d.plugin/modules/nvme/testdata/config.yaml
-1
@@ -1,3 +1,2 @@
1 update_every: 123
2 timeout: 123.123
3 -binary_path: "ok"