go.d/powerstore: add Dell PowerStore storage array collector (V2 framework) (#21929)
Costa Tsaousis committed
Mar 19, 2026 at 00:12 UTC
fa04535fea7a646342ed338f98404fc7e64da8e2
47 files changed
+3683
src/go/plugin/go.d/collector/init.go
+1
@@ -90,6 +90,7 @@ import (
90
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/postgres"
91
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/powerdns"
92
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/powerdns_recursor"
93
+ _ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/powerstore"
94
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/prometheus"
95
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/proxysql"
96
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/pulsar"
src/go/plugin/go.d/collector/powerstore/charts.yaml
new
+789
@@ -0,0 +1,789 @@
1
+version: v1
2
+context_namespace: powerstore
3
+groups:
4
+ - family: Cluster Capacity
5
+ metrics:
6
+ - cluster_space_physical_total
7
+ - cluster_space_physical_used
8
+ - cluster_space_logical_provisioned
9
+ - cluster_space_logical_used
10
+ - cluster_space_data_physical_used
11
+ - cluster_space_shared_logical_used
12
+ - cluster_space_efficiency_ratio
13
+ - cluster_space_data_reduction
14
+ - cluster_space_snapshot_savings
15
+ - cluster_space_thin_savings
16
+ charts:
17
+ - id: cluster_space_usage
18
+ title: Cluster Space Usage
19
+ context: cluster_space_usage
20
+ units: bytes
21
+ algorithm: absolute
22
+ dimensions:
23
+ - selector: cluster_space_physical_used
24
+ name: used
25
+ - selector: cluster_space_physical_total
26
+ name: total
27
+ - id: cluster_space_logical
28
+ title: Cluster Logical Space
29
+ context: cluster_space_logical
30
+ units: bytes
31
+ algorithm: absolute
32
+ dimensions:
33
+ - selector: cluster_space_logical_provisioned
34
+ name: provisioned
35
+ - selector: cluster_space_logical_used
36
+ name: used
37
+ - selector: cluster_space_data_physical_used
38
+ name: data_physical
39
+ - selector: cluster_space_shared_logical_used
40
+ name: shared
41
+ - id: cluster_space_efficiency
42
+ title: Cluster Space Efficiency Ratios
43
+ context: cluster_space_efficiency
44
+ units: ratio
45
+ algorithm: absolute
46
+ dimensions:
47
+ - selector: cluster_space_efficiency_ratio
48
+ name: efficiency
49
+ options:
50
+ float: true
51
+ - selector: cluster_space_data_reduction
52
+ name: data_reduction
53
+ options:
54
+ float: true
55
+ - selector: cluster_space_snapshot_savings
56
+ name: snapshot_savings
57
+ options:
58
+ float: true
59
+ - selector: cluster_space_thin_savings
60
+ name: thin_savings
61
+ options:
62
+ float: true
63
+
64
+ - family: Hardware Health
65
+ metrics:
66
+ - hardware_fan_ok
67
+ - hardware_fan_degraded
68
+ - hardware_fan_failed
69
+ - hardware_fan_unknown
70
+ - hardware_psu_ok
71
+ - hardware_psu_degraded
72
+ - hardware_psu_failed
73
+ - hardware_psu_unknown
74
+ - hardware_drive_ok
75
+ - hardware_drive_degraded
76
+ - hardware_drive_failed
77
+ - hardware_drive_unknown
78
+ - hardware_battery_ok
79
+ - hardware_battery_degraded
80
+ - hardware_battery_failed
81
+ - hardware_battery_unknown
82
+ - hardware_node_ok
83
+ - hardware_node_degraded
84
+ - hardware_node_failed
85
+ - hardware_node_unknown
86
+ charts:
87
+ - id: hardware_health_fan
88
+ title: Fan Health Status
89
+ context: hardware_health_fan
90
+ units: fans
91
+ type: stacked
92
+ algorithm: absolute
93
+ dimensions:
94
+ - selector: hardware_fan_ok
95
+ name: ok
96
+ - selector: hardware_fan_degraded
97
+ name: degraded
98
+ - selector: hardware_fan_failed
99
+ name: failed
100
+ - selector: hardware_fan_unknown
101
+ name: unknown
102
+ - id: hardware_health_psu
103
+ title: Power Supply Health Status
104
+ context: hardware_health_psu
105
+ units: PSUs
106
+ type: stacked
107
+ algorithm: absolute
108
+ dimensions:
109
+ - selector: hardware_psu_ok
110
+ name: ok
111
+ - selector: hardware_psu_degraded
112
+ name: degraded
113
+ - selector: hardware_psu_failed
114
+ name: failed
115
+ - selector: hardware_psu_unknown
116
+ name: unknown
117
+ - id: hardware_health_drive
118
+ title: Drive Health Status
119
+ context: hardware_health_drive
120
+ units: drives
121
+ type: stacked
122
+ algorithm: absolute
123
+ dimensions:
124
+ - selector: hardware_drive_ok
125
+ name: ok
126
+ - selector: hardware_drive_degraded
127
+ name: degraded
128
+ - selector: hardware_drive_failed
129
+ name: failed
130
+ - selector: hardware_drive_unknown
131
+ name: unknown
132
+ - id: hardware_health_battery
133
+ title: Battery Health Status
134
+ context: hardware_health_battery
135
+ units: batteries
136
+ type: stacked
137
+ algorithm: absolute
138
+ dimensions:
139
+ - selector: hardware_battery_ok
140
+ name: ok
141
+ - selector: hardware_battery_degraded
142
+ name: degraded
143
+ - selector: hardware_battery_failed
144
+ name: failed
145
+ - selector: hardware_battery_unknown
146
+ name: unknown
147
+ - id: hardware_health_node
148
+ title: Node Health Status
149
+ context: hardware_health_node
150
+ units: nodes
151
+ type: stacked
152
+ algorithm: absolute
153
+ dimensions:
154
+ - selector: hardware_node_ok
155
+ name: ok
156
+ - selector: hardware_node_degraded
157
+ name: degraded
158
+ - selector: hardware_node_failed
159
+ name: failed
160
+ - selector: hardware_node_unknown
161
+ name: unknown
162
+
163
+ - family: Alerts
164
+ metrics:
165
+ - alerts_critical
166
+ - alerts_major
167
+ - alerts_minor
168
+ - alerts_info
169
+ charts:
170
+ - id: alerts_active
171
+ title: Active Alerts by Severity
172
+ context: alerts_active
173
+ units: alerts
174
+ type: stacked
175
+ algorithm: absolute
176
+ dimensions:
177
+ - selector: alerts_critical
178
+ name: critical
179
+ - selector: alerts_major
180
+ name: major
181
+ - selector: alerts_minor
182
+ name: minor
183
+ - selector: alerts_info
184
+ name: info
185
+
186
+ - family: NAS
187
+ metrics:
188
+ - nas_started
189
+ - nas_stopped
190
+ - nas_degraded
191
+ - nas_unknown
192
+ charts:
193
+ - id: nas_server_status
194
+ title: NAS Server Status
195
+ context: nas_server_status
196
+ units: servers
197
+ type: stacked
198
+ algorithm: absolute
199
+ dimensions:
200
+ - selector: nas_started
201
+ name: started
202
+ - selector: nas_stopped
203
+ name: stopped
204
+ - selector: nas_degraded
205
+ name: degraded
206
+ - selector: nas_unknown
207
+ name: unknown
208
+
209
+ - family: Replication
210
+ metrics:
211
+ - copy_data_remaining
212
+ - copy_data_transferred
213
+ - copy_transfer_rate
214
+ charts:
215
+ - id: copy_data
216
+ title: Replication Data
217
+ context: copy_data
218
+ units: bytes
219
+ algorithm: absolute
220
+ dimensions:
221
+ - selector: copy_data_remaining
222
+ name: remaining
223
+ - selector: copy_data_transferred
224
+ name: transferred
225
+ - id: copy_transfer_rate
226
+ title: Replication Transfer Rate
227
+ context: copy_transfer_rate
228
+ units: bytes/s
229
+ algorithm: absolute
230
+ dimensions:
231
+ - selector: copy_transfer_rate
232
+ name: rate
233
+ options:
234
+ float: true
235
+
236
+ - family: Appliance
237
+ metrics:
238
+ - appliance_perf_read_iops
239
+ - appliance_perf_write_iops
240
+ - appliance_perf_read_bandwidth
241
+ - appliance_perf_write_bandwidth
242
+ - appliance_perf_avg_read_latency
243
+ - appliance_perf_avg_write_latency
244
+ - appliance_perf_avg_latency
245
+ - appliance_cpu_utilization
246
+ - appliance_space_physical_used
247
+ - appliance_space_physical_total
248
+ - appliance_space_logical_provisioned
249
+ - appliance_space_logical_used
250
+ - appliance_space_data_physical_used
251
+ - appliance_space_shared_logical_used
252
+ - appliance_space_efficiency_ratio
253
+ - appliance_space_data_reduction
254
+ - appliance_space_snapshot_savings
255
+ - appliance_space_thin_savings
256
+ charts:
257
+ - id: appliance_iops
258
+ title: Appliance IOPS
259
+ context: appliance_iops
260
+ units: ops/s
261
+ type: area
262
+ algorithm: absolute
263
+ instances:
264
+ by_labels: [appliance]
265
+ dimensions:
266
+ - selector: appliance_perf_read_iops
267
+ name: read
268
+ options:
269
+ float: true
270
+ - selector: appliance_perf_write_iops
271
+ name: write
272
+ options:
273
+ multiplier: -1
274
+ float: true
275
+ - id: appliance_bandwidth
276
+ title: Appliance Bandwidth
277
+ context: appliance_bandwidth
278
+ units: bytes/s
279
+ type: area
280
+ algorithm: absolute
281
+ instances:
282
+ by_labels: [appliance]
283
+ dimensions:
284
+ - selector: appliance_perf_read_bandwidth
285
+ name: read
286
+ options:
287
+ float: true
288
+ - selector: appliance_perf_write_bandwidth
289
+ name: write
290
+ options:
291
+ multiplier: -1
292
+ float: true
293
+ - id: appliance_latency
294
+ title: Appliance Latency
295
+ context: appliance_latency
296
+ units: microseconds
297
+ algorithm: absolute
298
+ instances:
299
+ by_labels: [appliance]
300
+ dimensions:
301
+ - selector: appliance_perf_avg_read_latency
302
+ name: read
303
+ options:
304
+ float: true
305
+ - selector: appliance_perf_avg_write_latency
306
+ name: write
307
+ options:
308
+ float: true
309
+ - selector: appliance_perf_avg_latency
310
+ name: avg
311
+ options:
312
+ float: true
313
+ - id: appliance_cpu
314
+ title: Appliance IO Workload CPU Utilization
315
+ context: appliance_cpu
316
+ units: percentage
317
+ algorithm: absolute
318
+ instances:
319
+ by_labels: [appliance]
320
+ dimensions:
321
+ - selector: appliance_cpu_utilization
322
+ name: utilization
323
+ options:
324
+ float: true
325
+ - id: appliance_space
326
+ title: Appliance Space Usage
327
+ context: appliance_space
328
+ units: bytes
329
+ algorithm: absolute
330
+ instances:
331
+ by_labels: [appliance]
332
+ dimensions:
333
+ - selector: appliance_space_physical_used
334
+ name: used
335
+ - selector: appliance_space_physical_total
336
+ name: total
337
+ - id: appliance_space_logical
338
+ title: Appliance Logical Space
339
+ context: appliance_space_logical
340
+ units: bytes
341
+ algorithm: absolute
342
+ instances:
343
+ by_labels: [appliance]
344
+ dimensions:
345
+ - selector: appliance_space_logical_provisioned
346
+ name: provisioned
347
+ - selector: appliance_space_logical_used
348
+ name: used
349
+ - selector: appliance_space_data_physical_used
350
+ name: data_physical
351
+ - selector: appliance_space_shared_logical_used
352
+ name: shared
353
+ - id: appliance_space_efficiency
354
+ title: Appliance Space Efficiency Ratios
355
+ context: appliance_space_efficiency
356
+ units: ratio
357
+ algorithm: absolute
358
+ instances:
359
+ by_labels: [appliance]
360
+ dimensions:
361
+ - selector: appliance_space_efficiency_ratio
362
+ name: efficiency
363
+ options:
364
+ float: true
365
+ - selector: appliance_space_data_reduction
366
+ name: data_reduction
367
+ options:
368
+ float: true
369
+ - selector: appliance_space_snapshot_savings
370
+ name: snapshot_savings
371
+ options:
372
+ float: true
373
+ - selector: appliance_space_thin_savings
374
+ name: thin_savings
375
+ options:
376
+ float: true
377
+
378
+ - family: Volume
379
+ metrics:
380
+ - volume_perf_read_iops
381
+ - volume_perf_write_iops
382
+ - volume_perf_read_bandwidth
383
+ - volume_perf_write_bandwidth
384
+ - volume_perf_avg_read_latency
385
+ - volume_perf_avg_write_latency
386
+ - volume_perf_avg_latency
387
+ - volume_space_logical_provisioned
388
+ - volume_space_logical_used
389
+ - volume_space_thin_savings
390
+ charts:
391
+ - id: volume_iops
392
+ title: Volume IOPS
393
+ context: volume_iops
394
+ units: ops/s
395
+ type: area
396
+ algorithm: absolute
397
+ instances:
398
+ by_labels: [volume]
399
+ dimensions:
400
+ - selector: volume_perf_read_iops
401
+ name: read
402
+ options:
403
+ float: true
404
+ - selector: volume_perf_write_iops
405
+ name: write
406
+ options:
407
+ multiplier: -1
408
+ float: true
409
+ - id: volume_bandwidth
410
+ title: Volume Bandwidth
411
+ context: volume_bandwidth
412
+ units: bytes/s
413
+ type: area
414
+ algorithm: absolute
415
+ instances:
416
+ by_labels: [volume]
417
+ dimensions:
418
+ - selector: volume_perf_read_bandwidth
419
+ name: read
420
+ options:
421
+ float: true
422
+ - selector: volume_perf_write_bandwidth
423
+ name: write
424
+ options:
425
+ multiplier: -1
426
+ float: true
427
+ - id: volume_latency
428
+ title: Volume Latency
429
+ context: volume_latency
430
+ units: microseconds
431
+ algorithm: absolute
432
+ instances:
433
+ by_labels: [volume]
434
+ dimensions:
435
+ - selector: volume_perf_avg_read_latency
436
+ name: read
437
+ options:
438
+ float: true
439
+ - selector: volume_perf_avg_write_latency
440
+ name: write
441
+ options:
442
+ float: true
443
+ - selector: volume_perf_avg_latency
444
+ name: avg
445
+ options:
446
+ float: true
447
+ - id: volume_space
448
+ title: Volume Space Usage
449
+ context: volume_space
450
+ units: bytes
451
+ algorithm: absolute
452
+ instances:
453
+ by_labels: [volume]
454
+ dimensions:
455
+ - selector: volume_space_logical_provisioned
456
+ name: provisioned
457
+ - selector: volume_space_logical_used
458
+ name: used
459
+ - id: volume_space_savings
460
+ title: Volume Thin Savings
461
+ context: volume_space_savings
462
+ units: ratio
463
+ algorithm: absolute
464
+ instances:
465
+ by_labels: [volume]
466
+ dimensions:
467
+ - selector: volume_space_thin_savings
468
+ name: thin_savings
469
+ options:
470
+ float: true
471
+
472
+ - family: Node
473
+ metrics:
474
+ - node_perf_read_iops
475
+ - node_perf_write_iops
476
+ - node_perf_read_bandwidth
477
+ - node_perf_write_bandwidth
478
+ - node_perf_avg_read_latency
479
+ - node_perf_avg_write_latency
480
+ - node_perf_avg_latency
481
+ - node_current_logins
482
+ charts:
483
+ - id: node_iops
484
+ title: Node IOPS
485
+ context: node_iops
486
+ units: ops/s
487
+ type: area
488
+ algorithm: absolute
489
+ instances:
490
+ by_labels: [node]
491
+ dimensions:
492
+ - selector: node_perf_read_iops
493
+ name: read
494
+ options:
495
+ float: true
496
+ - selector: node_perf_write_iops
497
+ name: write
498
+ options:
499
+ multiplier: -1
500
+ float: true
501
+ - id: node_bandwidth
502
+ title: Node Bandwidth
503
+ context: node_bandwidth
504
+ units: bytes/s
505
+ type: area
506
+ algorithm: absolute
507
+ instances:
508
+ by_labels: [node]
509
+ dimensions:
510
+ - selector: node_perf_read_bandwidth
511
+ name: read
512
+ options:
513
+ float: true
514
+ - selector: node_perf_write_bandwidth
515
+ name: write
516
+ options:
517
+ multiplier: -1
518
+ float: true
519
+ - id: node_latency
520
+ title: Node Latency
521
+ context: node_latency
522
+ units: microseconds
523
+ algorithm: absolute
524
+ instances:
525
+ by_labels: [node]
526
+ dimensions:
527
+ - selector: node_perf_avg_read_latency
528
+ name: read
529
+ options:
530
+ float: true
531
+ - selector: node_perf_avg_write_latency
532
+ name: write
533
+ options:
534
+ float: true
535
+ - selector: node_perf_avg_latency
536
+ name: avg
537
+ options:
538
+ float: true
539
+ - id: node_logins
540
+ title: Node Current Logins
541
+ context: node_logins
542
+ units: logins
543
+ algorithm: absolute
544
+ instances:
545
+ by_labels: [node]
546
+ dimensions:
547
+ - selector: node_current_logins
548
+ name: logins
549
+
550
+ - family: FC Port
551
+ metrics:
552
+ - fc_port_perf_read_iops
553
+ - fc_port_perf_write_iops
554
+ - fc_port_perf_read_bandwidth
555
+ - fc_port_perf_write_bandwidth
556
+ - fc_port_perf_avg_read_latency
557
+ - fc_port_perf_avg_write_latency
558
+ - fc_port_perf_avg_latency
559
+ - fc_port_link_up
560
+ charts:
561
+ - id: fc_port_iops
562
+ title: FC Port IOPS
563
+ context: fc_port_iops
564
+ units: ops/s
565
+ type: area
566
+ algorithm: absolute
567
+ instances:
568
+ by_labels: [fc_port]
569
+ dimensions:
570
+ - selector: fc_port_perf_read_iops
571
+ name: read
572
+ options:
573
+ float: true
574
+ - selector: fc_port_perf_write_iops
575
+ name: write
576
+ options:
577
+ multiplier: -1
578
+ float: true
579
+ - id: fc_port_bandwidth
580
+ title: FC Port Bandwidth
581
+ context: fc_port_bandwidth
582
+ units: bytes/s
583
+ type: area
584
+ algorithm: absolute
585
+ instances:
586
+ by_labels: [fc_port]
587
+ dimensions:
588
+ - selector: fc_port_perf_read_bandwidth
589
+ name: read
590
+ options:
591
+ float: true
592
+ - selector: fc_port_perf_write_bandwidth
593
+ name: write
594
+ options:
595
+ multiplier: -1
596
+ float: true
597
+ - id: fc_port_latency
598
+ title: FC Port Latency
599
+ context: fc_port_latency
600
+ units: microseconds
601
+ algorithm: absolute
602
+ instances:
603
+ by_labels: [fc_port]
604
+ dimensions:
605
+ - selector: fc_port_perf_avg_read_latency
606
+ name: read
607
+ options:
608
+ float: true
609
+ - selector: fc_port_perf_avg_write_latency
610
+ name: write
611
+ options:
612
+ float: true
613
+ - selector: fc_port_perf_avg_latency
614
+ name: avg
615
+ options:
616
+ float: true
617
+ - id: fc_port_link_status
618
+ title: FC Port Link Status
619
+ context: fc_port_link_status
620
+ units: status
621
+ algorithm: absolute
622
+ instances:
623
+ by_labels: [fc_port]
624
+ dimensions:
625
+ - selector: fc_port_link_up
626
+ name: up
627
+
628
+ - family: Ethernet Port
629
+ metrics:
630
+ - eth_port_bytes_rx_ps
631
+ - eth_port_bytes_tx_ps
632
+ - eth_port_pkt_rx_ps
633
+ - eth_port_pkt_tx_ps
634
+ - eth_port_pkt_rx_crc_error_ps
635
+ - eth_port_pkt_rx_no_buffer_error_ps
636
+ - eth_port_pkt_tx_error_ps
637
+ - eth_port_link_up
638
+ charts:
639
+ - id: eth_port_bytes
640
+ title: Ethernet Port Bytes Rate
641
+ context: eth_port_bytes
642
+ units: bytes/s
643
+ type: area
644
+ algorithm: absolute
645
+ instances:
646
+ by_labels: [eth_port]
647
+ dimensions:
648
+ - selector: eth_port_bytes_rx_ps
649
+ name: received
650
+ options:
651
+ float: true
652
+ - selector: eth_port_bytes_tx_ps
653
+ name: sent
654
+ options:
655
+ multiplier: -1
656
+ float: true
657
+ - id: eth_port_packets
658
+ title: Ethernet Port Packets Rate
659
+ context: eth_port_packets
660
+ units: packets/s
661
+ type: area
662
+ algorithm: absolute
663
+ instances:
664
+ by_labels: [eth_port]
665
+ dimensions:
666
+ - selector: eth_port_pkt_rx_ps
667
+ name: received
668
+ options:
669
+ float: true
670
+ - selector: eth_port_pkt_tx_ps
671
+ name: sent
672
+ options:
673
+ multiplier: -1
674
+ float: true
675
+ - id: eth_port_errors
676
+ title: Ethernet Port Errors Rate
677
+ context: eth_port_errors
678
+ units: errors/s
679
+ type: stacked
680
+ algorithm: absolute
681
+ instances:
682
+ by_labels: [eth_port]
683
+ dimensions:
684
+ - selector: eth_port_pkt_rx_crc_error_ps
685
+ name: rx_crc
686
+ options:
687
+ float: true
688
+ - selector: eth_port_pkt_rx_no_buffer_error_ps
689
+ name: rx_no_buffer
690
+ options:
691
+ float: true
692
+ - selector: eth_port_pkt_tx_error_ps
693
+ name: tx_error
694
+ options:
695
+ float: true
696
+ - id: eth_port_link_status
697
+ title: Ethernet Port Link Status
698
+ context: eth_port_link_status
699
+ units: status
700
+ algorithm: absolute
701
+ instances:
702
+ by_labels: [eth_port]
703
+ dimensions:
704
+ - selector: eth_port_link_up
705
+ name: up
706
+
707
+ - family: File System
708
+ metrics:
709
+ - file_system_perf_read_iops
710
+ - file_system_perf_write_iops
711
+ - file_system_perf_read_bandwidth
712
+ - file_system_perf_write_bandwidth
713
+ - file_system_perf_avg_read_latency
714
+ - file_system_perf_avg_write_latency
715
+ - file_system_perf_avg_latency
716
+ charts:
717
+ - id: filesystem_iops
718
+ title: File System IOPS
719
+ context: filesystem_iops
720
+ units: ops/s
721
+ type: area
722
+ algorithm: absolute
723
+ instances:
724
+ by_labels: [filesystem]
725
+ dimensions:
726
+ - selector: file_system_perf_read_iops
727
+ name: read
728
+ options:
729
+ float: true
730
+ - selector: file_system_perf_write_iops
731
+ name: write
732
+ options:
733
+ multiplier: -1
734
+ float: true
735
+ - id: filesystem_bandwidth
736
+ title: File System Bandwidth
737
+ context: filesystem_bandwidth
738
+ units: bytes/s
739
+ type: area
740
+ algorithm: absolute
741
+ instances:
742
+ by_labels: [filesystem]
743
+ dimensions:
744
+ - selector: file_system_perf_read_bandwidth
745
+ name: read
746
+ options:
747
+ float: true
748
+ - selector: file_system_perf_write_bandwidth
749
+ name: write
750
+ options:
751
+ multiplier: -1
752
+ float: true
753
+ - id: filesystem_latency
754
+ title: File System Latency
755
+ context: filesystem_latency
756
+ units: microseconds
757
+ algorithm: absolute
758
+ instances:
759
+ by_labels: [filesystem]
760
+ dimensions:
761
+ - selector: file_system_perf_avg_read_latency
762
+ name: read
763
+ options:
764
+ float: true
765
+ - selector: file_system_perf_avg_write_latency
766
+ name: write
767
+ options:
768
+ float: true
769
+ - selector: file_system_perf_avg_latency
770
+ name: avg
771
+ options:
772
+ float: true
773
+
774
+ - family: Drive
775
+ metrics:
776
+ - drive_endurance_remaining
777
+ charts:
778
+ - id: drive_endurance
779
+ title: Drive Endurance Remaining
780
+ context: drive_endurance
781
+ units: percentage
782
+ algorithm: absolute
783
+ instances:
784
+ by_labels: [drive]
785
+ dimensions:
786
+ - selector: drive_endurance_remaining
787
+ name: remaining
788
+ options:
789
+ float: true
src/go/plugin/go.d/collector/powerstore/client/client.go
new
+364
@@ -0,0 +1,364 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package client
4
+
5
+import (
6
+ "encoding/json"
7
+ "fmt"
8
+ "net/http"
9
+ "net/http/cookiejar"
10
+ "net/url"
11
+ "path"
12
+ "strconv"
13
+ "sync"
14
+
15
+ "github.com/netdata/netdata/go/plugins/pkg/web"
16
+)
17
+
18
+const (
19
+ apiBasePath = "/api/rest"
20
+ dellEMCToken = "DELL-EMC-TOKEN"
21
+ defaultLimit = "2000"
22
+)
23
+
24
+// New creates a new PowerStore REST API client.
25
+func New(client web.ClientConfig, request web.RequestConfig) (*Client, error) {
26
+ httpClient, err := web.NewHTTPClient(client)
27
+ if err != nil {
28
+ return nil, err
29
+ }
30
+
31
+ jar, err := cookiejar.New(nil)
32
+ if err != nil {
33
+ return nil, fmt.Errorf("error creating cookie jar: %v", err)
34
+ }
35
+ httpClient.Jar = jar
36
+
37
+ return &Client{
38
+ Request: request,
39
+ httpClient: httpClient,
40
+ csrf: &csrfToken{},
41
+ }, nil
42
+}
43
+
44
+// Client represents a Dell PowerStore REST API client.
45
+type Client struct {
46
+ Request web.RequestConfig
47
+ httpClient *http.Client
48
+ csrf *csrfToken
49
+}
50
+
51
+// Login authenticates with the PowerStore API.
52
+// GET /api/rest/login_session with Basic Auth.
53
+// Caches auth_cookie (via cookiejar) and DELL-EMC-TOKEN (from response header).
54
+func (c *Client) Login() error {
55
+ req := c.createRequest("/login_session")
56
+
57
+ resp, err := c.doOK(req)
58
+ defer web.CloseBody(resp)
59
+ if err != nil {
60
+ return fmt.Errorf("login failed: %v", err)
61
+ }
62
+
63
+ c.cacheCSRFToken(resp)
64
+ return nil
65
+}
66
+
67
+// Logout clears the client session state.
68
+func (c *Client) Logout() {
69
+ c.csrf.unset()
70
+}
71
+
72
+// Clusters returns all clusters.
73
+func (c *Client) Clusters() ([]Cluster, error) {
74
+ return doGetAllPages[Cluster](c, "/cluster", nil)
75
+}
76
+
77
+// Appliances returns all appliances.
78
+func (c *Client) Appliances() ([]Appliance, error) {
79
+ return doGetAllPages[Appliance](c, "/appliance", nil)
80
+}
81
+
82
+// Volumes returns all volumes.
83
+func (c *Client) Volumes() ([]Volume, error) {
84
+ return doGetAllPages[Volume](c, "/volume", nil)
85
+}
86
+
87
+// AllHardware returns all hardware components.
88
+func (c *Client) AllHardware() ([]Hardware, error) {
89
+ return doGetAllPages[Hardware](c, "/hardware", nil)
90
+}
91
+
92
+// Alerts returns alerts filtered by state.
93
+func (c *Client) Alerts(state string) ([]Alert, error) {
94
+ return doGetAllPages[Alert](c, "/alert", url.Values{"state": {"eq." + state}})
95
+}
96
+
97
+// FcPorts returns all Fibre Channel ports.
98
+func (c *Client) FcPorts() ([]FcPort, error) {
99
+ return doGetAllPages[FcPort](c, "/fc_port", nil)
100
+}
101
+
102
+// EthPorts returns all Ethernet ports.
103
+func (c *Client) EthPorts() ([]EthPort, error) {
104
+ return doGetAllPages[EthPort](c, "/eth_port", nil)
105
+}
106
+
107
+// FileSystems returns all file systems.
108
+func (c *Client) FileSystems() ([]FileSystem, error) {
109
+ return doGetAllPages[FileSystem](c, "/file_system", nil)
110
+}
111
+
112
+// NASServers returns all NAS servers.
113
+func (c *Client) NASServers() ([]NAS, error) {
114
+ return doGetAllPages[NAS](c, "/nas_server", nil)
115
+}
116
+
117
+// PerformanceMetricsByAppliance returns performance metrics for an appliance.
118
+func (c *Client) PerformanceMetricsByAppliance(id string) ([]PerformanceMetrics, error) {
119
+ return doMetrics[PerformanceMetrics](c, "performance_metrics_by_appliance", id, "Five_Mins")
120
+}
121
+
122
+// PerformanceMetricsByVolume returns performance metrics for a volume.
123
+func (c *Client) PerformanceMetricsByVolume(id string) ([]PerformanceMetrics, error) {
124
+ return doMetrics[PerformanceMetrics](c, "performance_metrics_by_volume", id, "Five_Mins")
125
+}
126
+
127
+// PerformanceMetricsByNode returns performance metrics for a node.
128
+func (c *Client) PerformanceMetricsByNode(id string) ([]PerformanceMetrics, error) {
129
+ return doMetrics[PerformanceMetrics](c, "performance_metrics_by_node", id, "Five_Mins")
130
+}
131
+
132
+// PerformanceMetricsByFcPort returns performance metrics for an FC port.
133
+func (c *Client) PerformanceMetricsByFcPort(id string) ([]PerformanceMetrics, error) {
134
+ return doMetrics[PerformanceMetrics](c, "performance_metrics_by_fe_fc_port", id, "Five_Mins")
135
+}
136
+
137
+// EthPortPerformanceMetrics returns performance metrics for an Ethernet port.
138
+func (c *Client) EthPortPerformanceMetrics(id string) ([]EthPortMetrics, error) {
139
+ return doMetrics[EthPortMetrics](c, "performance_metrics_by_fe_eth_port", id, "Five_Mins")
140
+}
141
+
142
+// PerformanceMetricsByFileSystem returns performance metrics for a file system.
143
+func (c *Client) PerformanceMetricsByFileSystem(id string) ([]FileSystemMetrics, error) {
144
+ return doMetrics[FileSystemMetrics](c, "performance_metrics_by_file_system", id, "Five_Mins")
145
+}
146
+
147
+// SpaceMetricsByCluster returns space metrics for a cluster.
148
+func (c *Client) SpaceMetricsByCluster(id string) ([]SpaceMetrics, error) {
149
+ return doMetrics[SpaceMetrics](c, "space_metrics_by_cluster", id, "One_Day")
150
+}
151
+
152
+// SpaceMetricsByAppliance returns space metrics for an appliance.
153
+func (c *Client) SpaceMetricsByAppliance(id string) ([]SpaceMetrics, error) {
154
+ return doMetrics[SpaceMetrics](c, "space_metrics_by_appliance", id, "One_Day")
155
+}
156
+
157
+// SpaceMetricsByVolume returns space metrics for a volume.
158
+func (c *Client) SpaceMetricsByVolume(id string) ([]SpaceMetrics, error) {
159
+ return doMetrics[SpaceMetrics](c, "space_metrics_by_volume", id, "Five_Mins")
160
+}
161
+
162
+// WearMetricsByDrive returns wear metrics for a drive.
163
+func (c *Client) WearMetricsByDrive(id string) ([]WearMetrics, error) {
164
+ return doMetrics[WearMetrics](c, "wear_metrics_by_drive", id, "Five_Mins")
165
+}
166
+
167
+// CopyMetricsByAppliance returns copy/replication metrics for an appliance.
168
+func (c *Client) CopyMetricsByAppliance(id string) ([]CopyMetrics, error) {
169
+ return doMetrics[CopyMetrics](c, "copy_metrics_by_appliance", id, "Five_Mins")
170
+}
171
+
172
+func doMetrics[T any](c *Client, entity, entityID, interval string) ([]T, error) {
173
+ body := MetricsRequest{Entity: entity, EntityID: entityID, Interval: interval}
174
+ var v []T
175
+ if err := c.doPostWithRetry(&v, "/metrics/generate", body); err != nil {
176
+ return nil, err
177
+ }
178
+ return v, nil
179
+}
180
+
181
+func (c *Client) createRequest(urlPath string) web.RequestConfig {
182
+ req := c.Request.Copy()
183
+ u, _ := url.Parse(req.URL)
184
+ u.Path = path.Join(u.Path, apiBasePath, urlPath)
185
+ req.URL = u.String()
186
+ return req
187
+}
188
+
189
+func (c *Client) createGetRequest(urlPath string, params url.Values) web.RequestConfig {
190
+ req := c.createRequest(urlPath)
191
+ u, _ := url.Parse(req.URL)
192
+ q := u.Query()
193
+ q.Set("select", "*")
194
+ q.Set("limit", defaultLimit)
195
+ for k, vals := range params {
196
+ for _, v := range vals {
197
+ q.Set(k, v)
198
+ }
199
+ }
200
+ u.RawQuery = q.Encode()
201
+ req.URL = u.String()
202
+
203
+ if tok := c.csrf.get(); tok != "" {
204
+ if req.Headers == nil {
205
+ req.Headers = make(map[string]string)
206
+ }
207
+ req.Headers[dellEMCToken] = tok
208
+ }
209
+ return req
210
+}
211
+
212
+func (c *Client) createPostRequest(urlPath string, body any) (web.RequestConfig, error) {
213
+ req := c.createRequest(urlPath)
214
+
215
+ b, err := json.Marshal(body)
216
+ if err != nil {
217
+ return req, fmt.Errorf("error marshaling request body: %v", err)
218
+ }
219
+
220
+ if req.Headers == nil {
221
+ req.Headers = make(map[string]string)
222
+ }
223
+ req.Headers["Content-Type"] = "application/json"
224
+ if tok := c.csrf.get(); tok != "" {
225
+ req.Headers[dellEMCToken] = tok
226
+ }
227
+ req.Method = http.MethodPost
228
+ req.Body = string(b)
229
+ return req, nil
230
+}
231
+
232
+func (c *Client) do(req web.RequestConfig) (*http.Response, error) {
233
+ httpReq, err := web.NewHTTPRequest(req)
234
+ if err != nil {
235
+ return nil, fmt.Errorf("error creating http request to %s: %v", req.URL, err)
236
+ }
237
+ return c.httpClient.Do(httpReq)
238
+}
239
+
240
+func (c *Client) doOK(req web.RequestConfig) (*http.Response, error) {
241
+ resp, err := c.do(req)
242
+ if err != nil {
243
+ return nil, err
244
+ }
245
+ if err = checkStatusCode(resp); err != nil {
246
+ err = fmt.Errorf("%s returned %v", req.URL, err)
247
+ }
248
+ return resp, err
249
+}
250
+
251
+func (c *Client) doOKWithRetry(req web.RequestConfig) (*http.Response, error) {
252
+ resp, err := c.do(req)
253
+ if err != nil {
254
+ return nil, err
255
+ }
256
+ // PowerStore returns 403 when the session/token is stale (not 401)
257
+ if resp.StatusCode == http.StatusForbidden {
258
+ web.CloseBody(resp)
259
+ if err = c.Login(); err != nil {
260
+ return nil, fmt.Errorf("re-login after 403 failed: %v", err)
261
+ }
262
+ req = c.applyCSRF(req)
263
+ return c.doOK(req)
264
+ }
265
+ if err = checkStatusCode(resp); err != nil {
266
+ err = fmt.Errorf("%s returned %v", req.URL, err)
267
+ }
268
+ return resp, err
269
+}
270
+
271
+// doGetAllPages fetches all pages from a paginated GET endpoint.
272
+// PowerStore returns HTTP 206 (Partial Content) when more pages are available,
273
+// with a server-enforced maximum of 2000 items per page.
274
+func doGetAllPages[T any](c *Client, urlPath string, params url.Values) ([]T, error) {
275
+ var all []T
276
+ offset := 0
277
+
278
+ for {
279
+ reqParams := make(url.Values)
280
+ for k, v := range params {
281
+ reqParams[k] = v
282
+ }
283
+ if offset > 0 {
284
+ reqParams.Set("offset", strconv.Itoa(offset))
285
+ }
286
+
287
+ req := c.createGetRequest(urlPath, reqParams)
288
+
289
+ resp, err := c.doOKWithRetry(req)
290
+ if err != nil {
291
+ web.CloseBody(resp)
292
+ return nil, err
293
+ }
294
+
295
+ var page []T
296
+ err = json.NewDecoder(resp.Body).Decode(&page)
297
+ c.cacheCSRFToken(resp)
298
+ partial := resp.StatusCode == http.StatusPartialContent
299
+ web.CloseBody(resp)
300
+
301
+ if err != nil {
302
+ return nil, fmt.Errorf("error decoding %s response: %v", urlPath, err)
303
+ }
304
+
305
+ all = append(all, page...)
306
+
307
+ if !partial || len(page) == 0 {
308
+ break
309
+ }
310
+ offset += len(page)
311
+ }
312
+
313
+ return all, nil
314
+}
315
+
316
+func (c *Client) doPostWithRetry(dst any, urlPath string, body any) error {
317
+ req, err := c.createPostRequest(urlPath, body)
318
+ if err != nil {
319
+ return err
320
+ }
321
+ resp, err := c.doOKWithRetry(req)
322
+ defer web.CloseBody(resp)
323
+ if err != nil {
324
+ return err
325
+ }
326
+ c.cacheCSRFToken(resp)
327
+ return json.NewDecoder(resp.Body).Decode(dst)
328
+}
329
+
330
+func (c *Client) cacheCSRFToken(resp *http.Response) {
331
+ if resp == nil {
332
+ return
333
+ }
334
+ if tok := resp.Header.Get(dellEMCToken); tok != "" {
335
+ c.csrf.set(tok)
336
+ }
337
+}
338
+
339
+func (c *Client) applyCSRF(req web.RequestConfig) web.RequestConfig {
340
+ if tok := c.csrf.get(); tok != "" {
341
+ if req.Headers == nil {
342
+ req.Headers = make(map[string]string)
343
+ }
344
+ req.Headers[dellEMCToken] = tok
345
+ }
346
+ return req
347
+}
348
+
349
+func checkStatusCode(resp *http.Response) error {
350
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
351
+ return fmt.Errorf("HTTP status code %d", resp.StatusCode)
352
+ }
353
+ return nil
354
+}
355
+
356
+// csrfToken safely stores the DELL-EMC-TOKEN value.
357
+type csrfToken struct {
358
+ mux sync.RWMutex
359
+ value string
360
+}
361
+
362
+func (t *csrfToken) get() string { t.mux.RLock(); defer t.mux.RUnlock(); return t.value }
363
+func (t *csrfToken) set(v string) { t.mux.Lock(); defer t.mux.Unlock(); t.value = v }
364
+func (t *csrfToken) unset() { t.set("") }
src/go/plugin/go.d/collector/powerstore/client/types.go
new
+205
@@ -0,0 +1,205 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package client
4
+
5
+// Cluster represents a PowerStore cluster.
6
+type Cluster struct {
7
+ ID string `json:"id"`
8
+ GlobalID string `json:"global_id"`
9
+ Name string `json:"name"`
10
+ ManagementAddress string `json:"management_address"`
11
+ State string `json:"state"`
12
+}
13
+
14
+// Appliance represents a PowerStore appliance.
15
+type Appliance struct {
16
+ ID string `json:"id"`
17
+ Name string `json:"name"`
18
+ ServiceTag string `json:"service_tag,omitempty"`
19
+}
20
+
21
+// Volume represents a PowerStore volume.
22
+type Volume struct {
23
+ ID string `json:"id"`
24
+ Name string `json:"name"`
25
+ Size int64 `json:"size"`
26
+ LogicalUsed int64 `json:"logical_used"`
27
+ State string `json:"state"`
28
+ Type string `json:"type"`
29
+ ApplianceID string `json:"appliance_id"`
30
+}
31
+
32
+// Hardware represents a PowerStore hardware component.
33
+type Hardware struct {
34
+ ID string `json:"id"`
35
+ Name string `json:"name"`
36
+ Type string `json:"type"`
37
+ LifecycleState string `json:"lifecycle_state"`
38
+ StatusLED string `json:"status_led,omitempty"`
39
+ ApplianceID string `json:"appliance_id,omitempty"`
40
+ ExtraDetails string `json:"extra_details,omitempty"`
41
+ PartNumber string `json:"part_number,omitempty"`
42
+ SerialNumber string `json:"serial_number,omitempty"`
43
+ Slot int `json:"slot,omitempty"`
44
+}
45
+
46
+// Alert represents a PowerStore alert.
47
+type Alert struct {
48
+ ID string `json:"id"`
49
+ Severity string `json:"severity"`
50
+ State string `json:"state"`
51
+ ResourceType string `json:"resource_type"`
52
+ ResourceName string `json:"resource_name"`
53
+ Description string `json:"description_l10n"`
54
+ GeneratedTimestamp string `json:"generated_timestamp"`
55
+ IsAcknowledged bool `json:"is_acknowledged"`
56
+}
57
+
58
+// FcPort represents a PowerStore Fibre Channel port.
59
+type FcPort struct {
60
+ ID string `json:"id"`
61
+ Name string `json:"name"`
62
+ ApplianceID string `json:"appliance_id"`
63
+ NodeID string `json:"node_id"`
64
+ IsLinkUp bool `json:"is_link_up"`
65
+ CurrentSpeed string `json:"current_speed"`
66
+ Wwn string `json:"wwn"`
67
+}
68
+
69
+// EthPort represents a PowerStore Ethernet port.
70
+type EthPort struct {
71
+ ID string `json:"id"`
72
+ Name string `json:"name"`
73
+ ApplianceID string `json:"appliance_id"`
74
+ NodeID string `json:"node_id"`
75
+ IsLinkUp bool `json:"is_link_up"`
76
+ CurrentSpeed string `json:"current_speed"`
77
+ MacAddress string `json:"mac_address"`
78
+ CurrentMTU int32 `json:"current_mtu"`
79
+}
80
+
81
+// FileSystem represents a PowerStore file system.
82
+type FileSystem struct {
83
+ ID string `json:"id"`
84
+ Name string `json:"name"`
85
+ NasServerID string `json:"nas_server_id"`
86
+ SizeTotal int64 `json:"size_total"`
87
+ SizeUsed int64 `json:"size_used"`
88
+ FilesystemType string `json:"filesystem_type"`
89
+}
90
+
91
+// NAS represents a PowerStore NAS server.
92
+type NAS struct {
93
+ ID string `json:"id"`
94
+ Name string `json:"name"`
95
+ OperationalStatus string `json:"operational_status"`
96
+ CurrentNodeID string `json:"current_node_id"`
97
+}
98
+
99
+// Node represents a PowerStore node (queried from hardware endpoint).
100
+type Node struct {
101
+ ID string `json:"id"`
102
+ Name string `json:"name"`
103
+ ApplianceID string `json:"appliance_id"`
104
+}
105
+
106
+// MetricsRequest is the POST body for /api/rest/metrics/generate.
107
+type MetricsRequest struct {
108
+ Entity string `json:"entity"`
109
+ EntityID string `json:"entity_id"`
110
+ Interval string `json:"interval"`
111
+}
112
+
113
+// PerformanceMetrics contains common performance fields shared by most entity types.
114
+type PerformanceMetrics struct {
115
+ ApplianceID string `json:"appliance_id,omitempty"`
116
+ NodeID string `json:"node_id,omitempty"`
117
+ VolumeID string `json:"volume_id,omitempty"`
118
+ FePortID string `json:"fe_port_id,omitempty"`
119
+
120
+ // Current values
121
+ ReadIops float64 `json:"read_iops"`
122
+ WriteIops float64 `json:"write_iops"`
123
+ TotalIops float64 `json:"total_iops"`
124
+ ReadBandwidth float64 `json:"read_bandwidth"`
125
+ WriteBandwidth float64 `json:"write_bandwidth"`
126
+ TotalBandwidth float64 `json:"total_bandwidth"`
127
+
128
+ // Averages
129
+ AvgReadLatency float64 `json:"avg_read_latency"`
130
+ AvgWriteLatency float64 `json:"avg_write_latency"`
131
+ AvgLatency float64 `json:"avg_latency"`
132
+ AvgIoSize float64 `json:"avg_io_size"`
133
+ AvgReadIops float64 `json:"avg_read_iops"`
134
+ AvgWriteIops float64 `json:"avg_write_iops"`
135
+
136
+ // Appliance-specific
137
+ IoWorkloadCPUUtilization float64 `json:"io_workload_cpu_utilization,omitempty"`
138
+ AvgIoWorkloadCPUUtilization float64 `json:"avg_io_workload_cpu_utilization,omitempty"`
139
+
140
+ // Node-specific
141
+ CurrentLogins *int64 `json:"current_logins,omitempty"`
142
+}
143
+
144
+// EthPortMetrics contains Ethernet port performance fields.
145
+type EthPortMetrics struct {
146
+ ApplianceID string `json:"appliance_id,omitempty"`
147
+ FePortID string `json:"fe_port_id,omitempty"`
148
+ NodeID string `json:"node_id,omitempty"`
149
+
150
+ BytesRxPs float64 `json:"bytes_rx_ps"`
151
+ BytesTxPs float64 `json:"bytes_tx_ps"`
152
+ PktRxPs float64 `json:"pkt_rx_ps"`
153
+ PktTxPs float64 `json:"pkt_tx_ps"`
154
+ PktRxCrcErrorPs float64 `json:"pkt_rx_crc_error_ps"`
155
+ PktRxNoBufferErrorPs float64 `json:"pkt_rx_no_buffer_error_ps"`
156
+ PktTxErrorPs float64 `json:"pkt_tx_error_ps"`
157
+}
158
+
159
+// SpaceMetrics contains capacity/space fields.
160
+type SpaceMetrics struct {
161
+ ClusterID string `json:"cluster_id,omitempty"`
162
+ ApplianceID string `json:"appliance_id,omitempty"`
163
+ VolumeID string `json:"volume_id,omitempty"`
164
+
165
+ PhysicalTotal *int64 `json:"physical_total,omitempty"`
166
+ PhysicalUsed *int64 `json:"physical_used,omitempty"`
167
+ LogicalProvisioned *int64 `json:"logical_provisioned,omitempty"`
168
+ LogicalUsed *int64 `json:"logical_used,omitempty"`
169
+ DataPhysicalUsed *int64 `json:"data_physical_used,omitempty"`
170
+ SharedLogicalUsed *int64 `json:"shared_logical_used,omitempty"`
171
+
172
+ EfficiencyRatio float64 `json:"efficiency_ratio"`
173
+ DataReduction float64 `json:"data_reduction"`
174
+ SnapshotSavings float64 `json:"snapshot_savings"`
175
+ ThinSavings float64 `json:"thin_savings"`
176
+}
177
+
178
+// WearMetrics contains drive wear/endurance fields.
179
+type WearMetrics struct {
180
+ DriveID string `json:"drive_id"`
181
+ PercentEnduranceRemaining float64 `json:"percent_endurance_remaining"`
182
+}
183
+
184
+// CopyMetrics contains replication/copy fields.
185
+type CopyMetrics struct {
186
+ ApplianceID string `json:"appliance_id,omitempty"`
187
+ DataRemaining *int64 `json:"data_remaining,omitempty"`
188
+ DataTransferred *int64 `json:"data_transferred,omitempty"`
189
+ TransferRate float64 `json:"transfer_rate"`
190
+}
191
+
192
+// FileSystemMetrics contains file system performance fields.
193
+type FileSystemMetrics struct {
194
+ FileSystemID string `json:"file_system_id"`
195
+
196
+ ReadIops float64 `json:"read_iops"`
197
+ WriteIops float64 `json:"write_iops"`
198
+ TotalIops float64 `json:"total_iops"`
199
+ ReadBandwidth float64 `json:"read_bandwidth"`
200
+ WriteBandwidth float64 `json:"write_bandwidth"`
201
+ TotalBandwidth float64 `json:"total_bandwidth"`
202
+ AvgReadLatency float64 `json:"avg_read_latency"`
203
+ AvgWriteLatency float64 `json:"avg_write_latency"`
204
+ AvgLatency float64 `json:"avg_latency"`
205
+}
src/go/plugin/go.d/collector/powerstore/collect.go
new
+157
@@ -0,0 +1,157 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import (
6
+ "sync"
7
+ "time"
8
+
9
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/powerstore/client"
10
+)
11
+
12
+const discoveryEvery = 5
13
+
14
+func (c *Collector) collect() error {
15
+ c.runs++
16
+ if !c.lastDiscoveryOK || c.runs%discoveryEvery == 0 {
17
+ if err := c.discovery(); err != nil {
18
+ if !c.lastDiscoveryOK {
19
+ return err
20
+ }
21
+ c.Warningf("discovery refresh failed, using previous data: %v", err)
22
+ }
23
+ }
24
+
25
+ // Collect metrics from API concurrently, bounded by semaphore.
26
+ var wg sync.WaitGroup
27
+ for _, fn := range []func(){
28
+ c.collectClusterSpace,
29
+ c.collectAppliances,
30
+ c.collectVolumes,
31
+ c.collectNodes,
32
+ c.collectFcPorts,
33
+ c.collectEthPorts,
34
+ c.collectFileSystems,
35
+ c.collectAlerts,
36
+ c.collectDriveWear,
37
+ c.collectReplication,
38
+ } {
39
+ wg.Add(1)
40
+ go func(f func()) {
41
+ defer wg.Done()
42
+ f()
43
+ }(fn)
44
+ }
45
+ wg.Wait()
46
+
47
+ // These use cached discovery data, no API calls.
48
+ c.collectHardwareHealth()
49
+ c.collectNASStatus()
50
+
51
+ return nil
52
+}
53
+
54
+func (c *Collector) discovery() error {
55
+ start := time.Now()
56
+ c.Debugf("starting discovery")
57
+
58
+ // Build results in a temp struct; only swap on full success.
59
+ var d discovered
60
+
61
+ clusters, err := c.client.Clusters()
62
+ if err != nil {
63
+ c.lastDiscoveryOK = false
64
+ return err
65
+ }
66
+ d.clusters = clusters
67
+
68
+ appliances, err := c.client.Appliances()
69
+ if err != nil {
70
+ c.lastDiscoveryOK = false
71
+ return err
72
+ }
73
+ d.appliances = make(map[string]client.Appliance, len(appliances))
74
+ for _, a := range appliances {
75
+ d.appliances[a.ID] = a
76
+ }
77
+
78
+ volumes, err := c.client.Volumes()
79
+ if err != nil {
80
+ c.lastDiscoveryOK = false
81
+ return err
82
+ }
83
+ d.volumes = make(map[string]client.Volume, len(volumes))
84
+ for _, v := range volumes {
85
+ if c.volumeMatches(v.Name) {
86
+ d.volumes[v.ID] = v
87
+ }
88
+ }
89
+
90
+ allHW, err := c.client.AllHardware()
91
+ if err != nil {
92
+ c.lastDiscoveryOK = false
93
+ return err
94
+ }
95
+ d.hardware = allHW
96
+ d.nodes = make(map[string]client.Node)
97
+ d.drives = make(map[string]client.Hardware)
98
+ for _, h := range allHW {
99
+ switch h.Type {
100
+ case "Node":
101
+ d.nodes[h.ID] = client.Node{ID: h.ID, Name: h.Name, ApplianceID: h.ApplianceID}
102
+ case "Drive":
103
+ d.drives[h.ID] = h
104
+ }
105
+ }
106
+
107
+ fcPorts, err := c.client.FcPorts()
108
+ if err != nil {
109
+ c.lastDiscoveryOK = false
110
+ return err
111
+ }
112
+ d.fcPorts = make(map[string]client.FcPort, len(fcPorts))
113
+ for _, p := range fcPorts {
114
+ d.fcPorts[p.ID] = p
115
+ }
116
+
117
+ ethPorts, err := c.client.EthPorts()
118
+ if err != nil {
119
+ c.lastDiscoveryOK = false
120
+ return err
121
+ }
122
+ d.ethPorts = make(map[string]client.EthPort, len(ethPorts))
123
+ for _, p := range ethPorts {
124
+ d.ethPorts[p.ID] = p
125
+ }
126
+
127
+ fileSystems, err := c.client.FileSystems()
128
+ if err != nil {
129
+ c.lastDiscoveryOK = false
130
+ return err
131
+ }
132
+ d.fileSystems = make(map[string]client.FileSystem, len(fileSystems))
133
+ for _, fs := range fileSystems {
134
+ d.fileSystems[fs.ID] = fs
135
+ }
136
+
137
+ nas, err := c.client.NASServers()
138
+ if err != nil {
139
+ c.lastDiscoveryOK = false
140
+ return err
141
+ }
142
+ d.nasServers = make(map[string]client.NAS, len(nas))
143
+ for _, n := range nas {
144
+ d.nasServers[n.ID] = n
145
+ }
146
+
147
+ // All API calls succeeded — atomic swap.
148
+ c.discovered = d
149
+ c.lastDiscoveryOK = true
150
+
151
+ c.Debugf("discovery: %d clusters, %d appliances, %d volumes, %d nodes, %d FC ports, %d ETH ports, %d file systems, %d NAS servers, %d drives (took %s)",
152
+ len(d.clusters), len(d.appliances), len(d.volumes), len(d.nodes),
153
+ len(d.fcPorts), len(d.ethPorts), len(d.fileSystems), len(d.nasServers), len(d.drives),
154
+ time.Since(start))
155
+
156
+ return nil
157
+}
src/go/plugin/go.d/collector/powerstore/collect_alerts.go
new
+33
@@ -0,0 +1,33 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+func (c *Collector) collectAlerts() {
6
+ c.sem <- struct{}{}
7
+ defer func() { <-c.sem }()
8
+
9
+ alerts, err := c.client.Alerts("ACTIVE")
10
+ if err != nil {
11
+ c.Warningf("error collecting alerts: %v", err)
12
+ return
13
+ }
14
+
15
+ var crit, major, minor, info float64
16
+ for _, a := range alerts {
17
+ switch a.Severity {
18
+ case "Critical":
19
+ crit++
20
+ case "Major":
21
+ major++
22
+ case "Minor":
23
+ minor++
24
+ case "Info":
25
+ info++
26
+ }
27
+ }
28
+
29
+ c.mx.alerts.critical.Observe(crit)
30
+ c.mx.alerts.major.Observe(major)
31
+ c.mx.alerts.minor.Observe(minor)
32
+ c.mx.alerts.info.Observe(info)
33
+}
src/go/plugin/go.d/collector/powerstore/collect_appliance.go
new
+64
@@ -0,0 +1,64 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import "sync"
6
+
7
+func (c *Collector) collectAppliances() {
8
+ var wg sync.WaitGroup
9
+
10
+ for id, app := range c.discovered.appliances {
11
+ wg.Add(1)
12
+ go func(id, name string) {
13
+ defer wg.Done()
14
+ c.sem <- struct{}{}
15
+ defer func() { <-c.sem }()
16
+
17
+ pm, err := c.client.PerformanceMetricsByAppliance(id)
18
+ if err != nil {
19
+ c.Warningf("error collecting appliance %s perf metrics: %v", id, err)
20
+ } else if len(pm) > 0 {
21
+ last := pm[len(pm)-1]
22
+ c.mx.appliance.perf.readIops.WithLabelValues(name).Observe(last.ReadIops)
23
+ c.mx.appliance.perf.writeIops.WithLabelValues(name).Observe(last.WriteIops)
24
+ c.mx.appliance.perf.readBandwidth.WithLabelValues(name).Observe(last.ReadBandwidth)
25
+ c.mx.appliance.perf.writeBandwidth.WithLabelValues(name).Observe(last.WriteBandwidth)
26
+ c.mx.appliance.perf.avgReadLatency.WithLabelValues(name).Observe(last.AvgReadLatency)
27
+ c.mx.appliance.perf.avgWriteLatency.WithLabelValues(name).Observe(last.AvgWriteLatency)
28
+ c.mx.appliance.perf.avgLatency.WithLabelValues(name).Observe(last.AvgLatency)
29
+ c.mx.appliance.cpu.WithLabelValues(name).Observe(last.IoWorkloadCPUUtilization)
30
+ }
31
+
32
+ sm, err := c.client.SpaceMetricsByAppliance(id)
33
+ if err != nil {
34
+ c.Warningf("error collecting appliance %s space metrics: %v", id, err)
35
+ } else if len(sm) > 0 {
36
+ last := sm[len(sm)-1]
37
+ if last.PhysicalTotal != nil {
38
+ c.mx.appliance.space.physicalTotal.WithLabelValues(name).Observe(float64(*last.PhysicalTotal))
39
+ }
40
+ if last.PhysicalUsed != nil {
41
+ c.mx.appliance.space.physicalUsed.WithLabelValues(name).Observe(float64(*last.PhysicalUsed))
42
+ }
43
+ if last.LogicalProvisioned != nil {
44
+ c.mx.appliance.space.logicalProvisioned.WithLabelValues(name).Observe(float64(*last.LogicalProvisioned))
45
+ }
46
+ if last.LogicalUsed != nil {
47
+ c.mx.appliance.space.logicalUsed.WithLabelValues(name).Observe(float64(*last.LogicalUsed))
48
+ }
49
+ if last.DataPhysicalUsed != nil {
50
+ c.mx.appliance.space.dataPhysicalUsed.WithLabelValues(name).Observe(float64(*last.DataPhysicalUsed))
51
+ }
52
+ if last.SharedLogicalUsed != nil {
53
+ c.mx.appliance.space.sharedLogicalUsed.WithLabelValues(name).Observe(float64(*last.SharedLogicalUsed))
54
+ }
55
+ c.mx.appliance.space.efficiencyRatio.WithLabelValues(name).Observe(last.EfficiencyRatio)
56
+ c.mx.appliance.space.dataReduction.WithLabelValues(name).Observe(last.DataReduction)
57
+ c.mx.appliance.space.snapshotSavings.WithLabelValues(name).Observe(last.SnapshotSavings)
58
+ c.mx.appliance.space.thinSavings.WithLabelValues(name).Observe(last.ThinSavings)
59
+ }
60
+ }(id, app.Name)
61
+ }
62
+
63
+ wg.Wait()
64
+}
src/go/plugin/go.d/collector/powerstore/collect_cluster.go
new
+47
@@ -0,0 +1,47 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+func (c *Collector) collectClusterSpace() {
6
+ if len(c.discovered.clusters) == 0 {
7
+ return
8
+ }
9
+
10
+ c.sem <- struct{}{}
11
+ defer func() { <-c.sem }()
12
+
13
+ cl := c.discovered.clusters[0]
14
+ sm, err := c.client.SpaceMetricsByCluster(cl.ID)
15
+ if err != nil {
16
+ c.Warningf("error collecting cluster space metrics: %v", err)
17
+ return
18
+ }
19
+ if len(sm) == 0 {
20
+ return
21
+ }
22
+
23
+ last := sm[len(sm)-1]
24
+ mx := c.mx.cluster
25
+ if last.PhysicalTotal != nil {
26
+ mx.spacePhysicalTotal.Observe(float64(*last.PhysicalTotal))
27
+ }
28
+ if last.PhysicalUsed != nil {
29
+ mx.spacePhysicalUsed.Observe(float64(*last.PhysicalUsed))
30
+ }
31
+ if last.LogicalProvisioned != nil {
32
+ mx.spaceLogicalProvisioned.Observe(float64(*last.LogicalProvisioned))
33
+ }
34
+ if last.LogicalUsed != nil {
35
+ mx.spaceLogicalUsed.Observe(float64(*last.LogicalUsed))
36
+ }
37
+ if last.DataPhysicalUsed != nil {
38
+ mx.spaceDataPhysicalUsed.Observe(float64(*last.DataPhysicalUsed))
39
+ }
40
+ if last.SharedLogicalUsed != nil {
41
+ mx.spaceSharedLogicalUsed.Observe(float64(*last.SharedLogicalUsed))
42
+ }
43
+ mx.spaceEfficiencyRatio.Observe(float64(last.EfficiencyRatio))
44
+ mx.spaceDataReduction.Observe(float64(last.DataReduction))
45
+ mx.spaceSnapshotSavings.Observe(float64(last.SnapshotSavings))
46
+ mx.spaceThinSavings.Observe(float64(last.ThinSavings))
47
+}
src/go/plugin/go.d/collector/powerstore/collect_drives.go
new
+32
@@ -0,0 +1,32 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import "sync"
6
+
7
+func (c *Collector) collectDriveWear() {
8
+ var wg sync.WaitGroup
9
+
10
+ for id, drv := range c.discovered.drives {
11
+ wg.Add(1)
12
+ go func(id, name string) {
13
+ defer wg.Done()
14
+ c.sem <- struct{}{}
15
+ defer func() { <-c.sem }()
16
+
17
+ wm, err := c.client.WearMetricsByDrive(id)
18
+ if err != nil {
19
+ c.Warningf("error collecting drive %s wear metrics: %v", id, err)
20
+ return
21
+ }
22
+ if len(wm) == 0 {
23
+ return
24
+ }
25
+
26
+ last := wm[len(wm)-1]
27
+ c.mx.drive.enduranceRemaining.WithLabelValues(name).Observe(float64(last.PercentEnduranceRemaining))
28
+ }(id, drv.Name)
29
+ }
30
+
31
+ wg.Wait()
32
+}
src/go/plugin/go.d/collector/powerstore/collect_filesystem.go
new
+34
@@ -0,0 +1,34 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import "sync"
6
+
7
+func (c *Collector) collectFileSystems() {
8
+ var wg sync.WaitGroup
9
+
10
+ for id, fs := range c.discovered.fileSystems {
11
+ wg.Add(1)
12
+ go func(id, name string) {
13
+ defer wg.Done()
14
+ c.sem <- struct{}{}
15
+ defer func() { <-c.sem }()
16
+
17
+ pm, err := c.client.PerformanceMetricsByFileSystem(id)
18
+ if err != nil {
19
+ c.Warningf("error collecting filesystem %s perf metrics: %v", id, err)
20
+ } else if len(pm) > 0 {
21
+ last := pm[len(pm)-1]
22
+ c.mx.fileSystem.perf.readIops.WithLabelValues(name).Observe(last.ReadIops)
23
+ c.mx.fileSystem.perf.writeIops.WithLabelValues(name).Observe(last.WriteIops)
24
+ c.mx.fileSystem.perf.readBandwidth.WithLabelValues(name).Observe(last.ReadBandwidth)
25
+ c.mx.fileSystem.perf.writeBandwidth.WithLabelValues(name).Observe(last.WriteBandwidth)
26
+ c.mx.fileSystem.perf.avgReadLatency.WithLabelValues(name).Observe(last.AvgReadLatency)
27
+ c.mx.fileSystem.perf.avgWriteLatency.WithLabelValues(name).Observe(last.AvgWriteLatency)
28
+ c.mx.fileSystem.perf.avgLatency.WithLabelValues(name).Observe(last.AvgLatency)
29
+ }
30
+ }(id, fs.Name)
31
+ }
32
+
33
+ wg.Wait()
34
+}
src/go/plugin/go.d/collector/powerstore/collect_hardware.go
new
+79
@@ -0,0 +1,79 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+// collectHardwareHealth classifies hardware components by lifecycle state.
6
+// Uses cached hardware data from discovery to avoid redundant API calls.
7
+func (c *Collector) collectHardwareHealth() {
8
+ var fanOK, fanDeg, fanFail, fanUnk float64
9
+ var psuOK, psuDeg, psuFail, psuUnk float64
10
+ var drvOK, drvDeg, drvFail, drvUnk float64
11
+ var batOK, batDeg, batFail, batUnk float64
12
+ var nodOK, nodDeg, nodFail, nodUnk float64
13
+
14
+ for _, h := range c.discovered.hardware {
15
+ ok, deg, fail, unk := classifyState(h.LifecycleState)
16
+ switch h.Type {
17
+ case "Fan":
18
+ fanOK += ok
19
+ fanDeg += deg
20
+ fanFail += fail
21
+ fanUnk += unk
22
+ case "Power_Supply":
23
+ psuOK += ok
24
+ psuDeg += deg
25
+ psuFail += fail
26
+ psuUnk += unk
27
+ case "Drive":
28
+ drvOK += ok
29
+ drvDeg += deg
30
+ drvFail += fail
31
+ drvUnk += unk
32
+ case "Battery":
33
+ batOK += ok
34
+ batDeg += deg
35
+ batFail += fail
36
+ batUnk += unk
37
+ case "Node":
38
+ nodOK += ok
39
+ nodDeg += deg
40
+ nodFail += fail
41
+ nodUnk += unk
42
+ }
43
+ }
44
+
45
+ hw := c.mx.hardware
46
+ hw.fanOK.Observe(fanOK)
47
+ hw.fanDegraded.Observe(fanDeg)
48
+ hw.fanFailed.Observe(fanFail)
49
+ hw.fanUnknown.Observe(fanUnk)
50
+ hw.psuOK.Observe(psuOK)
51
+ hw.psuDegraded.Observe(psuDeg)
52
+ hw.psuFailed.Observe(psuFail)
53
+ hw.psuUnknown.Observe(psuUnk)
54
+ hw.driveOK.Observe(drvOK)
55
+ hw.driveDegraded.Observe(drvDeg)
56
+ hw.driveFailed.Observe(drvFail)
57
+ hw.driveUnknown.Observe(drvUnk)
58
+ hw.batteryOK.Observe(batOK)
59
+ hw.batteryDegraded.Observe(batDeg)
60
+ hw.batteryFailed.Observe(batFail)
61
+ hw.batteryUnknown.Observe(batUnk)
62
+ hw.nodeOK.Observe(nodOK)
63
+ hw.nodeDegraded.Observe(nodDeg)
64
+ hw.nodeFailed.Observe(nodFail)
65
+ hw.nodeUnknown.Observe(nodUnk)
66
+}
67
+
68
+func classifyState(state string) (ok, degraded, failed, unknown float64) {
69
+ switch state {
70
+ case "Healthy", "healthy":
71
+ return 1, 0, 0, 0
72
+ case "Degraded", "degraded":
73
+ return 0, 1, 0, 0
74
+ case "Failed", "failed", "Error", "error":
75
+ return 0, 0, 1, 0
76
+ default:
77
+ return 0, 0, 0, 1
78
+ }
79
+}
src/go/plugin/go.d/collector/powerstore/collect_nas.go
new
+25
@@ -0,0 +1,25 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+func (c *Collector) collectNASStatus() {
6
+ var started, stopped, degraded, unknown float64
7
+
8
+ for _, nas := range c.discovered.nasServers {
9
+ switch nas.OperationalStatus {
10
+ case "Started":
11
+ started++
12
+ case "Stopped":
13
+ stopped++
14
+ case "Degraded":
15
+ degraded++
16
+ default:
17
+ unknown++
18
+ }
19
+ }
20
+
21
+ c.mx.nas.started.Observe(started)
22
+ c.mx.nas.stopped.Observe(stopped)
23
+ c.mx.nas.degraded.Observe(degraded)
24
+ c.mx.nas.unknown.Observe(unknown)
25
+}
src/go/plugin/go.d/collector/powerstore/collect_node.go
new
+37
@@ -0,0 +1,37 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import "sync"
6
+
7
+func (c *Collector) collectNodes() {
8
+ var wg sync.WaitGroup
9
+
10
+ for id, node := range c.discovered.nodes {
11
+ wg.Add(1)
12
+ go func(id, name string) {
13
+ defer wg.Done()
14
+ c.sem <- struct{}{}
15
+ defer func() { <-c.sem }()
16
+
17
+ pm, err := c.client.PerformanceMetricsByNode(id)
18
+ if err != nil {
19
+ c.Warningf("error collecting node %s perf metrics: %v", id, err)
20
+ } else if len(pm) > 0 {
21
+ last := pm[len(pm)-1]
22
+ c.mx.node.perf.readIops.WithLabelValues(name).Observe(last.ReadIops)
23
+ c.mx.node.perf.writeIops.WithLabelValues(name).Observe(last.WriteIops)
24
+ c.mx.node.perf.readBandwidth.WithLabelValues(name).Observe(last.ReadBandwidth)
25
+ c.mx.node.perf.writeBandwidth.WithLabelValues(name).Observe(last.WriteBandwidth)
26
+ c.mx.node.perf.avgReadLatency.WithLabelValues(name).Observe(last.AvgReadLatency)
27
+ c.mx.node.perf.avgWriteLatency.WithLabelValues(name).Observe(last.AvgWriteLatency)
28
+ c.mx.node.perf.avgLatency.WithLabelValues(name).Observe(last.AvgLatency)
29
+ if last.CurrentLogins != nil {
30
+ c.mx.node.currentLogins.WithLabelValues(name).Observe(float64(*last.CurrentLogins))
31
+ }
32
+ }
33
+ }(id, node.Name)
34
+ }
35
+
36
+ wg.Wait()
37
+}
src/go/plugin/go.d/collector/powerstore/collect_ports.go
new
+75
@@ -0,0 +1,75 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import "sync"
6
+
7
+func (c *Collector) collectFcPorts() {
8
+ var wg sync.WaitGroup
9
+
10
+ for id, port := range c.discovered.fcPorts {
11
+ wg.Add(1)
12
+ go func(id, name string, linkUp bool) {
13
+ defer wg.Done()
14
+ c.sem <- struct{}{}
15
+ defer func() { <-c.sem }()
16
+
17
+ var linkVal float64
18
+ if linkUp {
19
+ linkVal = 1
20
+ }
21
+ c.mx.fcPort.linkUp.WithLabelValues(name).Observe(linkVal)
22
+
23
+ pm, err := c.client.PerformanceMetricsByFcPort(id)
24
+ if err != nil {
25
+ c.Warningf("error collecting FC port %s perf metrics: %v", id, err)
26
+ } else if len(pm) > 0 {
27
+ last := pm[len(pm)-1]
28
+ c.mx.fcPort.perf.readIops.WithLabelValues(name).Observe(last.ReadIops)
29
+ c.mx.fcPort.perf.writeIops.WithLabelValues(name).Observe(last.WriteIops)
30
+ c.mx.fcPort.perf.readBandwidth.WithLabelValues(name).Observe(last.ReadBandwidth)
31
+ c.mx.fcPort.perf.writeBandwidth.WithLabelValues(name).Observe(last.WriteBandwidth)
32
+ c.mx.fcPort.perf.avgReadLatency.WithLabelValues(name).Observe(last.AvgReadLatency)
33
+ c.mx.fcPort.perf.avgWriteLatency.WithLabelValues(name).Observe(last.AvgWriteLatency)
34
+ c.mx.fcPort.perf.avgLatency.WithLabelValues(name).Observe(last.AvgLatency)
35
+ }
36
+ }(id, port.Name, port.IsLinkUp)
37
+ }
38
+
39
+ wg.Wait()
40
+}
41
+
42
+func (c *Collector) collectEthPorts() {
43
+ var wg sync.WaitGroup
44
+
45
+ for id, port := range c.discovered.ethPorts {
46
+ wg.Add(1)
47
+ go func(id, name string, linkUp bool) {
48
+ defer wg.Done()
49
+ c.sem <- struct{}{}
50
+ defer func() { <-c.sem }()
51
+
52
+ var linkVal float64
53
+ if linkUp {
54
+ linkVal = 1
55
+ }
56
+ c.mx.ethPort.linkUp.WithLabelValues(name).Observe(linkVal)
57
+
58
+ pm, err := c.client.EthPortPerformanceMetrics(id)
59
+ if err != nil {
60
+ c.Warningf("error collecting ETH port %s perf metrics: %v", id, err)
61
+ } else if len(pm) > 0 {
62
+ last := pm[len(pm)-1]
63
+ c.mx.ethPort.bytesRxPs.WithLabelValues(name).Observe(float64(last.BytesRxPs))
64
+ c.mx.ethPort.bytesTxPs.WithLabelValues(name).Observe(float64(last.BytesTxPs))
65
+ c.mx.ethPort.pktRxPs.WithLabelValues(name).Observe(float64(last.PktRxPs))
66
+ c.mx.ethPort.pktTxPs.WithLabelValues(name).Observe(float64(last.PktTxPs))
67
+ c.mx.ethPort.pktRxCrcErrorPs.WithLabelValues(name).Observe(float64(last.PktRxCrcErrorPs))
68
+ c.mx.ethPort.pktRxNoBufferErrorPs.WithLabelValues(name).Observe(float64(last.PktRxNoBufferErrorPs))
69
+ c.mx.ethPort.pktTxErrorPs.WithLabelValues(name).Observe(float64(last.PktTxErrorPs))
70
+ }
71
+ }(id, port.Name, port.IsLinkUp)
72
+ }
73
+
74
+ wg.Wait()
75
+}
src/go/plugin/go.d/collector/powerstore/collect_replication.go
new
+49
@@ -0,0 +1,49 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import "sync"
6
+
7
+func (c *Collector) collectReplication() {
8
+ var wg sync.WaitGroup
9
+ var mu sync.Mutex
10
+
11
+ var dataRemaining, dataTransferred int64
12
+ var transferRate float64
13
+
14
+ for id := range c.discovered.appliances {
15
+ wg.Add(1)
16
+ go func(id string) {
17
+ defer wg.Done()
18
+ c.sem <- struct{}{}
19
+ defer func() { <-c.sem }()
20
+
21
+ cm, err := c.client.CopyMetricsByAppliance(id)
22
+ if err != nil {
23
+ c.Warningf("error collecting appliance %s copy metrics: %v", id, err)
24
+ return
25
+ }
26
+ if len(cm) == 0 {
27
+ return
28
+ }
29
+
30
+ last := cm[len(cm)-1]
31
+
32
+ mu.Lock()
33
+ if last.DataRemaining != nil {
34
+ dataRemaining += *last.DataRemaining
35
+ }
36
+ if last.DataTransferred != nil {
37
+ dataTransferred += *last.DataTransferred
38
+ }
39
+ transferRate += last.TransferRate
40
+ mu.Unlock()
41
+ }(id)
42
+ }
43
+
44
+ wg.Wait()
45
+
46
+ c.mx.replication.dataRemaining.Observe(float64(dataRemaining))
47
+ c.mx.replication.dataTransferred.Observe(float64(dataTransferred))
48
+ c.mx.replication.transferRate.Observe(transferRate)
49
+}
src/go/plugin/go.d/collector/powerstore/collect_volume.go
new
+48
@@ -0,0 +1,48 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import "sync"
6
+
7
+func (c *Collector) collectVolumes() {
8
+ var wg sync.WaitGroup
9
+
10
+ for id, vol := range c.discovered.volumes {
11
+ wg.Add(1)
12
+ go func(id, name string) {
13
+ defer wg.Done()
14
+ c.sem <- struct{}{}
15
+ defer func() { <-c.sem }()
16
+
17
+ pm, err := c.client.PerformanceMetricsByVolume(id)
18
+ if err != nil {
19
+ c.Warningf("error collecting volume %s perf metrics: %v", id, err)
20
+ } else if len(pm) > 0 {
21
+ last := pm[len(pm)-1]
22
+ c.mx.volume.perf.readIops.WithLabelValues(name).Observe(last.ReadIops)
23
+ c.mx.volume.perf.writeIops.WithLabelValues(name).Observe(last.WriteIops)
24
+ c.mx.volume.perf.readBandwidth.WithLabelValues(name).Observe(last.ReadBandwidth)
25
+ c.mx.volume.perf.writeBandwidth.WithLabelValues(name).Observe(last.WriteBandwidth)
26
+ c.mx.volume.perf.avgReadLatency.WithLabelValues(name).Observe(last.AvgReadLatency)
27
+ c.mx.volume.perf.avgWriteLatency.WithLabelValues(name).Observe(last.AvgWriteLatency)
28
+ c.mx.volume.perf.avgLatency.WithLabelValues(name).Observe(last.AvgLatency)
29
+ }
30
+
31
+ sm, err := c.client.SpaceMetricsByVolume(id)
32
+ if err != nil {
33
+ c.Warningf("error collecting volume %s space metrics: %v", id, err)
34
+ } else if len(sm) > 0 {
35
+ last := sm[len(sm)-1]
36
+ if last.LogicalProvisioned != nil {
37
+ c.mx.volume.spaceLogicalProv.WithLabelValues(name).Observe(float64(*last.LogicalProvisioned))
38
+ }
39
+ if last.LogicalUsed != nil {
40
+ c.mx.volume.spaceLogicalUsed.WithLabelValues(name).Observe(float64(*last.LogicalUsed))
41
+ }
42
+ c.mx.volume.spaceThinSavings.WithLabelValues(name).Observe(last.ThinSavings)
43
+ }
44
+ }(id, vol.Name)
45
+ }
46
+
47
+ wg.Wait()
48
+}
src/go/plugin/go.d/collector/powerstore/collector.go
new
+156
@@ -0,0 +1,156 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import (
6
+ "context"
7
+ _ "embed"
8
+ "errors"
9
+ "fmt"
10
+ "time"
11
+
12
+ "github.com/netdata/netdata/go/plugins/pkg/confopt"
13
+ "github.com/netdata/netdata/go/plugins/pkg/matcher"
14
+ "github.com/netdata/netdata/go/plugins/pkg/metrix"
15
+ "github.com/netdata/netdata/go/plugins/pkg/web"
16
+ "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
17
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/powerstore/client"
18
+)
19
+
20
+//go:embed "config_schema.json"
21
+var configSchema string
22
+
23
+//go:embed "charts.yaml"
24
+var chartTemplateYAML string
25
+
26
+func init() {
27
+ collectorapi.Register("powerstore", collectorapi.Creator{
28
+ JobConfigSchema: configSchema,
29
+ Defaults: collectorapi.Defaults{
30
+ UpdateEvery: 30,
31
+ },
32
+ CreateV2: func() collectorapi.CollectorV2 { return New() },
33
+ Config: func() any { return &Config{} },
34
+ })
35
+}
36
+
37
+const maxConcurrentAPIRequests = 10
38
+
39
+func New() *Collector {
40
+ store := metrix.NewCollectorStore()
41
+ mx := newCollectorMetrics(store)
42
+
43
+ return &Collector{
44
+ Config: Config{
45
+ HTTPConfig: web.HTTPConfig{
46
+ RequestConfig: web.RequestConfig{
47
+ URL: "https://127.0.0.1",
48
+ },
49
+ ClientConfig: web.ClientConfig{
50
+ Timeout: confopt.Duration(30 * time.Second),
51
+ },
52
+ },
53
+ },
54
+ store: store,
55
+ mx: mx,
56
+ sem: make(chan struct{}, maxConcurrentAPIRequests),
57
+ }
58
+}
59
+
60
+type Config struct {
61
+ Vnode string `yaml:"vnode,omitempty" json:"vnode"`
62
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
63
+ AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
64
+ web.HTTPConfig `yaml:",inline" json:""`
65
+ VolumeSelector string `yaml:"volume_selector,omitempty" json:"volume_selector"`
66
+}
67
+
68
+type (
69
+ Collector struct {
70
+ collectorapi.Base
71
+ Config `yaml:",inline" json:""`
72
+
73
+ store metrix.CollectorStore
74
+ mx *collectorMetrics
75
+
76
+ client *client.Client
77
+
78
+ discovered discovered
79
+ lastDiscoveryOK bool
80
+ runs int
81
+
82
+ volMatcher matcher.Matcher
83
+ sem chan struct{} // limits concurrent API calls
84
+ }
85
+ discovered struct {
86
+ clusters []client.Cluster
87
+ appliances map[string]client.Appliance
88
+ volumes map[string]client.Volume
89
+ nodes map[string]client.Node
90
+ fcPorts map[string]client.FcPort
91
+ ethPorts map[string]client.EthPort
92
+ fileSystems map[string]client.FileSystem
93
+ nasServers map[string]client.NAS
94
+ drives map[string]client.Hardware
95
+ hardware []client.Hardware
96
+ }
97
+)
98
+
99
+func (c *Collector) Configuration() any {
100
+ return c.Config
101
+}
102
+
103
+func (c *Collector) Init(context.Context) error {
104
+ if c.Username == "" || c.Password == "" {
105
+ return errors.New("config: username and password aren't set")
106
+ }
107
+
108
+ cli, err := client.New(c.ClientConfig, c.RequestConfig)
109
+ if err != nil {
110
+ return fmt.Errorf("error creating PowerStore client: %v", err)
111
+ }
112
+ c.client = cli
113
+
114
+ if c.VolumeSelector != "" {
115
+ m, err := matcher.NewSimplePatternsMatcher(c.VolumeSelector)
116
+ if err != nil {
117
+ return fmt.Errorf("error creating volume selector: %v", err)
118
+ }
119
+ c.volMatcher = m
120
+ }
121
+
122
+ c.Debugf("using URL %s", c.URL)
123
+ c.Debugf("using timeout: %s", c.Timeout)
124
+
125
+ return nil
126
+}
127
+
128
+func (c *Collector) volumeMatches(name string) bool {
129
+ if c.volMatcher == nil {
130
+ return true
131
+ }
132
+ return c.volMatcher.MatchString(name)
133
+}
134
+
135
+func (c *Collector) Check(context.Context) error {
136
+ if err := c.client.Login(); err != nil {
137
+ return err
138
+ }
139
+ // Verify API access by running discovery (no metrics written).
140
+ return c.discovery()
141
+}
142
+
143
+func (c *Collector) Collect(context.Context) error {
144
+ return c.collect()
145
+}
146
+
147
+func (c *Collector) MetricStore() metrix.CollectorStore { return c.store }
148
+
149
+func (c *Collector) ChartTemplateYAML() string { return chartTemplateYAML }
150
+
151
+func (c *Collector) Cleanup(context.Context) {
152
+ if c.client == nil {
153
+ return
154
+ }
155
+ c.client.Logout()
156
+}
src/go/plugin/go.d/collector/powerstore/collector_test.go
new
+384
@@ -0,0 +1,384 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import (
6
+ "context"
7
+ "encoding/json"
8
+ "net/http"
9
+ "net/http/httptest"
10
+ "os"
11
+ "strings"
12
+ "testing"
13
+
14
+ "github.com/netdata/netdata/go/plugins/pkg/metrix"
15
+ "github.com/netdata/netdata/go/plugins/pkg/web"
16
+ "github.com/netdata/netdata/go/plugins/plugin/framework/chartengine"
17
+ "github.com/netdata/netdata/go/plugins/plugin/framework/charttpl"
18
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
19
+ "github.com/stretchr/testify/assert"
20
+ "github.com/stretchr/testify/require"
21
+)
22
+
23
+func TestCollector_Init(t *testing.T) {
24
+ tests := map[string]struct {
25
+ config Config
26
+ wantFail bool
27
+ }{
28
+ "success with valid config": {
29
+ config: Config{
30
+ HTTPConfig: web.HTTPConfig{
31
+ RequestConfig: web.RequestConfig{
32
+ URL: "https://127.0.0.1",
33
+ Username: "admin",
34
+ Password: "password",
35
+ },
36
+ },
37
+ },
38
+ },
39
+ "fail without username": {
40
+ wantFail: true,
41
+ config: Config{
42
+ HTTPConfig: web.HTTPConfig{
43
+ RequestConfig: web.RequestConfig{
44
+ URL: "https://127.0.0.1",
45
+ Password: "password",
46
+ },
47
+ },
48
+ },
49
+ },
50
+ "fail without password": {
51
+ wantFail: true,
52
+ config: Config{
53
+ HTTPConfig: web.HTTPConfig{
54
+ RequestConfig: web.RequestConfig{
55
+ URL: "https://127.0.0.1",
56
+ Username: "admin",
57
+ },
58
+ },
59
+ },
60
+ },
61
+ }
62
+
63
+ for name, test := range tests {
64
+ t.Run(name, func(t *testing.T) {
65
+ collr := New()
66
+ collr.Config = test.config
67
+
68
+ if test.wantFail {
69
+ assert.Error(t, collr.Init(context.Background()))
70
+ } else {
71
+ assert.NoError(t, collr.Init(context.Background()))
72
+ }
73
+ })
74
+ }
75
+}
76
+
77
+func TestCollector_Check(t *testing.T) {
78
+ srv := newMockPowerStoreServer()
79
+ defer srv.Close()
80
+
81
+ collr := New()
82
+ collr.Config = Config{
83
+ HTTPConfig: web.HTTPConfig{
84
+ RequestConfig: web.RequestConfig{
85
+ URL: srv.URL,
86
+ Username: "admin",
87
+ Password: "password",
88
+ },
89
+ },
90
+ }
91
+
92
+ require.NoError(t, collr.Init(context.Background()))
93
+ assert.NoError(t, collr.Check(context.Background()))
94
+}
95
+
96
+func TestCollector_Collect(t *testing.T) {
97
+ srv := newMockPowerStoreServer()
98
+ defer srv.Close()
99
+
100
+ collr := New()
101
+ collr.Config = Config{
102
+ HTTPConfig: web.HTTPConfig{
103
+ RequestConfig: web.RequestConfig{
104
+ URL: srv.URL,
105
+ Username: "admin",
106
+ Password: "password",
107
+ },
108
+ },
109
+ }
110
+
111
+ require.NoError(t, collr.Init(context.Background()))
112
+ require.NoError(t, collr.Check(context.Background()))
113
+
114
+ cc := mustCycleController(t, collr.MetricStore())
115
+ cc.BeginCycle()
116
+ require.NoError(t, collr.Collect(context.Background()))
117
+ cc.CommitCycleSuccess()
118
+
119
+ r := collr.MetricStore().Read(metrix.ReadRaw())
120
+
121
+ // Cluster space metrics
122
+ assertValue(t, r, "cluster_space_physical_total", nil, 10995116277760)
123
+ assertValue(t, r, "cluster_space_physical_used", nil, 5497558138880)
124
+ assertValue(t, r, "cluster_space_efficiency_ratio", nil, 2.5)
125
+
126
+ // Appliance metrics
127
+ appLabels := metrix.Labels{"appliance": "Appliance-1"}
128
+ assertHasValue(t, r, "appliance_perf_read_iops", appLabels)
129
+ assertHasValue(t, r, "appliance_cpu_utilization", appLabels)
130
+ assertHasValue(t, r, "appliance_space_physical_total", appLabels)
131
+ assertHasValue(t, r, "appliance_space_logical_provisioned", appLabels)
132
+ assertHasValue(t, r, "appliance_space_logical_used", appLabels)
133
+ assertHasValue(t, r, "appliance_space_data_physical_used", appLabels)
134
+ assertHasValue(t, r, "appliance_space_shared_logical_used", appLabels)
135
+ assertValue(t, r, "appliance_space_efficiency_ratio", appLabels, 2.5)
136
+ assertValue(t, r, "appliance_space_data_reduction", appLabels, 1.8)
137
+ assertValue(t, r, "appliance_space_snapshot_savings", appLabels, 0.3)
138
+ assertValue(t, r, "appliance_space_thin_savings", appLabels, 0.7)
139
+
140
+ // Volume metrics
141
+ v1Labels := metrix.Labels{"volume": "prod-vol-01"}
142
+ v2Labels := metrix.Labels{"volume": "test-vol-01"}
143
+ assertHasValue(t, r, "volume_perf_read_iops", v1Labels)
144
+ assertHasValue(t, r, "volume_perf_read_iops", v2Labels)
145
+ assertHasValue(t, r, "volume_space_logical_provisioned", v1Labels)
146
+ assertHasValue(t, r, "volume_space_thin_savings", v1Labels)
147
+
148
+ // Node metrics
149
+ nodeLabels := metrix.Labels{"node": "Node-A"}
150
+ assertHasValue(t, r, "node_perf_read_iops", nodeLabels)
151
+ assertValue(t, r, "node_current_logins", nodeLabels, 5)
152
+
153
+ // FC port metrics
154
+ fcLabels := metrix.Labels{"fc_port": "FC-Port-0"}
155
+ assertHasValue(t, r, "fc_port_perf_read_iops", fcLabels)
156
+ assertHasValue(t, r, "fc_port_perf_avg_read_latency", fcLabels)
157
+ assertValue(t, r, "fc_port_link_up", fcLabels, 1)
158
+
159
+ // ETH port metrics
160
+ ethLabels := metrix.Labels{"eth_port": "ETH-Port-0"}
161
+ assertHasValue(t, r, "eth_port_bytes_rx_ps", ethLabels)
162
+ assertValue(t, r, "eth_port_link_up", ethLabels, 1)
163
+
164
+ // Filesystem metrics
165
+ fsLabels := metrix.Labels{"filesystem": "nfs-share-01"}
166
+ assertHasValue(t, r, "file_system_perf_read_iops", fsLabels)
167
+
168
+ // Hardware health metrics (testdata: 2 fans OK, 1 degraded; 2 PSUs; 2 drives; 1 battery; 2 nodes)
169
+ assertValue(t, r, "hardware_fan_ok", nil, 2)
170
+ assertValue(t, r, "hardware_fan_degraded", nil, 1)
171
+ assertValue(t, r, "hardware_psu_ok", nil, 2)
172
+ assertValue(t, r, "hardware_drive_ok", nil, 2)
173
+ assertValue(t, r, "hardware_battery_ok", nil, 1)
174
+ assertValue(t, r, "hardware_node_ok", nil, 2)
175
+
176
+ // Alert metrics
177
+ assertValue(t, r, "alerts_minor", nil, 1)
178
+ assertValue(t, r, "alerts_info", nil, 1)
179
+ assertValue(t, r, "alerts_critical", nil, 0)
180
+
181
+ // Drive wear metrics (D1 = "Drive-1" from hardware_all.json)
182
+ driveLabels := metrix.Labels{"drive": "Drive-1"}
183
+ assertHasValue(t, r, "drive_endurance_remaining", driveLabels)
184
+
185
+ // NAS status metrics
186
+ assertValue(t, r, "nas_started", nil, 1)
187
+ assertValue(t, r, "nas_stopped", nil, 0)
188
+
189
+ // Replication metrics
190
+ assertValue(t, r, "copy_data_remaining", nil, 1073741824)
191
+ assertValue(t, r, "copy_data_transferred", nil, 5368709120)
192
+ assertHasValue(t, r, "copy_transfer_rate", nil)
193
+
194
+ // Chart coverage: verify all chart template dimensions are materialized
195
+ collecttest.AssertChartCoverage(t, collr, collecttest.ChartCoverageExpectation{})
196
+}
197
+
198
+func TestCollector_CollectWithVolumeSelector(t *testing.T) {
199
+ srv := newMockPowerStoreServer()
200
+ defer srv.Close()
201
+
202
+ collr := New()
203
+ collr.Config = Config{
204
+ HTTPConfig: web.HTTPConfig{
205
+ RequestConfig: web.RequestConfig{
206
+ URL: srv.URL,
207
+ Username: "admin",
208
+ Password: "password",
209
+ },
210
+ },
211
+ VolumeSelector: "prod-*",
212
+ }
213
+
214
+ require.NoError(t, collr.Init(context.Background()))
215
+ require.NoError(t, collr.Check(context.Background()))
216
+
217
+ cc := mustCycleController(t, collr.MetricStore())
218
+ cc.BeginCycle()
219
+ require.NoError(t, collr.Collect(context.Background()))
220
+ cc.CommitCycleSuccess()
221
+
222
+ r := collr.MetricStore().Read(metrix.ReadRaw())
223
+
224
+ // prod-vol-01 should be included
225
+ assertHasValue(t, r, "volume_perf_read_iops", metrix.Labels{"volume": "prod-vol-01"})
226
+ // test-vol-01 should be excluded
227
+ _, ok := r.Value("volume_perf_read_iops", metrix.Labels{"volume": "test-vol-01"})
228
+ assert.False(t, ok, "test-vol-01 should be excluded by volume selector")
229
+}
230
+
231
+func TestCollector_Cleanup(t *testing.T) {
232
+ collr := New()
233
+ assert.NotPanics(t, func() { collr.Cleanup(context.Background()) })
234
+
235
+ srv := newMockPowerStoreServer()
236
+ defer srv.Close()
237
+
238
+ collr.Config = Config{
239
+ HTTPConfig: web.HTTPConfig{
240
+ RequestConfig: web.RequestConfig{
241
+ URL: srv.URL,
242
+ Username: "admin",
243
+ Password: "password",
244
+ },
245
+ },
246
+ }
247
+
248
+ require.NoError(t, collr.Init(context.Background()))
249
+ require.NoError(t, collr.Check(context.Background()))
250
+ assert.NotPanics(t, func() { collr.Cleanup(context.Background()) })
251
+}
252
+
253
+func TestCollector_ChartTemplateYAML(t *testing.T) {
254
+ templateYAML := New().ChartTemplateYAML()
255
+ collecttest.AssertChartTemplateSchema(t, templateYAML)
256
+
257
+ spec, err := charttpl.DecodeYAML([]byte(templateYAML))
258
+ require.NoError(t, err)
259
+ require.NoError(t, spec.Validate())
260
+
261
+ _, err = chartengine.Compile(spec, 1)
262
+ require.NoError(t, err)
263
+}
264
+
265
+func mustCycleController(t *testing.T, store metrix.CollectorStore) metrix.CycleController {
266
+ t.Helper()
267
+ managed, ok := metrix.AsCycleManagedStore(store)
268
+ require.True(t, ok, "store does not expose cycle control")
269
+ return managed.CycleController()
270
+}
271
+
272
+func assertValue(t *testing.T, r metrix.Reader, name string, labels metrix.Labels, want float64) {
273
+ t.Helper()
274
+ got, ok := r.Value(name, labels)
275
+ require.Truef(t, ok, "expected metric %s labels=%v", name, labels)
276
+ assert.InDeltaf(t, want, got, 1e-9, "unexpected value for %s labels=%v: got %v want %v", name, labels, got, want)
277
+}
278
+
279
+func assertHasValue(t *testing.T, r metrix.Reader, name string, labels metrix.Labels) {
280
+ t.Helper()
281
+ _, ok := r.Value(name, labels)
282
+ assert.Truef(t, ok, "expected metric %s labels=%v to exist", name, labels)
283
+}
284
+
285
+func newMockPowerStoreServer() *httptest.Server {
286
+ return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
287
+ w.Header().Set("DELL-EMC-TOKEN", "mock-csrf-token")
288
+ w.Header().Set("Content-Type", "application/json")
289
+
290
+ p := r.URL.Path
291
+
292
+ switch {
293
+ case strings.HasSuffix(p, "/login_session"):
294
+ w.WriteHeader(http.StatusOK)
295
+ _, _ = w.Write([]byte(`{"id":"session-1"}`))
296
+ case strings.HasSuffix(p, "/cluster"):
297
+ writeTestData(w, "testdata/cluster.json")
298
+ case strings.HasSuffix(p, "/appliance"):
299
+ writeTestData(w, "testdata/appliance.json")
300
+ case strings.HasSuffix(p, "/volume"):
301
+ writeTestData(w, "testdata/volume.json")
302
+ case strings.HasSuffix(p, "/hardware"):
303
+ hwType := r.URL.Query().Get("type")
304
+ switch hwType {
305
+ case "eq.Node":
306
+ writeTestData(w, "testdata/hardware_node.json")
307
+ case "eq.Fan":
308
+ writeTestData(w, "testdata/hardware_fan.json")
309
+ case "eq.Power_Supply":
310
+ writeTestData(w, "testdata/hardware_psu.json")
311
+ case "eq.Drive":
312
+ writeTestData(w, "testdata/hardware_drive.json")
313
+ case "eq.Battery":
314
+ writeTestData(w, "testdata/hardware_battery.json")
315
+ case "":
316
+ writeTestData(w, "testdata/hardware_all.json")
317
+ default:
318
+ _, _ = w.Write([]byte(`[]`))
319
+ }
320
+ case strings.HasSuffix(p, "/alert"):
321
+ writeTestData(w, "testdata/alert.json")
322
+ case strings.HasSuffix(p, "/fc_port"):
323
+ writeTestData(w, "testdata/fc_port.json")
324
+ case strings.HasSuffix(p, "/eth_port"):
325
+ writeTestData(w, "testdata/eth_port.json")
326
+ case strings.HasSuffix(p, "/file_system"):
327
+ writeTestData(w, "testdata/file_system.json")
328
+ case strings.HasSuffix(p, "/nas_server"):
329
+ writeTestData(w, "testdata/nas_server.json")
330
+ case strings.HasSuffix(p, "/metrics/generate"):
331
+ handleMetricsGenerate(w, r)
332
+ default:
333
+ w.WriteHeader(http.StatusNotFound)
334
+ }
335
+ }))
336
+}
337
+
338
+func handleMetricsGenerate(w http.ResponseWriter, r *http.Request) {
339
+ var body struct {
340
+ Entity string `json:"entity"`
341
+ EntityID string `json:"entity_id"`
342
+ }
343
+ if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
344
+ w.WriteHeader(http.StatusBadRequest)
345
+ return
346
+ }
347
+
348
+ // Route metrics by entity type, validating entity_id to prevent wrong attribution
349
+ switch {
350
+ case body.Entity == "performance_metrics_by_appliance" && body.EntityID == "A1":
351
+ writeTestData(w, "testdata/metrics_perf_appliance.json")
352
+ case body.Entity == "performance_metrics_by_volume" && (body.EntityID == "V1" || body.EntityID == "V2"):
353
+ writeTestData(w, "testdata/metrics_perf_volume.json")
354
+ case body.Entity == "performance_metrics_by_node" && body.EntityID == "N1":
355
+ writeTestData(w, "testdata/metrics_perf_node.json")
356
+ case body.Entity == "performance_metrics_by_fe_fc_port" && body.EntityID == "FC1":
357
+ writeTestData(w, "testdata/metrics_perf_fc_port.json")
358
+ case body.Entity == "performance_metrics_by_fe_eth_port" && body.EntityID == "ETH1":
359
+ writeTestData(w, "testdata/metrics_perf_eth_port.json")
360
+ case body.Entity == "performance_metrics_by_file_system" && body.EntityID == "FS1":
361
+ writeTestData(w, "testdata/metrics_perf_filesystem.json")
362
+ case body.Entity == "space_metrics_by_cluster":
363
+ writeTestData(w, "testdata/metrics_space_cluster.json")
364
+ case body.Entity == "space_metrics_by_appliance" && body.EntityID == "A1":
365
+ writeTestData(w, "testdata/metrics_space_appliance.json")
366
+ case body.Entity == "space_metrics_by_volume" && (body.EntityID == "V1" || body.EntityID == "V2"):
367
+ writeTestData(w, "testdata/metrics_space_volume.json")
368
+ case body.Entity == "wear_metrics_by_drive" && body.EntityID == "D1":
369
+ writeTestData(w, "testdata/metrics_wear_drive.json")
370
+ case body.Entity == "copy_metrics_by_appliance" && body.EntityID == "A1":
371
+ writeTestData(w, "testdata/metrics_copy_appliance.json")
372
+ default:
373
+ _, _ = w.Write([]byte(`[]`))
374
+ }
375
+}
376
+
377
+func writeTestData(w http.ResponseWriter, path string) {
378
+ data, err := os.ReadFile(path)
379
+ if err != nil {
380
+ w.WriteHeader(http.StatusInternalServerError)
381
+ return
382
+ }
383
+ _, _ = w.Write(data)
384
+}
src/go/plugin/go.d/collector/powerstore/config_schema.json
new
+189
@@ -0,0 +1,189 @@
1
+{
2
+ "jsonSchema": {
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Dell PowerStore 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": 30
13
+ },
14
+ "autodetection_retry": {
15
+ "title": "Detection retry",
16
+ "description": "Recheck interval in seconds. Zero means no recheck will be scheduled.",
17
+ "type": "integer",
18
+ "minimum": 0,
19
+ "default": 0
20
+ },
21
+ "url": {
22
+ "title": "URL",
23
+ "description": "The base URL of the PowerStore management interface.",
24
+ "type": "string",
25
+ "default": "https://127.0.0.1",
26
+ "format": "uri"
27
+ },
28
+ "timeout": {
29
+ "title": "Timeout",
30
+ "description": "The timeout in seconds for HTTP requests.",
31
+ "type": "number",
32
+ "minimum": 0.5,
33
+ "default": 30
34
+ },
35
+ "not_follow_redirects": {
36
+ "title": "Not follow redirects",
37
+ "description": "If set, the client will not follow HTTP redirects automatically.",
38
+ "type": "boolean"
39
+ },
40
+ "username": {
41
+ "title": "Username",
42
+ "description": "The username for PowerStore API authentication.",
43
+ "type": "string",
44
+ "sensitive": true
45
+ },
46
+ "password": {
47
+ "title": "Password",
48
+ "description": "The password for PowerStore API authentication.",
49
+ "type": "string",
50
+ "sensitive": true
51
+ },
52
+ "proxy_url": {
53
+ "title": "Proxy URL",
54
+ "description": "The URL of the proxy server.",
55
+ "type": "string"
56
+ },
57
+ "proxy_username": {
58
+ "title": "Proxy username",
59
+ "description": "The username for proxy authentication.",
60
+ "type": "string",
61
+ "sensitive": true
62
+ },
63
+ "proxy_password": {
64
+ "title": "Proxy password",
65
+ "description": "The password for proxy authentication.",
66
+ "type": "string",
67
+ "sensitive": true
68
+ },
69
+ "headers": {
70
+ "title": "Headers",
71
+ "description": "Additional HTTP headers to include in requests.",
72
+ "type": [
73
+ "object",
74
+ "null"
75
+ ],
76
+ "additionalProperties": {
77
+ "type": "string"
78
+ }
79
+ },
80
+ "tls_skip_verify": {
81
+ "title": "Skip TLS verification",
82
+ "description": "If set, TLS certificate verification will be skipped.",
83
+ "type": "boolean"
84
+ },
85
+ "tls_ca": {
86
+ "title": "TLS CA",
87
+ "description": "The path to the CA certificate file for TLS verification.",
88
+ "type": "string",
89
+ "pattern": "^$|^/"
90
+ },
91
+ "tls_cert": {
92
+ "title": "TLS certificate",
93
+ "description": "The path to the client TLS certificate file.",
94
+ "type": "string",
95
+ "pattern": "^$|^/"
96
+ },
97
+ "tls_key": {
98
+ "title": "TLS key",
99
+ "description": "The path to the client TLS key file.",
100
+ "type": "string",
101
+ "pattern": "^$|^/"
102
+ },
103
+ "volume_selector": {
104
+ "title": "Volume selector",
105
+ "description": "Specifies a space-separated list of volume name patterns to monitor. Use * for wildcards, prefix with ! to exclude.",
106
+ "type": "string",
107
+ "default": "*"
108
+ },
109
+ "vnode": {
110
+ "title": "Virtual Node",
111
+ "description": "Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).",
112
+ "type": "string"
113
+ }
114
+ },
115
+ "required": [
116
+ "url",
117
+ "username",
118
+ "password"
119
+ ],
120
+ "additionalProperties": false,
121
+ "patternProperties": {
122
+ "^name$": {}
123
+ }
124
+ },
125
+ "uiSchema": {
126
+ "uiOptions": {
127
+ "fullPage": true
128
+ },
129
+ "url": {
130
+ "ui:placeholder": "https://127.0.0.1"
131
+ },
132
+ "timeout": {
133
+ "ui:help": "Accepts decimals for sub-second values (e.g., 0.5 = 500ms)."
134
+ },
135
+ "password": {
136
+ "ui:widget": "password"
137
+ },
138
+ "proxy_password": {
139
+ "ui:widget": "password"
140
+ },
141
+ "ui:flavour": "tabs",
142
+ "ui:options": {
143
+ "tabs": [
144
+ {
145
+ "title": "Base",
146
+ "fields": [
147
+ "update_every",
148
+ "autodetection_retry",
149
+ "url",
150
+ "timeout",
151
+ "not_follow_redirects",
152
+ "volume_selector",
153
+ "vnode"
154
+ ]
155
+ },
156
+ {
157
+ "title": "Auth",
158
+ "fields": [
159
+ "username",
160
+ "password"
161
+ ]
162
+ },
163
+ {
164
+ "title": "TLS",
165
+ "fields": [
166
+ "tls_skip_verify",
167
+ "tls_ca",
168
+ "tls_cert",
169
+ "tls_key"
170
+ ]
171
+ },
172
+ {
173
+ "title": "Proxy",
174
+ "fields": [
175
+ "proxy_url",
176
+ "proxy_username",
177
+ "proxy_password"
178
+ ]
179
+ },
180
+ {
181
+ "title": "Headers",
182
+ "fields": [
183
+ "headers"
184
+ ]
185
+ }
186
+ ]
187
+ }
188
+ }
189
+}
src/go/plugin/go.d/collector/powerstore/metadata.yaml
new
+630
@@ -0,0 +1,630 @@
1
+plugin_name: go.d.plugin
2
+modules:
3
+ - meta:
4
+ id: collector-go.d.plugin-powerstore
5
+ plugin_name: go.d.plugin
6
+ module_name: powerstore
7
+ monitored_instance:
8
+ name: Dell PowerStore
9
+ link: https://www.dell.com/en-us/dt/storage/powerstore-storage-appliance.htm
10
+ icon_filename: dell.svg
11
+ categories:
12
+ - data-collection.storage
13
+ keywords:
14
+ - powerstore
15
+ - dell
16
+ - storage
17
+ - san
18
+ - nas
19
+ - block
20
+ - file
21
+ related_resources:
22
+ integrations:
23
+ list: []
24
+ info_provided_to_referring_integrations:
25
+ description: ""
26
+ overview:
27
+ data_collection:
28
+ metrics_description: |
29
+ This collector monitors Dell PowerStore storage arrays via the PowerStore REST API.
30
+
31
+ It collects metrics for the following components:
32
+
33
+ - **Cluster Capacity** — physical and logical space usage, efficiency ratios (data reduction, snapshot savings, thin provisioning)
34
+ - **Appliance Performance** — IOPS, bandwidth, latency, CPU utilization, and space usage per appliance
35
+ - **Volume Performance** — IOPS, bandwidth, latency, provisioned/used space, and thin savings per volume
36
+ - **Node Performance** — IOPS, bandwidth, latency, and current login count per node
37
+ - **FC Port Performance** — IOPS, bandwidth, latency, and link status per Fibre Channel port
38
+ - **Ethernet Port Performance** — bytes/packets rate, error rates, and link status per Ethernet port
39
+ - **File System Performance** — IOPS, bandwidth, and latency per file system
40
+ - **Hardware Health** — component-level status counts (fans, PSUs, drives, batteries, nodes) by health state (OK, degraded, failed, unknown)
41
+ - **Active Alerts** — count of currently active alerts by severity (critical, major, minor, info)
42
+ - **Drive Wear** — SSD endurance remaining percentage per drive
43
+ - **NAS Server Status** — operational status counts (started, stopped, degraded, unknown)
44
+ - **Replication** — data remaining, data transferred, and transfer rate across appliances
45
+ method_description: |
46
+ The collector uses the PowerStore REST API (`/api/rest/*` endpoints) to fetch data.
47
+ Authentication uses HTTP Basic Auth — the collector calls `/api/rest/login_session` to obtain
48
+ a session cookie and a `DELL-EMC-TOKEN` CSRF token, which are reused for subsequent requests.
49
+ Sessions are automatically refreshed on HTTP 403 (token expiry).
50
+
51
+ Discovery runs on startup and then every 5 collection cycles to refresh the hardware inventory,
52
+ appliances, volumes, nodes, ports, file systems, and NAS servers.
53
+
54
+ Performance and space metrics are collected via the `/api/rest/metrics/generate` endpoint using
55
+ the PowerStore metrics generation API. The collector requests `Five_Mins` interval data for
56
+ performance metrics and `One_Day` for cluster space metrics, taking the most recent data point.
57
+
58
+ Discovery API calls use pagination (2000 items per page, HTTP 206 for partial results).
59
+ Performance metrics for different entities are collected concurrently (up to 10 parallel API calls).
60
+ supported_platforms:
61
+ include: []
62
+ exclude: []
63
+ multi_instance: true
64
+ additional_permissions:
65
+ description: |
66
+ A user account on the PowerStore array is required. Any role with API read access
67
+ is sufficient (e.g., the built-in `Operator` role). The account does not need write permissions.
68
+ default_behavior:
69
+ auto_detection:
70
+ description: |
71
+ The collector does not auto-detect PowerStore arrays. You must configure the management IP address,
72
+ username, and password explicitly.
73
+ limits:
74
+ description: |
75
+ The collector makes multiple API calls per collection cycle: discovery calls (once every 5 cycles)
76
+ plus per-entity metrics calls (appliances, volumes, nodes, ports, file systems, drives, and
77
+ replication — one API call per entity). With many volumes or ports, the number of API calls
78
+ scales linearly. The default 30-second collection interval provides ample headroom.
79
+ performance_impact:
80
+ description: |
81
+ The PowerStore REST API is lightweight and read-only. The collector uses the metrics generation
82
+ API which returns pre-aggregated data, imposing minimal load on the array. Session tokens and
83
+ CSRF tokens are cached across collection cycles.
84
+ setup:
85
+ prerequisites:
86
+ list:
87
+ - title: PowerStore API access
88
+ description: |
89
+ 1. A user account on the PowerStore array with at least read-only access.
90
+ The built-in `Operator` role is sufficient.
91
+ 2. Network connectivity from the Netdata Agent to the PowerStore management IP (HTTPS, port 443).
92
+ 3. The PowerStore management REST API must be accessible (enabled by default).
93
+ - title: TLS certificate
94
+ description: |
95
+ PowerStore arrays typically use self-signed TLS certificates. You will likely need to set
96
+ `tls_skip_verify: yes` in the configuration, or provide the array's CA certificate via `tls_ca`.
97
+ configuration:
98
+ file:
99
+ name: go.d/powerstore.conf
100
+ options:
101
+ description: |
102
+ The following options can be defined globally: update_every, autodetection_retry.
103
+ folding:
104
+ title: Config options
105
+ enabled: true
106
+ list:
107
+ - name: update_every
108
+ description: Data collection interval (seconds).
109
+ default_value: 30
110
+ required: false
111
+ group: Collection
112
+ - name: autodetection_retry
113
+ description: Autodetection retry interval (seconds). Set 0 to disable.
114
+ default_value: 0
115
+ required: false
116
+ group: Collection
117
+
118
+ - name: url
119
+ description: PowerStore management URL.
120
+ default_value: https://127.0.0.1
121
+ required: true
122
+ group: Target
123
+ - name: timeout
124
+ description: HTTP request timeout (seconds).
125
+ default_value: 30
126
+ required: false
127
+ group: Target
128
+
129
+ - name: username
130
+ description: Username for PowerStore API authentication.
131
+ default_value: ""
132
+ required: true
133
+ group: HTTP Auth
134
+ - name: password
135
+ description: Password for PowerStore API authentication.
136
+ default_value: ""
137
+ required: true
138
+ group: HTTP Auth
139
+
140
+ - name: volume_selector
141
+ description: |
142
+ Space-separated [simple patterns](https://github.com/netdata/netdata/tree/master/src/libnetdata/simple_pattern#readme)
143
+ to filter which volumes to monitor. Use `*` for wildcards, `!` prefix to exclude.
144
+ An empty value or `*` monitors all volumes.
145
+ default_value: "*"
146
+ required: false
147
+ group: Filtering
148
+
149
+ - name: tls_skip_verify
150
+ description: Skip TLS certificate and hostname verification (insecure, but often needed for self-signed certs on storage arrays).
151
+ default_value: no
152
+ required: false
153
+ group: TLS
154
+ - name: tls_ca
155
+ description: Path to CA bundle used to validate the server certificate.
156
+ default_value: ""
157
+ required: false
158
+ group: TLS
159
+ - name: tls_cert
160
+ description: Path to client TLS certificate (for mTLS).
161
+ default_value: ""
162
+ required: false
163
+ group: TLS
164
+ - name: tls_key
165
+ description: Path to client TLS private key (for mTLS).
166
+ default_value: ""
167
+ required: false
168
+ group: TLS
169
+
170
+ - name: proxy_url
171
+ description: HTTP proxy URL.
172
+ default_value: ""
173
+ required: false
174
+ group: Proxy
175
+ - name: proxy_username
176
+ description: Username for proxy Basic HTTP authentication.
177
+ default_value: ""
178
+ required: false
179
+ group: Proxy
180
+ - name: proxy_password
181
+ description: Password for proxy Basic HTTP authentication.
182
+ default_value: ""
183
+ required: false
184
+ group: Proxy
185
+
186
+ - name: headers
187
+ description: "Additional HTTP headers (one per line as key: value)."
188
+ default_value: ""
189
+ required: false
190
+ group: Request
191
+ - name: not_follow_redirects
192
+ description: Do not follow HTTP redirects.
193
+ default_value: no
194
+ required: false
195
+ group: Request
196
+
197
+ - name: vnode
198
+ description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
199
+ default_value: ""
200
+ required: false
201
+ group: Virtual Node
202
+ examples:
203
+ folding:
204
+ title: Config
205
+ enabled: true
206
+ list:
207
+ - name: Basic
208
+ description: A minimal configuration connecting to a PowerStore array with a self-signed certificate.
209
+ config: |
210
+ jobs:
211
+ - name: ps-lab
212
+ url: https://10.0.0.1
213
+ username: monitor
214
+ password: monitor123
215
+ tls_skip_verify: yes
216
+ - name: With volume filtering
217
+ description: Monitor only production volumes, excluding temporary and test volumes.
218
+ config: |
219
+ jobs:
220
+ - name: ps-prod
221
+ url: https://10.0.0.1
222
+ username: monitor
223
+ password: monitor123
224
+ tls_skip_verify: yes
225
+ volume_selector: "prod-* !*-temp !*-test"
226
+ - name: Multi-instance
227
+ description: |
228
+ > **Note**: When you define multiple jobs, their names must be unique.
229
+
230
+ Monitoring multiple PowerStore arrays.
231
+ config: |
232
+ jobs:
233
+ - name: ps-site-a
234
+ url: https://10.0.0.1
235
+ username: monitor
236
+ password: monitor123
237
+ tls_skip_verify: yes
238
+
239
+ - name: ps-site-b
240
+ url: https://10.0.0.2
241
+ username: monitor
242
+ password: monitor456
243
+ tls_skip_verify: yes
244
+ troubleshooting:
245
+ problems:
246
+ list:
247
+ - name: Authentication failure
248
+ description: |
249
+ If the collector fails with `login failed`:
250
+
251
+ 1. Verify the username and password are correct.
252
+ 2. Ensure the user account has at least `Operator` role privileges.
253
+ 3. Check that the account is not locked or expired on the array.
254
+ - name: Connection refused or timeout
255
+ description: |
256
+ If the collector fails with connection errors:
257
+
258
+ 1. Verify the `url` points to the management IP of the PowerStore array.
259
+ 2. Ensure HTTPS port 443 is reachable from the Netdata Agent host.
260
+ 3. If using a firewall, allow outbound HTTPS to the array.
261
+ 4. Try increasing `timeout` if the array is on a high-latency link.
262
+ - name: TLS certificate errors
263
+ description: |
264
+ PowerStore arrays ship with self-signed certificates. If you see TLS errors:
265
+
266
+ 1. Set `tls_skip_verify: yes` in the configuration (most common solution).
267
+ 2. Alternatively, export the array's certificate and provide it via `tls_ca`.
268
+ - name: HTTP 403 errors
269
+ description: |
270
+ PowerStore returns HTTP 403 when the session/CSRF token is stale. The collector
271
+ automatically re-authenticates on 403, but if you see persistent 403 errors:
272
+
273
+ 1. Verify the user account still has API access.
274
+ 2. Check if the array's security policy has been changed to restrict API access.
275
+ 3. Ensure no IP-based access controls are blocking the Netdata Agent host.
276
+ alerts: []
277
+ metrics:
278
+ folding:
279
+ title: Metrics
280
+ enabled: false
281
+ description: ""
282
+ availability: []
283
+ scopes:
284
+ - name: global
285
+ description: These metrics refer to the entire monitored PowerStore cluster.
286
+ labels: []
287
+ metrics:
288
+ - name: powerstore.cluster_space_usage
289
+ description: Cluster Space Usage
290
+ unit: bytes
291
+ chart_type: line
292
+ dimensions:
293
+ - name: used
294
+ - name: total
295
+ - name: powerstore.cluster_space_logical
296
+ description: Cluster Logical Space
297
+ unit: bytes
298
+ chart_type: line
299
+ dimensions:
300
+ - name: provisioned
301
+ - name: used
302
+ - name: data_physical
303
+ - name: shared
304
+ - name: powerstore.cluster_space_efficiency
305
+ description: Cluster Space Efficiency Ratios
306
+ unit: ratio
307
+ chart_type: line
308
+ dimensions:
309
+ - name: efficiency
310
+ - name: data_reduction
311
+ - name: snapshot_savings
312
+ - name: thin_savings
313
+ - name: powerstore.hardware_health_fan
314
+ description: Fan Health Status
315
+ unit: fans
316
+ chart_type: stacked
317
+ dimensions:
318
+ - name: ok
319
+ - name: degraded
320
+ - name: failed
321
+ - name: unknown
322
+ - name: powerstore.hardware_health_psu
323
+ description: Power Supply Health Status
324
+ unit: PSUs
325
+ chart_type: stacked
326
+ dimensions:
327
+ - name: ok
328
+ - name: degraded
329
+ - name: failed
330
+ - name: unknown
331
+ - name: powerstore.hardware_health_drive
332
+ description: Drive Health Status
333
+ unit: drives
334
+ chart_type: stacked
335
+ dimensions:
336
+ - name: ok
337
+ - name: degraded
338
+ - name: failed
339
+ - name: unknown
340
+ - name: powerstore.hardware_health_battery
341
+ description: Battery Health Status
342
+ unit: batteries
343
+ chart_type: stacked
344
+ dimensions:
345
+ - name: ok
346
+ - name: degraded
347
+ - name: failed
348
+ - name: unknown
349
+ - name: powerstore.hardware_health_node
350
+ description: Node Health Status
351
+ unit: nodes
352
+ chart_type: stacked
353
+ dimensions:
354
+ - name: ok
355
+ - name: degraded
356
+ - name: failed
357
+ - name: unknown
358
+ - name: powerstore.alerts_active
359
+ description: Active Alerts by Severity
360
+ unit: alerts
361
+ chart_type: stacked
362
+ dimensions:
363
+ - name: critical
364
+ - name: major
365
+ - name: minor
366
+ - name: info
367
+ - name: powerstore.nas_server_status
368
+ description: NAS Server Status
369
+ unit: servers
370
+ chart_type: stacked
371
+ dimensions:
372
+ - name: started
373
+ - name: stopped
374
+ - name: degraded
375
+ - name: unknown
376
+ - name: powerstore.copy_data
377
+ description: Replication Data
378
+ unit: bytes
379
+ chart_type: line
380
+ dimensions:
381
+ - name: remaining
382
+ - name: transferred
383
+ - name: powerstore.copy_transfer_rate
384
+ description: Replication Transfer Rate
385
+ unit: bytes/s
386
+ chart_type: line
387
+ dimensions:
388
+ - name: rate
389
+ - name: appliance
390
+ description: These metrics refer to individual PowerStore appliances.
391
+ labels:
392
+ - name: appliance
393
+ description: Appliance name (e.g., `Appliance-WX-D8031`).
394
+ metrics:
395
+ - name: powerstore.appliance_iops
396
+ description: Appliance IOPS
397
+ unit: ops/s
398
+ chart_type: area
399
+ dimensions:
400
+ - name: read
401
+ - name: write
402
+ - name: powerstore.appliance_bandwidth
403
+ description: Appliance Bandwidth
404
+ unit: bytes/s
405
+ chart_type: area
406
+ dimensions:
407
+ - name: read
408
+ - name: write
409
+ - name: powerstore.appliance_latency
410
+ description: Appliance Latency
411
+ unit: microseconds
412
+ chart_type: line
413
+ dimensions:
414
+ - name: read
415
+ - name: write
416
+ - name: avg
417
+ - name: powerstore.appliance_cpu
418
+ description: Appliance IO Workload CPU Utilization
419
+ unit: percentage
420
+ chart_type: line
421
+ dimensions:
422
+ - name: utilization
423
+ - name: powerstore.appliance_space
424
+ description: Appliance Space Usage
425
+ unit: bytes
426
+ chart_type: line
427
+ dimensions:
428
+ - name: used
429
+ - name: total
430
+ - name: powerstore.appliance_space_logical
431
+ description: Appliance Logical Space
432
+ unit: bytes
433
+ chart_type: line
434
+ dimensions:
435
+ - name: provisioned
436
+ - name: used
437
+ - name: data_physical
438
+ - name: shared
439
+ - name: powerstore.appliance_space_efficiency
440
+ description: Appliance Space Efficiency Ratios
441
+ unit: ratio
442
+ chart_type: line
443
+ dimensions:
444
+ - name: efficiency
445
+ - name: data_reduction
446
+ - name: snapshot_savings
447
+ - name: thin_savings
448
+ - name: volume
449
+ description: These metrics refer to individual PowerStore volumes. Volumes can be filtered using the `volume_selector` configuration option.
450
+ labels:
451
+ - name: volume
452
+ description: Volume name (e.g., `prod-db-01`).
453
+ metrics:
454
+ - name: powerstore.volume_iops
455
+ description: Volume IOPS
456
+ unit: ops/s
457
+ chart_type: area
458
+ dimensions:
459
+ - name: read
460
+ - name: write
461
+ - name: powerstore.volume_bandwidth
462
+ description: Volume Bandwidth
463
+ unit: bytes/s
464
+ chart_type: area
465
+ dimensions:
466
+ - name: read
467
+ - name: write
468
+ - name: powerstore.volume_latency
469
+ description: Volume Latency
470
+ unit: microseconds
471
+ chart_type: line
472
+ dimensions:
473
+ - name: read
474
+ - name: write
475
+ - name: avg
476
+ - name: powerstore.volume_space
477
+ description: Volume Space Usage
478
+ unit: bytes
479
+ chart_type: line
480
+ dimensions:
481
+ - name: provisioned
482
+ - name: used
483
+ - name: powerstore.volume_space_savings
484
+ description: Volume Thin Savings
485
+ unit: ratio
486
+ chart_type: line
487
+ dimensions:
488
+ - name: thin_savings
489
+ - name: node
490
+ description: These metrics refer to individual PowerStore nodes within an appliance.
491
+ labels:
492
+ - name: node
493
+ description: Node name (e.g., `Appliance-WX-D8031-node-A`).
494
+ metrics:
495
+ - name: powerstore.node_iops
496
+ description: Node IOPS
497
+ unit: ops/s
498
+ chart_type: area
499
+ dimensions:
500
+ - name: read
501
+ - name: write
502
+ - name: powerstore.node_bandwidth
503
+ description: Node Bandwidth
504
+ unit: bytes/s
505
+ chart_type: area
506
+ dimensions:
507
+ - name: read
508
+ - name: write
509
+ - name: powerstore.node_latency
510
+ description: Node Latency
511
+ unit: microseconds
512
+ chart_type: line
513
+ dimensions:
514
+ - name: read
515
+ - name: write
516
+ - name: avg
517
+ - name: powerstore.node_logins
518
+ description: Node Current Logins
519
+ unit: logins
520
+ chart_type: line
521
+ dimensions:
522
+ - name: logins
523
+ - name: fc port
524
+ description: These metrics refer to individual Fibre Channel ports used for host connectivity.
525
+ labels:
526
+ - name: fc_port
527
+ description: FC port name (e.g., `Appliance-WX-D8031-node-A-Port-0`).
528
+ metrics:
529
+ - name: powerstore.fc_port_iops
530
+ description: FC Port IOPS
531
+ unit: ops/s
532
+ chart_type: area
533
+ dimensions:
534
+ - name: read
535
+ - name: write
536
+ - name: powerstore.fc_port_bandwidth
537
+ description: FC Port Bandwidth
538
+ unit: bytes/s
539
+ chart_type: area
540
+ dimensions:
541
+ - name: read
542
+ - name: write
543
+ - name: powerstore.fc_port_latency
544
+ description: FC Port Latency
545
+ unit: microseconds
546
+ chart_type: line
547
+ dimensions:
548
+ - name: read
549
+ - name: write
550
+ - name: avg
551
+ - name: powerstore.fc_port_link_status
552
+ description: FC Port Link Status
553
+ unit: status
554
+ chart_type: line
555
+ dimensions:
556
+ - name: up
557
+ - name: ethernet port
558
+ description: These metrics refer to individual Ethernet ports (management and iSCSI connectivity).
559
+ labels:
560
+ - name: eth_port
561
+ description: Ethernet port name (e.g., `Appliance-WX-D8031-node-A-Port-eth0`).
562
+ metrics:
563
+ - name: powerstore.eth_port_bytes
564
+ description: Ethernet Port Bytes Rate
565
+ unit: bytes/s
566
+ chart_type: area
567
+ dimensions:
568
+ - name: received
569
+ - name: sent
570
+ - name: powerstore.eth_port_packets
571
+ description: Ethernet Port Packets Rate
572
+ unit: packets/s
573
+ chart_type: area
574
+ dimensions:
575
+ - name: received
576
+ - name: sent
577
+ - name: powerstore.eth_port_errors
578
+ description: Ethernet Port Errors Rate
579
+ unit: errors/s
580
+ chart_type: stacked
581
+ dimensions:
582
+ - name: rx_crc
583
+ - name: rx_no_buffer
584
+ - name: tx_error
585
+ - name: powerstore.eth_port_link_status
586
+ description: Ethernet Port Link Status
587
+ unit: status
588
+ chart_type: line
589
+ dimensions:
590
+ - name: up
591
+ - name: file system
592
+ description: These metrics refer to individual file systems (NFS/SMB shares).
593
+ labels:
594
+ - name: filesystem
595
+ description: File system name (e.g., `nfs-share-01`).
596
+ metrics:
597
+ - name: powerstore.filesystem_iops
598
+ description: File System IOPS
599
+ unit: ops/s
600
+ chart_type: area
601
+ dimensions:
602
+ - name: read
603
+ - name: write
604
+ - name: powerstore.filesystem_bandwidth
605
+ description: File System Bandwidth
606
+ unit: bytes/s
607
+ chart_type: area
608
+ dimensions:
609
+ - name: read
610
+ - name: write
611
+ - name: powerstore.filesystem_latency
612
+ description: File System Latency
613
+ unit: microseconds
614
+ chart_type: line
615
+ dimensions:
616
+ - name: read
617
+ - name: write
618
+ - name: avg
619
+ - name: drive
620
+ description: These metrics refer to individual physical drives (SSDs).
621
+ labels:
622
+ - name: drive
623
+ description: Drive name (e.g., `Drive_0_0_25`).
624
+ metrics:
625
+ - name: powerstore.drive_endurance
626
+ description: Drive Endurance Remaining
627
+ unit: percentage
628
+ chart_type: line
629
+ dimensions:
630
+ - name: remaining
src/go/plugin/go.d/collector/powerstore/metrics.go
new
+244
@@ -0,0 +1,244 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package powerstore
4
+
5
+import "github.com/netdata/netdata/go/plugins/pkg/metrix"
6
+
7
+type collectorMetrics struct {
8
+ cluster clusterGauges
9
+ appliance applianceGauges
10
+ volume volumeGauges
11
+ node nodeGauges
12
+ fcPort fcPortGauges
13
+ ethPort ethPortGauges
14
+ fileSystem fileSystemGauges
15
+ hardware hardwareGauges
16
+ alerts alertGauges
17
+ drive driveGauges
18
+ nas nasGauges
19
+ replication replicationGauges
20
+}
21
+
22
+type clusterGauges struct {
23
+ spacePhysicalTotal metrix.SnapshotGauge
24
+ spacePhysicalUsed metrix.SnapshotGauge
25
+ spaceLogicalProvisioned metrix.SnapshotGauge
26
+ spaceLogicalUsed metrix.SnapshotGauge
27
+ spaceDataPhysicalUsed metrix.SnapshotGauge
28
+ spaceSharedLogicalUsed metrix.SnapshotGauge
29
+ spaceEfficiencyRatio metrix.SnapshotGauge
30
+ spaceDataReduction metrix.SnapshotGauge
31
+ spaceSnapshotSavings metrix.SnapshotGauge
32
+ spaceThinSavings metrix.SnapshotGauge
33
+}
34
+
35
+type perfGaugeVecs struct {
36
+ readIops metrix.SnapshotGaugeVec
37
+ writeIops metrix.SnapshotGaugeVec
38
+ readBandwidth metrix.SnapshotGaugeVec
39
+ writeBandwidth metrix.SnapshotGaugeVec
40
+ avgReadLatency metrix.SnapshotGaugeVec
41
+ avgWriteLatency metrix.SnapshotGaugeVec
42
+ avgLatency metrix.SnapshotGaugeVec
43
+}
44
+
45
+type spaceGaugeVecs struct {
46
+ physicalTotal metrix.SnapshotGaugeVec
47
+ physicalUsed metrix.SnapshotGaugeVec
48
+ logicalProvisioned metrix.SnapshotGaugeVec
49
+ logicalUsed metrix.SnapshotGaugeVec
50
+ dataPhysicalUsed metrix.SnapshotGaugeVec
51
+ sharedLogicalUsed metrix.SnapshotGaugeVec
52
+ efficiencyRatio metrix.SnapshotGaugeVec
53
+ dataReduction metrix.SnapshotGaugeVec
54
+ snapshotSavings metrix.SnapshotGaugeVec
55
+ thinSavings metrix.SnapshotGaugeVec
56
+}
57
+
58
+type applianceGauges struct {
59
+ perf perfGaugeVecs
60
+ space spaceGaugeVecs
61
+ cpu metrix.SnapshotGaugeVec
62
+}
63
+
64
+type volumeGauges struct {
65
+ perf perfGaugeVecs
66
+ spaceLogicalProv metrix.SnapshotGaugeVec
67
+ spaceLogicalUsed metrix.SnapshotGaugeVec
68
+ spaceThinSavings metrix.SnapshotGaugeVec
69
+}
70
+
71
+type nodeGauges struct {
72
+ perf perfGaugeVecs
73
+ currentLogins metrix.SnapshotGaugeVec
74
+}
75
+
76
+type fcPortGauges struct {
77
+ perf perfGaugeVecs
78
+ linkUp metrix.SnapshotGaugeVec
79
+}
80
+
81
+type ethPortGauges struct {
82
+ bytesRxPs metrix.SnapshotGaugeVec
83
+ bytesTxPs metrix.SnapshotGaugeVec
84
+ pktRxPs metrix.SnapshotGaugeVec
85
+ pktTxPs metrix.SnapshotGaugeVec
86
+ pktRxCrcErrorPs metrix.SnapshotGaugeVec
87
+ pktRxNoBufferErrorPs metrix.SnapshotGaugeVec
88
+ pktTxErrorPs metrix.SnapshotGaugeVec
89
+ linkUp metrix.SnapshotGaugeVec
90
+}
91
+
92
+type fileSystemGauges struct {
93
+ perf perfGaugeVecs
94
+}
95
+
96
+type hardwareGauges struct {
97
+ fanOK, fanDegraded, fanFailed, fanUnknown metrix.SnapshotGauge
98
+ psuOK, psuDegraded, psuFailed, psuUnknown metrix.SnapshotGauge
99
+ driveOK, driveDegraded, driveFailed, driveUnknown metrix.SnapshotGauge
100
+ batteryOK, batteryDegraded, batteryFailed, batteryUnknown metrix.SnapshotGauge
101
+ nodeOK, nodeDegraded, nodeFailed, nodeUnknown metrix.SnapshotGauge
102
+}
103
+
104
+type alertGauges struct {
105
+ critical metrix.SnapshotGauge
106
+ major metrix.SnapshotGauge
107
+ minor metrix.SnapshotGauge
108
+ info metrix.SnapshotGauge
109
+}
110
+
111
+type driveGauges struct {
112
+ enduranceRemaining metrix.SnapshotGaugeVec
113
+}
114
+
115
+type nasGauges struct {
116
+ started metrix.SnapshotGauge
117
+ stopped metrix.SnapshotGauge
118
+ degraded metrix.SnapshotGauge
119
+ unknown metrix.SnapshotGauge
120
+}
121
+
122
+type replicationGauges struct {
123
+ dataRemaining metrix.SnapshotGauge
124
+ dataTransferred metrix.SnapshotGauge
125
+ transferRate metrix.SnapshotGauge
126
+}
127
+
128
+func newCollectorMetrics(store metrix.CollectorStore) *collectorMetrics {
129
+ meter := store.Write().SnapshotMeter("")
130
+
131
+ appVec := meter.Vec("appliance")
132
+ volVec := meter.Vec("volume")
133
+ nodeVec := meter.Vec("node")
134
+ fcVec := meter.Vec("fc_port")
135
+ ethVec := meter.Vec("eth_port")
136
+ fsVec := meter.Vec("filesystem")
137
+ driveVec := meter.Vec("drive")
138
+
139
+ newPerfVecs := func(vec metrix.SnapshotVecMeter, prefix string) perfGaugeVecs {
140
+ return perfGaugeVecs{
141
+ readIops: vec.Gauge(prefix + "_perf_read_iops"),
142
+ writeIops: vec.Gauge(prefix + "_perf_write_iops"),
143
+ readBandwidth: vec.Gauge(prefix + "_perf_read_bandwidth"),
144
+ writeBandwidth: vec.Gauge(prefix + "_perf_write_bandwidth"),
145
+ avgReadLatency: vec.Gauge(prefix + "_perf_avg_read_latency"),
146
+ avgWriteLatency: vec.Gauge(prefix + "_perf_avg_write_latency"),
147
+ avgLatency: vec.Gauge(prefix + "_perf_avg_latency"),
148
+ }
149
+ }
150
+
151
+ newSpaceVecs := func(vec metrix.SnapshotVecMeter, prefix string) spaceGaugeVecs {
152
+ return spaceGaugeVecs{
153
+ physicalTotal: vec.Gauge(prefix + "_space_physical_total"),
154
+ physicalUsed: vec.Gauge(prefix + "_space_physical_used"),
155
+ logicalProvisioned: vec.Gauge(prefix + "_space_logical_provisioned"),
156
+ logicalUsed: vec.Gauge(prefix + "_space_logical_used"),
157
+ dataPhysicalUsed: vec.Gauge(prefix + "_space_data_physical_used"),
158
+ sharedLogicalUsed: vec.Gauge(prefix + "_space_shared_logical_used"),
159
+ efficiencyRatio: vec.Gauge(prefix + "_space_efficiency_ratio"),
160
+ dataReduction: vec.Gauge(prefix + "_space_data_reduction"),
161
+ snapshotSavings: vec.Gauge(prefix + "_space_snapshot_savings"),
162
+ thinSavings: vec.Gauge(prefix + "_space_thin_savings"),
163
+ }
164
+ }
165
+
166
+ return &collectorMetrics{
167
+ cluster: clusterGauges{
168
+ spacePhysicalTotal: meter.Gauge("cluster_space_physical_total"),
169
+ spacePhysicalUsed: meter.Gauge("cluster_space_physical_used"),
170
+ spaceLogicalProvisioned: meter.Gauge("cluster_space_logical_provisioned"),
171
+ spaceLogicalUsed: meter.Gauge("cluster_space_logical_used"),
172
+ spaceDataPhysicalUsed: meter.Gauge("cluster_space_data_physical_used"),
173
+ spaceSharedLogicalUsed: meter.Gauge("cluster_space_shared_logical_used"),
174
+ spaceEfficiencyRatio: meter.Gauge("cluster_space_efficiency_ratio"),
175
+ spaceDataReduction: meter.Gauge("cluster_space_data_reduction"),
176
+ spaceSnapshotSavings: meter.Gauge("cluster_space_snapshot_savings"),
177
+ spaceThinSavings: meter.Gauge("cluster_space_thin_savings"),
178
+ },
179
+ appliance: applianceGauges{
180
+ perf: newPerfVecs(appVec, "appliance"),
181
+ space: newSpaceVecs(appVec, "appliance"),
182
+ cpu: appVec.Gauge("appliance_cpu_utilization"),
183
+ },
184
+ volume: volumeGauges{
185
+ perf: newPerfVecs(volVec, "volume"),
186
+ spaceLogicalProv: volVec.Gauge("volume_space_logical_provisioned"),
187
+ spaceLogicalUsed: volVec.Gauge("volume_space_logical_used"),
188
+ spaceThinSavings: volVec.Gauge("volume_space_thin_savings"),
189
+ },
190
+ node: nodeGauges{
191
+ perf: newPerfVecs(nodeVec, "node"),
192
+ currentLogins: nodeVec.Gauge("node_current_logins"),
193
+ },
194
+ fcPort: fcPortGauges{
195
+ perf: newPerfVecs(fcVec, "fc_port"),
196
+ linkUp: fcVec.Gauge("fc_port_link_up"),
197
+ },
198
+ ethPort: ethPortGauges{
199
+ bytesRxPs: ethVec.Gauge("eth_port_bytes_rx_ps"),
200
+ bytesTxPs: ethVec.Gauge("eth_port_bytes_tx_ps"),
201
+ pktRxPs: ethVec.Gauge("eth_port_pkt_rx_ps"),
202
+ pktTxPs: ethVec.Gauge("eth_port_pkt_tx_ps"),
203
+ pktRxCrcErrorPs: ethVec.Gauge("eth_port_pkt_rx_crc_error_ps"),
204
+ pktRxNoBufferErrorPs: ethVec.Gauge("eth_port_pkt_rx_no_buffer_error_ps"),
205
+ pktTxErrorPs: ethVec.Gauge("eth_port_pkt_tx_error_ps"),
206
+ linkUp: ethVec.Gauge("eth_port_link_up"),
207
+ },
208
+ fileSystem: fileSystemGauges{
209
+ perf: newPerfVecs(fsVec, "file_system"),
210
+ },
211
+ hardware: hardwareGauges{
212
+ fanOK: meter.Gauge("hardware_fan_ok"), fanDegraded: meter.Gauge("hardware_fan_degraded"),
213
+ fanFailed: meter.Gauge("hardware_fan_failed"), fanUnknown: meter.Gauge("hardware_fan_unknown"),
214
+ psuOK: meter.Gauge("hardware_psu_ok"), psuDegraded: meter.Gauge("hardware_psu_degraded"),
215
+ psuFailed: meter.Gauge("hardware_psu_failed"), psuUnknown: meter.Gauge("hardware_psu_unknown"),
216
+ driveOK: meter.Gauge("hardware_drive_ok"), driveDegraded: meter.Gauge("hardware_drive_degraded"),
217
+ driveFailed: meter.Gauge("hardware_drive_failed"), driveUnknown: meter.Gauge("hardware_drive_unknown"),
218
+ batteryOK: meter.Gauge("hardware_battery_ok"), batteryDegraded: meter.Gauge("hardware_battery_degraded"),
219
+ batteryFailed: meter.Gauge("hardware_battery_failed"), batteryUnknown: meter.Gauge("hardware_battery_unknown"),
220
+ nodeOK: meter.Gauge("hardware_node_ok"), nodeDegraded: meter.Gauge("hardware_node_degraded"),
221
+ nodeFailed: meter.Gauge("hardware_node_failed"), nodeUnknown: meter.Gauge("hardware_node_unknown"),
222
+ },
223
+ alerts: alertGauges{
224
+ critical: meter.Gauge("alerts_critical"),
225
+ major: meter.Gauge("alerts_major"),
226
+ minor: meter.Gauge("alerts_minor"),
227
+ info: meter.Gauge("alerts_info"),
228
+ },
229
+ drive: driveGauges{
230
+ enduranceRemaining: driveVec.Gauge("drive_endurance_remaining"),
231
+ },
232
+ nas: nasGauges{
233
+ started: meter.Gauge("nas_started"),
234
+ stopped: meter.Gauge("nas_stopped"),
235
+ degraded: meter.Gauge("nas_degraded"),
236
+ unknown: meter.Gauge("nas_unknown"),
237
+ },
238
+ replication: replicationGauges{
239
+ dataRemaining: meter.Gauge("copy_data_remaining"),
240
+ dataTransferred: meter.Gauge("copy_data_transferred"),
241
+ transferRate: meter.Gauge("copy_transfer_rate"),
242
+ },
243
+ }
244
+}
src/go/plugin/go.d/collector/powerstore/testdata/alert.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"AL1","severity":"Minor","state":"ACTIVE","resource_type":"hardware","resource_name":"Drive-1","description_l10n":"Drive temperature above threshold","generated_timestamp":"2024-01-01T00:00:00Z","is_acknowledged":false},{"id":"AL2","severity":"Info","state":"ACTIVE","resource_type":"software_system","resource_name":"System","description_l10n":"Software update available","generated_timestamp":"2024-01-01T00:00:00Z","is_acknowledged":false}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/appliance.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"A1","name":"Appliance-1","service_tag":"SVC001"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/cluster.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"0","global_id":"PS00000000001","name":"PowerStore-Cluster","management_address":"10.0.0.1","state":"Configured"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/eth_port.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"ETH1","name":"ETH-Port-0","appliance_id":"A1","node_id":"N1","is_link_up":true,"current_speed":"25_Gbps","mac_address":"00:11:22:33:44:55","current_mtu":9000}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/fc_port.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"FC1","name":"FC-Port-0","appliance_id":"A1","node_id":"N1","is_link_up":true,"current_speed":"32_Gbps","wwn":"50:00:00:00:00:00:00:01"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/file_system.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"FS1","name":"nfs-share-01","nas_server_id":"NAS1","size_total":1099511627776,"size_used":549755813888,"filesystem_type":"Primary"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/hardware_all.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"F1","name":"Fan-1","type":"Fan","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"F2","name":"Fan-2","type":"Fan","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"F3","name":"Fan-3","type":"Fan","lifecycle_state":"Degraded","appliance_id":"A1"},{"id":"P1","name":"PSU-1","type":"Power_Supply","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"P2","name":"PSU-2","type":"Power_Supply","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"D1","name":"Drive-1","type":"Drive","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"D2","name":"Drive-2","type":"Drive","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"B1","name":"Battery-1","type":"Battery","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"N1","name":"Node-A","type":"Node","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"N2","name":"Node-B","type":"Node","lifecycle_state":"Healthy","appliance_id":"A1"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/hardware_battery.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"B1","name":"Battery-1","type":"Battery","lifecycle_state":"Healthy","appliance_id":"A1"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/hardware_drive.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"D1","name":"Drive-1","type":"Drive","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"D2","name":"Drive-2","type":"Drive","lifecycle_state":"Healthy","appliance_id":"A1"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/hardware_fan.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"F1","name":"Fan-1","type":"Fan","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"F2","name":"Fan-2","type":"Fan","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"F3","name":"Fan-3","type":"Fan","lifecycle_state":"Degraded","appliance_id":"A1"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/hardware_node.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"N1","name":"Node-A","type":"Node","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"N2","name":"Node-B","type":"Node","lifecycle_state":"Healthy","appliance_id":"A1"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/hardware_psu.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"P1","name":"PSU-1","type":"Power_Supply","lifecycle_state":"Healthy","appliance_id":"A1"},{"id":"P2","name":"PSU-2","type":"Power_Supply","lifecycle_state":"Healthy","appliance_id":"A1"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_copy_appliance.json
new
+1
@@ -0,0 +1 @@
1
+[{"appliance_id":"A1","data_remaining":1073741824,"data_transferred":5368709120,"transfer_rate":52428800.0}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_perf_appliance.json
new
+1
@@ -0,0 +1 @@
1
+[{"appliance_id":"A1","read_iops":1500.5,"write_iops":800.3,"total_iops":2300.8,"read_bandwidth":157286400,"write_bandwidth":83886080,"total_bandwidth":241172480,"avg_read_latency":250.5,"avg_write_latency":350.2,"avg_latency":290.1,"io_workload_cpu_utilization":35.5}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_perf_eth_port.json
new
+1
@@ -0,0 +1 @@
1
+[{"fe_port_id":"ETH1","appliance_id":"A1","node_id":"N1","bytes_rx_ps":125000000.0,"bytes_tx_ps":62500000.0,"pkt_rx_ps":100000.0,"pkt_tx_ps":50000.0,"pkt_rx_crc_error_ps":0.5,"pkt_rx_no_buffer_error_ps":0.0,"pkt_tx_error_ps":0.0}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_perf_fc_port.json
new
+1
@@ -0,0 +1 @@
1
+[{"fe_port_id":"FC1","appliance_id":"A1","node_id":"N1","read_iops":300.0,"write_iops":200.0,"total_iops":500.0,"read_bandwidth":31457280,"write_bandwidth":20971520,"total_bandwidth":52428800,"avg_read_latency":180.0,"avg_write_latency":250.0,"avg_latency":210.0}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_perf_filesystem.json
new
+1
@@ -0,0 +1 @@
1
+[{"file_system_id":"FS1","read_iops":100.0,"write_iops":50.0,"total_iops":150.0,"read_bandwidth":10485760,"write_bandwidth":5242880,"total_bandwidth":15728640,"avg_read_latency":400.0,"avg_write_latency":500.0,"avg_latency":440.0}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_perf_node.json
new
+1
@@ -0,0 +1 @@
1
+[{"node_id":"N1","appliance_id":"A1","read_iops":750.0,"write_iops":400.0,"total_iops":1150.0,"read_bandwidth":78643200,"write_bandwidth":41943040,"total_bandwidth":120586240,"avg_read_latency":230.0,"avg_write_latency":320.0,"avg_latency":270.0,"current_logins":5}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_perf_volume.json
new
+1
@@ -0,0 +1 @@
1
+[{"volume_id":"V1","appliance_id":"A1","read_iops":500.2,"write_iops":300.1,"total_iops":800.3,"read_bandwidth":52428800,"write_bandwidth":31457280,"total_bandwidth":83886080,"avg_read_latency":200.0,"avg_write_latency":300.0,"avg_latency":240.0}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_space_appliance.json
new
+1
@@ -0,0 +1 @@
1
+[{"appliance_id":"A1","physical_total":10995116277760,"physical_used":5497558138880,"logical_provisioned":21990232555520,"logical_used":8246337208320,"data_physical_used":4398046511104,"shared_logical_used":1099511627776,"efficiency_ratio":2.5,"data_reduction":1.8,"snapshot_savings":0.3,"thin_savings":0.7}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_space_cluster.json
new
+1
@@ -0,0 +1 @@
1
+[{"cluster_id":"0","physical_total":10995116277760,"physical_used":5497558138880,"logical_provisioned":21990232555520,"logical_used":8246337208320,"data_physical_used":4398046511104,"shared_logical_used":1099511627776,"efficiency_ratio":2.5,"data_reduction":1.8,"snapshot_savings":0.3,"thin_savings":0.7}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_space_volume.json
new
+1
@@ -0,0 +1 @@
1
+[{"volume_id":"V1","logical_provisioned":107374182400,"logical_used":53687091200,"thin_savings":0.5}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/metrics_wear_drive.json
new
+1
@@ -0,0 +1 @@
1
+[{"drive_id":"D1","percent_endurance_remaining":98.5}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/nas_server.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"NAS1","name":"NAS-Server-1","operational_status":"Started","current_node_id":"N1"}]
\ No newline at end of file
src/go/plugin/go.d/collector/powerstore/testdata/volume.json
new
+1
@@ -0,0 +1 @@
1
+[{"id":"V1","name":"prod-vol-01","size":107374182400,"logical_used":53687091200,"state":"Ready","type":"Primary","appliance_id":"A1"},{"id":"V2","name":"test-vol-01","size":53687091200,"logical_used":10737418240,"state":"Ready","type":"Primary","appliance_id":"A1"}]
\ No newline at end of file
src/go/plugin/go.d/config/go.d/powerstore.conf
new
+16
@@ -0,0 +1,16 @@
1
+## All available configuration options, their descriptions and default values:
2
+## https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/collector/powerstore#readme
3
+
4
+#jobs:
5
+# - name : local
6
+# url : https://10.0.0.1
7
+# username : admin
8
+# password : password
9
+# tls_skip_verify: yes
10
+
11
+# - name : remote
12
+# url : https://10.0.0.2
13
+# username : admin
14
+# password : password
15
+# tls_skip_verify: yes
16
+# volume_selector: "prod-* !*-temp !*-test"