go.d/vsphere: add datastore, cluster, and resource pool monitoring (#21924)
Costa Tsaousis committed
Mar 10, 2026 at 22:28 UTC
d2d04ab8d22cde55e6a29ffe5a5062d6179b5e24
20 files changed
+3359
-291
src/go/plugin/go.d/collector/vsphere/charts.go
+796
-2
@@ -11,7 +11,52 @@ import (
11
)
12
13
const (
14
- prioVMCPUUtilization = collectorapi.Priority + iota
14
+ prioClusterHosts = collectorapi.Priority + iota
15
+ prioClusterCPUCapacity
16
+ prioClusterMemCapacity
17
+ prioClusterCPUTopology
18
+ prioClusterDRSConfig
19
+ prioClusterHAConfig
20
+ prioClusterOverallStatus
21
+ prioClusterVMotions
22
+ prioClusterDRSScore
23
+ prioClusterDRSBalance
24
+ prioClusterVMCount
25
+ prioClusterUsageCPU
26
+ prioClusterUsageMem
27
+ prioClusterCPUUtilization
28
+ prioClusterCPUUsage
29
+ prioClusterMemUtilization
30
+ prioClusterMemUsage
31
+ prioClusterServicesFairness
32
+ prioClusterServicesEffectiveCPU
33
+ prioClusterServicesEffectiveMem
34
+ prioClusterServicesFailover
35
+ prioClusterVMMigrations
36
+ prioClusterVMLifecycle
37
+ prioClusterVMManagement
38
+ prioClusterVMGuestOps
39
+ prioClusterVMColdMigrations
40
+
41
+ prioResourcePoolCPUUsage
42
+ prioResourcePoolCPUEntitlement
43
+ prioResourcePoolCPUAllocation
44
+ prioResourcePoolMemUsage
45
+ prioResourcePoolMemEntitlement
46
+ prioResourcePoolMemAllocation
47
+ prioResourcePoolMemBreakdown
48
+ prioResourcePoolCPUConfig
49
+ prioResourcePoolMemConfig
50
+ prioResourcePoolOverallStatus
51
+
52
+ prioDatastoreDiskIO
53
+ prioDatastoreDiskIOPS
54
+ prioDatastoreDiskLatency
55
+ prioDatastoreSpaceUtilization
56
+ prioDatastoreSpaceUsage
57
+ prioDatastoreOverallStatus
58
+
59
+ prioVMCPUUtilization
60
prioVmMemoryUtilization
61
prioVmMemoryUsage
62
prioVmMemorySwapUsage
@@ -386,6 +431,97 @@ var (
431
}
432
)
433
434
+var (
435
+ datastorePropertyChartsTmpl = collectorapi.Charts{
436
+ datastoreSpaceUtilizationChartTmpl.Copy(),
437
+ datastoreSpaceUsageChartTmpl.Copy(),
438
+ datastoreOverallStatusChartTmpl.Copy(),
439
+ }
440
+ datastorePerfChartsTmpl = collectorapi.Charts{
441
+ datastoreDiskIOChartTmpl.Copy(),
442
+ datastoreDiskIOPSChartTmpl.Copy(),
443
+ datastoreDiskLatencyChartTmpl.Copy(),
444
+ }
445
+
446
+ datastoreDiskIOChartTmpl = collectorapi.Chart{
447
+ ID: "%s_disk_io",
448
+ Title: "Datastore disk IO",
449
+ Units: "KiB/s",
450
+ Fam: "datastores disk",
451
+ Ctx: "vsphere.datastore_disk_io",
452
+ Type: collectorapi.Area,
453
+ Priority: prioDatastoreDiskIO,
454
+ Dims: collectorapi.Dims{
455
+ {ID: "%s_datastore.read.average", Name: "read"},
456
+ {ID: "%s_datastore.write.average", Name: "write", Mul: -1},
457
+ },
458
+ }
459
+ datastoreDiskIOPSChartTmpl = collectorapi.Chart{
460
+ ID: "%s_disk_iops",
461
+ Title: "Datastore disk IOPS",
462
+ Units: "operations/s",
463
+ Fam: "datastores disk",
464
+ Ctx: "vsphere.datastore_disk_iops",
465
+ Priority: prioDatastoreDiskIOPS,
466
+ Dims: collectorapi.Dims{
467
+ {ID: "%s_datastore.numberReadAveraged.average", Name: "reads"},
468
+ {ID: "%s_datastore.numberWriteAveraged.average", Name: "writes", Mul: -1},
469
+ },
470
+ }
471
+ datastoreDiskLatencyChartTmpl = collectorapi.Chart{
472
+ ID: "%s_disk_latency",
473
+ Title: "Datastore disk latency",
474
+ Units: "milliseconds",
475
+ Fam: "datastores disk",
476
+ Ctx: "vsphere.datastore_disk_latency",
477
+ Priority: prioDatastoreDiskLatency,
478
+ Dims: collectorapi.Dims{
479
+ {ID: "%s_datastore.totalReadLatency.average", Name: "read"},
480
+ {ID: "%s_datastore.totalWriteLatency.average", Name: "write"},
481
+ },
482
+ }
483
+
484
+ datastoreSpaceUtilizationChartTmpl = collectorapi.Chart{
485
+ ID: "%s_space_utilization",
486
+ Title: "Datastore space utilization",
487
+ Units: "percentage",
488
+ Fam: "datastores space",
489
+ Ctx: "vsphere.datastore_space_utilization",
490
+ Priority: prioDatastoreSpaceUtilization,
491
+ Dims: collectorapi.Dims{
492
+ {ID: "%s_used_space_pct", Name: "used", Div: 100},
493
+ },
494
+ }
495
+ datastoreSpaceUsageChartTmpl = collectorapi.Chart{
496
+ ID: "%s_space_usage",
497
+ Title: "Datastore space usage",
498
+ Units: "bytes",
499
+ Fam: "datastores space",
500
+ Ctx: "vsphere.datastore_space_usage",
501
+ Priority: prioDatastoreSpaceUsage,
502
+ Dims: collectorapi.Dims{
503
+ {ID: "%s_capacity", Name: "capacity"},
504
+ {ID: "%s_free_space", Name: "free"},
505
+ {ID: "%s_used_space", Name: "used"},
506
+ },
507
+ }
508
+
509
+ datastoreOverallStatusChartTmpl = collectorapi.Chart{
510
+ ID: "%s_overall_status",
511
+ Title: "Datastore overall alarm status",
512
+ Units: "status",
513
+ Fam: "datastores status",
514
+ Ctx: "vsphere.datastore_overall_status",
515
+ Priority: prioDatastoreOverallStatus,
516
+ Dims: collectorapi.Dims{
517
+ {ID: "%s_overall.status.green", Name: "green"},
518
+ {ID: "%s_overall.status.red", Name: "red"},
519
+ {ID: "%s_overall.status.yellow", Name: "yellow"},
520
+ {ID: "%s_overall.status.gray", Name: "gray"},
521
+ },
522
+ }
523
+)
524
+
525
const failedUpdatesLimit = 10
526
527
func (c *Collector) updateCharts() {
@@ -428,6 +564,90 @@ func (c *Collector) updateCharts() {
564
c.Error(err)
565
}
566
}
567
+
568
+ for id, fails := range c.discoveredDatastores {
569
+ if fails >= failedUpdatesLimit {
570
+ c.removeFromCharts(id)
571
+ delete(c.charted, id)
572
+ delete(c.discoveredDatastores, id)
573
+ delete(c.datastorePerfReceived, id)
574
+ delete(c.datastorePerfCharted, id)
575
+ continue
576
+ }
577
+
578
+ ds := c.resources.Datastores.Get(id)
579
+ if ds == nil || fails != 0 {
580
+ continue
581
+ }
582
+
583
+ if !c.charted[id] {
584
+ c.charted[id] = true
585
+ charts := newDatastorePropertyCharts(ds)
586
+ if err := c.Charts().Add(*charts...); err != nil {
587
+ c.Error(err)
588
+ }
589
+ }
590
+
591
+ if c.datastorePerfReceived[id] && !c.datastorePerfCharted[id] {
592
+ c.datastorePerfCharted[id] = true
593
+ charts := newDatastorePerfCharts(ds)
594
+ if err := c.Charts().Add(*charts...); err != nil {
595
+ c.Error(err)
596
+ }
597
+ }
598
+ }
599
+
600
+ for id, fails := range c.discoveredClusters {
601
+ if fails >= failedUpdatesLimit {
602
+ c.removeFromCharts(id)
603
+ delete(c.charted, id)
604
+ delete(c.discoveredClusters, id)
605
+ delete(c.clusterPerfReceived, id)
606
+ delete(c.clusterPerfCharted, id)
607
+ continue
608
+ }
609
+
610
+ cl := c.resources.Clusters.Get(id)
611
+ if cl == nil || fails != 0 {
612
+ continue
613
+ }
614
+
615
+ if !c.charted[id] {
616
+ c.charted[id] = true
617
+ charts := newClusterPropertyCharts(cl)
618
+ if err := c.Charts().Add(*charts...); err != nil {
619
+ c.Error(err)
620
+ }
621
+ }
622
+
623
+ if c.clusterPerfReceived[id] && !c.clusterPerfCharted[id] {
624
+ c.clusterPerfCharted[id] = true
625
+ charts := newClusterPerfCharts(cl)
626
+ if err := c.Charts().Add(*charts...); err != nil {
627
+ c.Error(err)
628
+ }
629
+ }
630
+ }
631
+
632
+ for id, fails := range c.discoveredResourcePools {
633
+ if fails >= failedUpdatesLimit {
634
+ c.removeFromCharts(id)
635
+ delete(c.charted, id)
636
+ delete(c.discoveredResourcePools, id)
637
+ continue
638
+ }
639
+
640
+ rp := c.resources.ResourcePools.Get(id)
641
+ if rp == nil || c.charted[id] || fails != 0 {
642
+ continue
643
+ }
644
+
645
+ c.charted[id] = true
646
+ charts := newResourcePoolCharts(rp)
647
+ if err := c.Charts().Add(*charts...); err != nil {
648
+ c.Error(err)
649
+ }
650
+ }
651
}
652
653
func newVMCHarts(vm *rs.VM) *collectorapi.Charts {
@@ -482,9 +702,583 @@ func getHostClusterName(host *rs.Host) string {
702
return host.Hier.Cluster.Name
703
}
704
705
+func newDatastorePropertyCharts(ds *rs.Datastore) *collectorapi.Charts {
706
+ charts := datastorePropertyChartsTmpl.Copy()
707
+ applyDatastoreChartLabels(charts, ds)
708
+ return charts
709
+}
710
+
711
+func newDatastorePerfCharts(ds *rs.Datastore) *collectorapi.Charts {
712
+ charts := datastorePerfChartsTmpl.Copy()
713
+ applyDatastoreChartLabels(charts, ds)
714
+ return charts
715
+}
716
+
717
+func applyDatastoreChartLabels(charts *collectorapi.Charts, ds *rs.Datastore) {
718
+ for _, chart := range *charts {
719
+ chart.ID = fmt.Sprintf(chart.ID, ds.ID)
720
+ chart.Labels = []collectorapi.Label{
721
+ {Key: "datacenter", Value: ds.Hier.DC.Name},
722
+ {Key: "datastore", Value: ds.Name},
723
+ {Key: "type", Value: ds.Type},
724
+ }
725
+ for _, dim := range chart.Dims {
726
+ dim.ID = fmt.Sprintf(dim.ID, ds.ID)
727
+ }
728
+ }
729
+}
730
+
731
+// --- Cluster chart templates ---
732
+
733
+var (
734
+ clusterPropertyChartsTmpl = collectorapi.Charts{
735
+ clusterHostsChartTmpl.Copy(),
736
+ clusterCPUCapacityChartTmpl.Copy(),
737
+ clusterMemCapacityChartTmpl.Copy(),
738
+ clusterCPUTopologyChartTmpl.Copy(),
739
+ clusterDRSConfigChartTmpl.Copy(),
740
+ clusterHAConfigChartTmpl.Copy(),
741
+ clusterOverallStatusChartTmpl.Copy(),
742
+ clusterVMotionsChartTmpl.Copy(),
743
+ clusterDRSScoreChartTmpl.Copy(),
744
+ clusterDRSBalanceChartTmpl.Copy(),
745
+ clusterVMCountChartTmpl.Copy(),
746
+ clusterUsageCPUChartTmpl.Copy(),
747
+ clusterUsageMemChartTmpl.Copy(),
748
+ }
749
+ clusterPerfChartsTmpl = collectorapi.Charts{
750
+ clusterCPUUtilizationChartTmpl.Copy(),
751
+ clusterCPUUsageChartTmpl.Copy(),
752
+ clusterMemUtilizationChartTmpl.Copy(),
753
+ clusterMemUsageChartTmpl.Copy(),
754
+ clusterServicesFairnessChartTmpl.Copy(),
755
+ clusterServicesEffectiveCPUChartTmpl.Copy(),
756
+ clusterServicesEffectiveMemChartTmpl.Copy(),
757
+ clusterServicesFailoverChartTmpl.Copy(),
758
+ clusterVMMigrationsChartTmpl.Copy(),
759
+ clusterVMLifecycleChartTmpl.Copy(),
760
+ clusterVMManagementChartTmpl.Copy(),
761
+ clusterVMGuestOpsChartTmpl.Copy(),
762
+ clusterVMColdMigrationsChartTmpl.Copy(),
763
+ }
764
+
765
+ // Property charts
766
+ clusterHostsChartTmpl = collectorapi.Chart{
767
+ ID: "%s_hosts",
768
+ Title: "Cluster host count",
769
+ Units: "hosts",
770
+ Fam: "clusters hosts",
771
+ Ctx: "vsphere.cluster_hosts",
772
+ Priority: prioClusterHosts,
773
+ Dims: collectorapi.Dims{
774
+ {ID: "%s_num_hosts", Name: "total"},
775
+ {ID: "%s_num_effective_hosts", Name: "effective"},
776
+ },
777
+ }
778
+ clusterCPUCapacityChartTmpl = collectorapi.Chart{
779
+ ID: "%s_cpu_capacity",
780
+ Title: "Cluster CPU capacity",
781
+ Units: "MHz",
782
+ Fam: "clusters cpu",
783
+ Ctx: "vsphere.cluster_cpu_capacity",
784
+ Priority: prioClusterCPUCapacity,
785
+ Dims: collectorapi.Dims{
786
+ {ID: "%s_total_cpu", Name: "total"},
787
+ {ID: "%s_effective_cpu", Name: "effective"},
788
+ },
789
+ }
790
+ clusterMemCapacityChartTmpl = collectorapi.Chart{
791
+ ID: "%s_mem_capacity",
792
+ Title: "Cluster memory capacity",
793
+ Units: "bytes",
794
+ Fam: "clusters mem",
795
+ Ctx: "vsphere.cluster_mem_capacity",
796
+ Priority: prioClusterMemCapacity,
797
+ Dims: collectorapi.Dims{
798
+ {ID: "%s_total_memory", Name: "total"},
799
+ {ID: "%s_effective_memory", Name: "effective"},
800
+ },
801
+ }
802
+ clusterCPUTopologyChartTmpl = collectorapi.Chart{
803
+ ID: "%s_cpu_topology",
804
+ Title: "Cluster CPU topology",
805
+ Units: "count",
806
+ Fam: "clusters cpu",
807
+ Ctx: "vsphere.cluster_cpu_topology",
808
+ Priority: prioClusterCPUTopology,
809
+ Dims: collectorapi.Dims{
810
+ {ID: "%s_num_cpu_cores", Name: "cores"},
811
+ {ID: "%s_num_cpu_threads", Name: "threads"},
812
+ },
813
+ }
814
+ clusterDRSConfigChartTmpl = collectorapi.Chart{
815
+ ID: "%s_drs_config",
816
+ Title: "Cluster DRS enabled",
817
+ Units: "status",
818
+ Fam: "clusters config",
819
+ Ctx: "vsphere.cluster_drs_config",
820
+ Priority: prioClusterDRSConfig,
821
+ Dims: collectorapi.Dims{
822
+ {ID: "%s_drs_enabled", Name: "enabled"},
823
+ },
824
+ }
825
+ clusterHAConfigChartTmpl = collectorapi.Chart{
826
+ ID: "%s_ha_config",
827
+ Title: "Cluster HA configuration",
828
+ Units: "status",
829
+ Fam: "clusters config",
830
+ Ctx: "vsphere.cluster_ha_config",
831
+ Priority: prioClusterHAConfig,
832
+ Dims: collectorapi.Dims{
833
+ {ID: "%s_ha_enabled", Name: "enabled"},
834
+ {ID: "%s_ha_adm_ctrl_enabled", Name: "admission_control"},
835
+ },
836
+ }
837
+ clusterOverallStatusChartTmpl = collectorapi.Chart{
838
+ ID: "%s_overall_status",
839
+ Title: "Cluster overall alarm status",
840
+ Units: "status",
841
+ Fam: "clusters status",
842
+ Ctx: "vsphere.cluster_overall_status",
843
+ Priority: prioClusterOverallStatus,
844
+ Dims: collectorapi.Dims{
845
+ {ID: "%s_overall.status.green", Name: "green"},
846
+ {ID: "%s_overall.status.red", Name: "red"},
847
+ {ID: "%s_overall.status.yellow", Name: "yellow"},
848
+ {ID: "%s_overall.status.gray", Name: "gray"},
849
+ },
850
+ }
851
+ clusterVMotionsChartTmpl = collectorapi.Chart{
852
+ ID: "%s_vmotions",
853
+ Title: "Cluster cumulative vMotion count",
854
+ Units: "migrations",
855
+ Fam: "clusters migrations",
856
+ Ctx: "vsphere.cluster_vmotions",
857
+ Type: collectorapi.Line,
858
+ Priority: prioClusterVMotions,
859
+ Dims: collectorapi.Dims{
860
+ {ID: "%s_num_vmotions", Name: "vmotions", Algo: collectorapi.Incremental},
861
+ },
862
+ }
863
+ clusterDRSScoreChartTmpl = collectorapi.Chart{
864
+ ID: "%s_drs_score",
865
+ Title: "Cluster DRS score",
866
+ Units: "percentage",
867
+ Fam: "clusters drs",
868
+ Ctx: "vsphere.cluster_drs_score",
869
+ Priority: prioClusterDRSScore,
870
+ Dims: collectorapi.Dims{
871
+ {ID: "%s_drs_score", Name: "score"},
872
+ },
873
+ }
874
+ clusterDRSBalanceChartTmpl = collectorapi.Chart{
875
+ ID: "%s_drs_balance",
876
+ Title: "Cluster DRS load balance",
877
+ Units: "score",
878
+ Fam: "clusters drs",
879
+ Ctx: "vsphere.cluster_drs_balance",
880
+ Priority: prioClusterDRSBalance,
881
+ Dims: collectorapi.Dims{
882
+ {ID: "%s_current_balance", Name: "current", Div: 1000},
883
+ {ID: "%s_target_balance", Name: "target", Div: 1000},
884
+ },
885
+ }
886
+ clusterVMCountChartTmpl = collectorapi.Chart{
887
+ ID: "%s_vm_count",
888
+ Title: "Cluster VM count",
889
+ Units: "VMs",
890
+ Fam: "clusters vms",
891
+ Ctx: "vsphere.cluster_vm_count",
892
+ Priority: prioClusterVMCount,
893
+ Dims: collectorapi.Dims{
894
+ {ID: "%s_usage_total_vm_count", Name: "total"},
895
+ {ID: "%s_usage_powered_off_vm_count", Name: "powered_off"},
896
+ },
897
+ }
898
+ clusterUsageCPUChartTmpl = collectorapi.Chart{
899
+ ID: "%s_usage_cpu",
900
+ Title: "Cluster DRS CPU usage summary",
901
+ Units: "MHz",
902
+ Fam: "clusters cpu",
903
+ Ctx: "vsphere.cluster_usage_cpu",
904
+ Priority: prioClusterUsageCPU,
905
+ Dims: collectorapi.Dims{
906
+ {ID: "%s_usage_cpu_demand_mhz", Name: "demand"},
907
+ {ID: "%s_usage_cpu_entitled_mhz", Name: "entitled"},
908
+ {ID: "%s_usage_cpu_reservation_mhz", Name: "reserved"},
909
+ },
910
+ }
911
+ clusterUsageMemChartTmpl = collectorapi.Chart{
912
+ ID: "%s_usage_mem",
913
+ Title: "Cluster DRS memory usage summary",
914
+ Units: "MB",
915
+ Fam: "clusters mem",
916
+ Ctx: "vsphere.cluster_usage_mem",
917
+ Priority: prioClusterUsageMem,
918
+ Dims: collectorapi.Dims{
919
+ {ID: "%s_usage_mem_demand_mb", Name: "demand"},
920
+ {ID: "%s_usage_mem_entitled_mb", Name: "entitled"},
921
+ {ID: "%s_usage_mem_reservation_mb", Name: "reserved"},
922
+ },
923
+ }
924
+
925
+ // Perf charts (created only when perf data arrives)
926
+ clusterCPUUtilizationChartTmpl = collectorapi.Chart{
927
+ ID: "%s_cpu_utilization",
928
+ Title: "Cluster CPU utilization",
929
+ Units: "percentage",
930
+ Fam: "clusters cpu",
931
+ Ctx: "vsphere.cluster_cpu_utilization",
932
+ Priority: prioClusterCPUUtilization,
933
+ Dims: collectorapi.Dims{
934
+ {ID: "%s_cpu.usage.average", Name: "used", Div: 100},
935
+ },
936
+ }
937
+ clusterCPUUsageChartTmpl = collectorapi.Chart{
938
+ ID: "%s_cpu_usage_mhz",
939
+ Title: "Cluster CPU usage",
940
+ Units: "MHz",
941
+ Fam: "clusters cpu",
942
+ Ctx: "vsphere.cluster_cpu_usage",
943
+ Priority: prioClusterCPUUsage,
944
+ Dims: collectorapi.Dims{
945
+ {ID: "%s_cpu.usagemhz.average", Name: "used"},
946
+ {ID: "%s_cpu.totalmhz.average", Name: "total"},
947
+ },
948
+ }
949
+ clusterMemUtilizationChartTmpl = collectorapi.Chart{
950
+ ID: "%s_mem_utilization",
951
+ Title: "Cluster memory utilization",
952
+ Units: "percentage",
953
+ Fam: "clusters mem",
954
+ Ctx: "vsphere.cluster_mem_utilization",
955
+ Priority: prioClusterMemUtilization,
956
+ Dims: collectorapi.Dims{
957
+ {ID: "%s_mem.usage.average", Name: "used", Div: 100},
958
+ },
959
+ }
960
+ clusterMemUsageChartTmpl = collectorapi.Chart{
961
+ ID: "%s_mem_usage",
962
+ Title: "Cluster memory usage",
963
+ Units: "KiB",
964
+ Fam: "clusters mem",
965
+ Ctx: "vsphere.cluster_mem_usage",
966
+ Priority: prioClusterMemUsage,
967
+ Dims: collectorapi.Dims{
968
+ {ID: "%s_mem.consumed.average", Name: "consumed"},
969
+ {ID: "%s_mem.active.average", Name: "active"},
970
+ {ID: "%s_mem.granted.average", Name: "granted"},
971
+ {ID: "%s_mem.shared.average", Name: "shared"},
972
+ {ID: "%s_mem.overhead.average", Name: "overhead"},
973
+ {ID: "%s_mem.swapused.average", Name: "swap_used"},
974
+ },
975
+ }
976
+ clusterServicesFairnessChartTmpl = collectorapi.Chart{
977
+ ID: "%s_services_fairness",
978
+ Title: "Cluster DRS resource distribution fairness",
979
+ Units: "score",
980
+ Fam: "clusters drs",
981
+ Ctx: "vsphere.cluster_services_fairness",
982
+ Priority: prioClusterServicesFairness,
983
+ Dims: collectorapi.Dims{
984
+ {ID: "%s_clusterServices.cpufairness.latest", Name: "cpu"},
985
+ {ID: "%s_clusterServices.memfairness.latest", Name: "memory"},
986
+ },
987
+ }
988
+ clusterServicesEffectiveCPUChartTmpl = collectorapi.Chart{
989
+ ID: "%s_services_effective_cpu",
990
+ Title: "Cluster effective CPU capacity",
991
+ Units: "MHz",
992
+ Fam: "clusters cpu",
993
+ Ctx: "vsphere.cluster_services_effective_cpu",
994
+ Priority: prioClusterServicesEffectiveCPU,
995
+ Dims: collectorapi.Dims{
996
+ {ID: "%s_clusterServices.effectivecpu.average", Name: "effective_cpu"},
997
+ },
998
+ }
999
+ clusterServicesEffectiveMemChartTmpl = collectorapi.Chart{
1000
+ ID: "%s_services_effective_mem",
1001
+ Title: "Cluster effective memory capacity",
1002
+ Units: "MB",
1003
+ Fam: "clusters mem",
1004
+ Ctx: "vsphere.cluster_services_effective_mem",
1005
+ Priority: prioClusterServicesEffectiveMem,
1006
+ Dims: collectorapi.Dims{
1007
+ {ID: "%s_clusterServices.effectivemem.average", Name: "effective_mem"},
1008
+ },
1009
+ }
1010
+ clusterServicesFailoverChartTmpl = collectorapi.Chart{
1011
+ ID: "%s_services_failover",
1012
+ Title: "Cluster HA failover capacity",
1013
+ Units: "failures",
1014
+ Fam: "clusters ha",
1015
+ Ctx: "vsphere.cluster_services_failover",
1016
+ Priority: prioClusterServicesFailover,
1017
+ Dims: collectorapi.Dims{
1018
+ {ID: "%s_clusterServices.failover.latest", Name: "failures_tolerable"},
1019
+ },
1020
+ }
1021
+ clusterVMMigrationsChartTmpl = collectorapi.Chart{
1022
+ ID: "%s_vm_migrations",
1023
+ Title: "Cluster VM migration operations",
1024
+ Units: "operations",
1025
+ Fam: "clusters vmop",
1026
+ Ctx: "vsphere.cluster_vm_migrations",
1027
+ Priority: prioClusterVMMigrations,
1028
+ Dims: collectorapi.Dims{
1029
+ {ID: "%s_vmop.numVMotion.latest", Name: "vmotion"},
1030
+ {ID: "%s_vmop.numSVMotion.latest", Name: "svmotion"},
1031
+ {ID: "%s_vmop.numXVMotion.latest", Name: "xvmotion"},
1032
+ },
1033
+ }
1034
+ clusterVMLifecycleChartTmpl = collectorapi.Chart{
1035
+ ID: "%s_vm_lifecycle",
1036
+ Title: "Cluster VM lifecycle operations",
1037
+ Units: "operations",
1038
+ Fam: "clusters vmop",
1039
+ Ctx: "vsphere.cluster_vm_lifecycle",
1040
+ Priority: prioClusterVMLifecycle,
1041
+ Dims: collectorapi.Dims{
1042
+ {ID: "%s_vmop.numPoweron.latest", Name: "poweron"},
1043
+ {ID: "%s_vmop.numPoweroff.latest", Name: "poweroff"},
1044
+ {ID: "%s_vmop.numCreate.latest", Name: "create"},
1045
+ {ID: "%s_vmop.numDestroy.latest", Name: "destroy"},
1046
+ {ID: "%s_vmop.numClone.latest", Name: "clone"},
1047
+ {ID: "%s_vmop.numDeploy.latest", Name: "deploy"},
1048
+ },
1049
+ }
1050
+ clusterVMManagementChartTmpl = collectorapi.Chart{
1051
+ ID: "%s_vm_management",
1052
+ Title: "Cluster VM management operations",
1053
+ Units: "operations",
1054
+ Fam: "clusters vmop",
1055
+ Ctx: "vsphere.cluster_vm_management",
1056
+ Priority: prioClusterVMManagement,
1057
+ Dims: collectorapi.Dims{
1058
+ {ID: "%s_vmop.numReconfigure.latest", Name: "reconfigure"},
1059
+ {ID: "%s_vmop.numReset.latest", Name: "reset"},
1060
+ {ID: "%s_vmop.numSuspend.latest", Name: "suspend"},
1061
+ {ID: "%s_vmop.numRegister.latest", Name: "register"},
1062
+ {ID: "%s_vmop.numUnregister.latest", Name: "unregister"},
1063
+ },
1064
+ }
1065
+ clusterVMGuestOpsChartTmpl = collectorapi.Chart{
1066
+ ID: "%s_vm_guest_ops",
1067
+ Title: "Cluster VM guest operations",
1068
+ Units: "operations",
1069
+ Fam: "clusters vmop",
1070
+ Ctx: "vsphere.cluster_vm_guest_ops",
1071
+ Priority: prioClusterVMGuestOps,
1072
+ Dims: collectorapi.Dims{
1073
+ {ID: "%s_vmop.numRebootGuest.latest", Name: "reboot"},
1074
+ {ID: "%s_vmop.numShutdownGuest.latest", Name: "shutdown"},
1075
+ {ID: "%s_vmop.numStandbyGuest.latest", Name: "standby"},
1076
+ },
1077
+ }
1078
+ clusterVMColdMigrationsChartTmpl = collectorapi.Chart{
1079
+ ID: "%s_vm_cold_migrations",
1080
+ Title: "Cluster VM cold migration operations",
1081
+ Units: "operations",
1082
+ Fam: "clusters vmop",
1083
+ Ctx: "vsphere.cluster_vm_cold_migrations",
1084
+ Priority: prioClusterVMColdMigrations,
1085
+ Dims: collectorapi.Dims{
1086
+ {ID: "%s_vmop.numChangeDS.latest", Name: "change_ds"},
1087
+ {ID: "%s_vmop.numChangeHost.latest", Name: "change_host"},
1088
+ {ID: "%s_vmop.numChangeHostDS.latest", Name: "change_host_ds"},
1089
+ },
1090
+ }
1091
+)
1092
+
1093
+// --- Resource Pool chart templates ---
1094
+
1095
+var (
1096
+ resourcePoolChartsTmpl = collectorapi.Charts{
1097
+ rpCPUUsageChartTmpl.Copy(),
1098
+ rpCPUEntitlementChartTmpl.Copy(),
1099
+ rpCPUAllocationChartTmpl.Copy(),
1100
+ rpMemUsageChartTmpl.Copy(),
1101
+ rpMemEntitlementChartTmpl.Copy(),
1102
+ rpMemAllocationChartTmpl.Copy(),
1103
+ rpMemBreakdownChartTmpl.Copy(),
1104
+ rpCPUConfigChartTmpl.Copy(),
1105
+ rpMemConfigChartTmpl.Copy(),
1106
+ rpOverallStatusChartTmpl.Copy(),
1107
+ }
1108
+
1109
+ rpCPUUsageChartTmpl = collectorapi.Chart{
1110
+ ID: "%s_cpu_usage",
1111
+ Title: "Resource Pool CPU usage vs demand",
1112
+ Units: "MHz",
1113
+ Fam: "resource pools cpu",
1114
+ Ctx: "vsphere.resource_pool_cpu_usage",
1115
+ Priority: prioResourcePoolCPUUsage,
1116
+ Dims: collectorapi.Dims{
1117
+ {ID: "%s_cpu_usage", Name: "usage"},
1118
+ {ID: "%s_cpu_demand", Name: "demand"},
1119
+ },
1120
+ }
1121
+ rpCPUEntitlementChartTmpl = collectorapi.Chart{
1122
+ ID: "%s_cpu_entitlement",
1123
+ Title: "Resource Pool CPU entitlement",
1124
+ Units: "MHz",
1125
+ Fam: "resource pools cpu",
1126
+ Ctx: "vsphere.resource_pool_cpu_entitlement",
1127
+ Priority: prioResourcePoolCPUEntitlement,
1128
+ Dims: collectorapi.Dims{
1129
+ {ID: "%s_cpu_entitlement_distributed", Name: "distributed"},
1130
+ },
1131
+ }
1132
+ rpCPUAllocationChartTmpl = collectorapi.Chart{
1133
+ ID: "%s_cpu_allocation",
1134
+ Title: "Resource Pool CPU allocation",
1135
+ Units: "MHz",
1136
+ Fam: "resource pools cpu",
1137
+ Ctx: "vsphere.resource_pool_cpu_allocation",
1138
+ Priority: prioResourcePoolCPUAllocation,
1139
+ Dims: collectorapi.Dims{
1140
+ {ID: "%s_cpu_reservation_used", Name: "reservation_used"},
1141
+ {ID: "%s_cpu_unreserved_for_vm", Name: "unreserved_for_vm"},
1142
+ {ID: "%s_cpu_max_usage", Name: "max_usage"},
1143
+ },
1144
+ }
1145
+ rpMemUsageChartTmpl = collectorapi.Chart{
1146
+ ID: "%s_mem_usage",
1147
+ Title: "Resource Pool memory usage",
1148
+ Units: "MB",
1149
+ Fam: "resource pools mem",
1150
+ Ctx: "vsphere.resource_pool_mem_usage",
1151
+ Priority: prioResourcePoolMemUsage,
1152
+ Dims: collectorapi.Dims{
1153
+ {ID: "%s_mem_usage_host", Name: "host"},
1154
+ {ID: "%s_mem_usage_guest", Name: "guest"},
1155
+ },
1156
+ }
1157
+ rpMemEntitlementChartTmpl = collectorapi.Chart{
1158
+ ID: "%s_mem_entitlement",
1159
+ Title: "Resource Pool memory entitlement",
1160
+ Units: "MB",
1161
+ Fam: "resource pools mem",
1162
+ Ctx: "vsphere.resource_pool_mem_entitlement",
1163
+ Priority: prioResourcePoolMemEntitlement,
1164
+ Dims: collectorapi.Dims{
1165
+ {ID: "%s_mem_entitlement_distributed", Name: "distributed"},
1166
+ },
1167
+ }
1168
+ rpMemAllocationChartTmpl = collectorapi.Chart{
1169
+ ID: "%s_mem_allocation",
1170
+ Title: "Resource Pool memory allocation",
1171
+ Units: "bytes",
1172
+ Fam: "resource pools mem",
1173
+ Ctx: "vsphere.resource_pool_mem_allocation",
1174
+ Priority: prioResourcePoolMemAllocation,
1175
+ Dims: collectorapi.Dims{
1176
+ {ID: "%s_mem_reservation_used", Name: "reservation_used"},
1177
+ {ID: "%s_mem_unreserved_for_vm", Name: "unreserved_for_vm"},
1178
+ {ID: "%s_mem_max_usage", Name: "max_usage"},
1179
+ },
1180
+ }
1181
+ rpMemBreakdownChartTmpl = collectorapi.Chart{
1182
+ ID: "%s_mem_breakdown",
1183
+ Title: "Resource Pool memory state breakdown",
1184
+ Units: "MB",
1185
+ Fam: "resource pools mem",
1186
+ Ctx: "vsphere.resource_pool_mem_breakdown",
1187
+ Priority: prioResourcePoolMemBreakdown,
1188
+ Dims: collectorapi.Dims{
1189
+ {ID: "%s_mem_private", Name: "private"},
1190
+ {ID: "%s_mem_shared", Name: "shared"},
1191
+ {ID: "%s_mem_swapped", Name: "swapped"},
1192
+ {ID: "%s_mem_ballooned", Name: "ballooned"},
1193
+ {ID: "%s_mem_overhead", Name: "overhead"},
1194
+ {ID: "%s_mem_consumed_overhead", Name: "consumed_overhead"},
1195
+ {ID: "%s_mem_compressed", Name: "compressed", Div: 1024},
1196
+ },
1197
+ }
1198
+ rpCPUConfigChartTmpl = collectorapi.Chart{
1199
+ ID: "%s_cpu_config",
1200
+ Title: "Resource Pool CPU configured reservation and limit",
1201
+ Units: "MHz",
1202
+ Fam: "resource pools cpu",
1203
+ Ctx: "vsphere.resource_pool_cpu_config",
1204
+ Priority: prioResourcePoolCPUConfig,
1205
+ Dims: collectorapi.Dims{
1206
+ {ID: "%s_cpu_reservation", Name: "reservation"},
1207
+ {ID: "%s_cpu_limit", Name: "limit"},
1208
+ },
1209
+ }
1210
+ rpMemConfigChartTmpl = collectorapi.Chart{
1211
+ ID: "%s_mem_config",
1212
+ Title: "Resource Pool memory configured reservation and limit",
1213
+ Units: "MB",
1214
+ Fam: "resource pools mem",
1215
+ Ctx: "vsphere.resource_pool_mem_config",
1216
+ Priority: prioResourcePoolMemConfig,
1217
+ Dims: collectorapi.Dims{
1218
+ {ID: "%s_mem_reservation", Name: "reservation"},
1219
+ {ID: "%s_mem_limit", Name: "limit"},
1220
+ },
1221
+ }
1222
+ rpOverallStatusChartTmpl = collectorapi.Chart{
1223
+ ID: "%s_overall_status",
1224
+ Title: "Resource Pool overall alarm status",
1225
+ Units: "status",
1226
+ Fam: "resource pools status",
1227
+ Ctx: "vsphere.resource_pool_overall_status",
1228
+ Priority: prioResourcePoolOverallStatus,
1229
+ Dims: collectorapi.Dims{
1230
+ {ID: "%s_overall.status.green", Name: "green"},
1231
+ {ID: "%s_overall.status.red", Name: "red"},
1232
+ {ID: "%s_overall.status.yellow", Name: "yellow"},
1233
+ {ID: "%s_overall.status.gray", Name: "gray"},
1234
+ },
1235
+ }
1236
+)
1237
+
1238
+func newClusterPropertyCharts(cl *rs.Cluster) *collectorapi.Charts {
1239
+ charts := clusterPropertyChartsTmpl.Copy()
1240
+ applyClusterChartLabels(charts, cl)
1241
+ return charts
1242
+}
1243
+
1244
+func newClusterPerfCharts(cl *rs.Cluster) *collectorapi.Charts {
1245
+ charts := clusterPerfChartsTmpl.Copy()
1246
+ applyClusterChartLabels(charts, cl)
1247
+ return charts
1248
+}
1249
+
1250
+func applyClusterChartLabels(charts *collectorapi.Charts, cl *rs.Cluster) {
1251
+ for _, chart := range *charts {
1252
+ chart.ID = fmt.Sprintf(chart.ID, cl.ID)
1253
+ chart.Labels = []collectorapi.Label{
1254
+ {Key: "datacenter", Value: cl.Hier.DC.Name},
1255
+ {Key: "cluster", Value: cl.Name},
1256
+ }
1257
+ for _, dim := range chart.Dims {
1258
+ dim.ID = fmt.Sprintf(dim.ID, cl.ID)
1259
+ }
1260
+ }
1261
+}
1262
+
1263
+func newResourcePoolCharts(rp *rs.ResourcePool) *collectorapi.Charts {
1264
+ charts := resourcePoolChartsTmpl.Copy()
1265
+ for _, chart := range *charts {
1266
+ chart.ID = fmt.Sprintf(chart.ID, rp.ID)
1267
+ chart.Labels = []collectorapi.Label{
1268
+ {Key: "datacenter", Value: rp.Hier.DC.Name},
1269
+ {Key: "cluster", Value: rp.Hier.Cluster.Name},
1270
+ {Key: "resource_pool", Value: rp.Name},
1271
+ }
1272
+ for _, dim := range chart.Dims {
1273
+ dim.ID = fmt.Sprintf(dim.ID, rp.ID)
1274
+ }
1275
+ }
1276
+ return charts
1277
+}
1278
+
1279
func (c *Collector) removeFromCharts(prefix string) {
1280
for _, c := range *c.Charts() {
487
- if strings.HasPrefix(c.ID, prefix) {
1281
+ if strings.HasPrefix(c.ID, prefix+"_") {
1282
c.MarkRemove()
1283
c.MarkNotCreated()
1284
}
src/go/plugin/go.d/collector/vsphere/client/client.go
+48
-5
@@ -10,6 +10,7 @@ import (
10
11
"github.com/vmware/govmomi"
12
"github.com/vmware/govmomi/performance"
13
+ "github.com/vmware/govmomi/property"
14
"github.com/vmware/govmomi/session"
15
"github.com/vmware/govmomi/view"
16
"github.com/vmware/govmomi/vim25"
@@ -21,11 +22,13 @@ import (
22
)
23
24
const (
24
- datacenter = "Datacenter"
25
- folder = "Folder"
26
- computeResource = "ComputeResource"
27
- hostSystem = "HostSystem"
28
- virtualMachine = "VirtualMachine"
25
+ datacenter = "Datacenter"
26
+ folder = "Folder"
27
+ computeResource = "ComputeResource"
28
+ hostSystem = "HostSystem"
29
+ virtualMachine = "VirtualMachine"
30
+ datastoreType = "Datastore"
31
+ resourcePoolType = "ResourcePool"
32
33
maxIdleConnections = 32
34
)
@@ -175,6 +178,46 @@ func (c *Client) VirtualMachines(pathSet ...string) (vms []mo.VirtualMachine, er
178
return
179
}
180
181
+func (c *Client) Datastores(pathSet ...string) (datastores []mo.Datastore, err error) {
182
+ err = c.root.Retrieve(context.Background(), []string{datastoreType}, pathSet, &datastores)
183
+ return
184
+}
185
+
186
+func (c *Client) DatastoresByRef(refs []types.ManagedObjectReference, pathSet ...string) ([]mo.Datastore, error) {
187
+ if len(refs) == 0 {
188
+ return nil, nil
189
+ }
190
+ var datastores []mo.Datastore
191
+ pc := property.DefaultCollector(c.client.Client)
192
+ err := pc.Retrieve(context.Background(), refs, pathSet, &datastores)
193
+ return datastores, err
194
+}
195
+
196
+func (c *Client) ClustersByRef(refs []types.ManagedObjectReference, pathSet ...string) ([]mo.ClusterComputeResource, error) {
197
+ if len(refs) == 0 {
198
+ return nil, nil
199
+ }
200
+ var clusters []mo.ClusterComputeResource
201
+ pc := property.DefaultCollector(c.client.Client)
202
+ err := pc.Retrieve(context.Background(), refs, pathSet, &clusters)
203
+ return clusters, err
204
+}
205
+
206
+func (c *Client) ResourcePools(pathSet ...string) (pools []mo.ResourcePool, err error) {
207
+ err = c.root.Retrieve(context.Background(), []string{resourcePoolType}, pathSet, &pools)
208
+ return
209
+}
210
+
211
+func (c *Client) ResourcePoolsByRef(refs []types.ManagedObjectReference, pathSet ...string) ([]mo.ResourcePool, error) {
212
+ if len(refs) == 0 {
213
+ return nil, nil
214
+ }
215
+ var pools []mo.ResourcePool
216
+ pc := property.DefaultCollector(c.client.Client)
217
+ err := pc.Retrieve(context.Background(), refs, pathSet, &pools)
218
+ return pools, err
219
+}
220
+
221
func (c *Client) CounterInfoByName() (map[string]*types.PerfCounterInfo, error) {
222
return c.perf.CounterInfoByName(context.Background())
223
}
src/go/plugin/go.d/collector/vsphere/collect.go
+443
@@ -11,6 +11,8 @@ import (
11
"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/oldmetrix"
12
13
"github.com/vmware/govmomi/performance"
14
+ "github.com/vmware/govmomi/vim25/mo"
15
+ "github.com/vmware/govmomi/vim25/types"
16
)
17
18
// ManagedEntityStatus
@@ -34,6 +36,10 @@ func (c *Collector) collect() (map[string]int64, error) {
36
return nil, err
37
}
38
39
+ c.collectDatastores(mx)
40
+ c.collectClusters(mx)
41
+ c.collectResourcePools(mx)
42
+
43
c.updateCharts()
44
45
c.Debugf("metrics collected, process took %s", time.Since(t))
@@ -124,3 +130,440 @@ func writeVMMetrics(mx map[string]int64, vm *rs.VM, metrics []performance.Metric
130
mx[key] = oldmetrix.Bool(vm.OverallStatus == v)
131
}
132
}
133
+
134
+func (c *Collector) collectDatastores(mx map[string]int64) {
135
+ if len(c.resources.Datastores) == 0 {
136
+ return
137
+ }
138
+
139
+ refreshed := c.refreshDatastoreProperties()
140
+
141
+ metrics := c.ScrapeDatastores(c.resources.Datastores)
142
+ // Datastore perf counters may return empty for vSAN or when no historical data is available yet.
143
+ // This is not an error — we still collect capacity and status from properties.
144
+ c.collectDatastoresMetrics(mx, metrics, refreshed)
145
+}
146
+
147
+func (c *Collector) refreshDatastoreProperties() map[string]bool {
148
+ refreshed := make(map[string]bool)
149
+
150
+ if c.dsPropertyCollector == nil {
151
+ return refreshed
152
+ }
153
+
154
+ refs := make([]types.ManagedObjectReference, 0, len(c.resources.Datastores))
155
+ for _, ds := range c.resources.Datastores {
156
+ refs = append(refs, ds.Ref)
157
+ }
158
+
159
+ dsList, err := c.dsPropertyCollector.DatastoresByRef(refs, "summary", "overallStatus")
160
+ if err != nil {
161
+ c.Warningf("failed to refresh datastore properties: %v", err)
162
+ return refreshed
163
+ }
164
+
165
+ for _, raw := range dsList {
166
+ ds := c.resources.Datastores.Get(raw.Reference().Value)
167
+ if ds == nil {
168
+ continue
169
+ }
170
+ refreshed[ds.ID] = true
171
+ ds.Capacity = raw.Summary.Capacity
172
+ ds.FreeSpace = raw.Summary.FreeSpace
173
+ ds.Accessible = raw.Summary.Accessible
174
+ ds.OverallStatus = string(raw.OverallStatus)
175
+ }
176
+
177
+ return refreshed
178
+}
179
+
180
+func (c *Collector) collectDatastoresMetrics(mx map[string]int64, metrics []performance.EntityMetric, refreshed map[string]bool) {
181
+ for id := range c.discoveredDatastores {
182
+ c.discoveredDatastores[id]++
183
+ }
184
+
185
+ for _, ds := range c.resources.Datastores {
186
+ if refreshed[ds.ID] {
187
+ c.discoveredDatastores[ds.ID] = 0
188
+ }
189
+ writeDatastoreMetrics(mx, ds)
190
+ }
191
+
192
+ for _, metric := range metrics {
193
+ if ds := c.resources.Datastores.Get(metric.Entity.Value); ds != nil {
194
+ c.discoveredDatastores[ds.ID] = 0
195
+ c.datastorePerfReceived[ds.ID] = true
196
+ writeDatastorePerfMetrics(mx, ds, metric.Value)
197
+ }
198
+ }
199
+}
200
+
201
+func writeDatastoreMetrics(mx map[string]int64, ds *rs.Datastore) {
202
+ // VMware docs: Capacity and FreeSpace are guaranteed valid only when Accessible is true.
203
+ var capacity, freeSpace, used int64
204
+ if ds.Accessible {
205
+ capacity = ds.Capacity
206
+ freeSpace = ds.FreeSpace
207
+ used = capacity - freeSpace
208
+ if used < 0 {
209
+ used = 0
210
+ }
211
+ }
212
+
213
+ mx[fmt.Sprintf("%s_capacity", ds.ID)] = capacity
214
+ mx[fmt.Sprintf("%s_free_space", ds.ID)] = freeSpace
215
+ mx[fmt.Sprintf("%s_used_space", ds.ID)] = used
216
+
217
+ if capacity > 0 {
218
+ // use float64 to avoid int64 overflow on datastores larger than 922 TB
219
+ mx[fmt.Sprintf("%s_used_space_pct", ds.ID)] = int64(float64(used) / float64(capacity) * 10000)
220
+ } else {
221
+ mx[fmt.Sprintf("%s_used_space_pct", ds.ID)] = 0
222
+ }
223
+
224
+ for _, v := range overallStatuses {
225
+ key := fmt.Sprintf("%s_overall.status.%s", ds.ID, v)
226
+ mx[key] = oldmetrix.Bool(ds.OverallStatus == v)
227
+ }
228
+}
229
+
230
+func writeDatastorePerfMetrics(mx map[string]int64, ds *rs.Datastore, metrics []performance.MetricSeries) {
231
+ for _, metric := range metrics {
232
+ if len(metric.Value) == 0 || metric.Value[0] == -1 {
233
+ continue
234
+ }
235
+ key := fmt.Sprintf("%s_%s", ds.ID, metric.Name)
236
+ mx[key] = metric.Value[0]
237
+ }
238
+}
239
+
240
+func (c *Collector) collectClusters(mx map[string]int64) {
241
+ if len(c.resources.Clusters) == 0 {
242
+ return
243
+ }
244
+
245
+ refreshed := c.refreshClusterProperties()
246
+
247
+ metrics := c.ScrapeClusters(c.resources.Clusters)
248
+ c.collectClustersMetrics(mx, metrics, refreshed)
249
+}
250
+
251
+func (c *Collector) refreshClusterProperties() map[string]bool {
252
+ refreshed := make(map[string]bool)
253
+
254
+ if c.clusterPropertyCollector == nil {
255
+ return refreshed
256
+ }
257
+
258
+ refs := make([]types.ManagedObjectReference, 0, len(c.resources.Clusters))
259
+ for _, cl := range c.resources.Clusters {
260
+ refs = append(refs, cl.Ref)
261
+ }
262
+
263
+ clusters, err := c.clusterPropertyCollector.ClustersByRef(refs, "name", "summary", "configurationEx", "overallStatus")
264
+ if err != nil {
265
+ c.Warningf("failed to refresh cluster properties: %v", err)
266
+ return refreshed
267
+ }
268
+
269
+ for _, raw := range clusters {
270
+ cl := c.resources.Clusters.Get(raw.Reference().Value)
271
+ if cl == nil {
272
+ continue
273
+ }
274
+ refreshed[cl.ID] = true
275
+ updateClusterFromProperties(cl, raw)
276
+ }
277
+
278
+ return refreshed
279
+}
280
+
281
+func updateClusterFromProperties(cl *rs.Cluster, raw mo.ClusterComputeResource) {
282
+ cl.OverallStatus = string(raw.OverallStatus)
283
+
284
+ var s *types.ComputeResourceSummary
285
+ if raw.Summary != nil {
286
+ s = raw.Summary.GetComputeResourceSummary()
287
+ }
288
+ if s != nil {
289
+ cl.TotalCpu = s.TotalCpu
290
+ cl.TotalMemory = s.TotalMemory
291
+ cl.NumCpuCores = s.NumCpuCores
292
+ cl.NumCpuThreads = s.NumCpuThreads
293
+ cl.EffectiveCpu = s.EffectiveCpu
294
+ cl.EffectiveMemory = s.EffectiveMemory
295
+ cl.NumHosts = s.NumHosts
296
+ cl.NumEffectiveHosts = s.NumEffectiveHosts
297
+ }
298
+
299
+ // Reset conditional fields to avoid stale values
300
+ cl.NumVmotions = 0
301
+ cl.CurrentBalance = 0
302
+ cl.TargetBalance = 0
303
+ cl.DrsScore = 0
304
+ cl.UsageCpuDemandMhz = 0
305
+ cl.UsageMemDemandMB = 0
306
+ cl.UsageCpuEntitledMhz = 0
307
+ cl.UsageMemEntitledMB = 0
308
+ cl.UsageCpuReservationMhz = 0
309
+ cl.UsageMemReservationMB = 0
310
+ cl.UsageTotalVmCount = 0
311
+ cl.UsagePoweredOffVmCount = 0
312
+ cl.DrsEnabled = false
313
+ cl.DrsMode = ""
314
+ cl.HaEnabled = false
315
+ cl.HaAdmCtrlEnabled = false
316
+
317
+ // Cluster-specific summary fields
318
+ if cs, ok := raw.Summary.(*types.ClusterComputeResourceSummary); ok {
319
+ cl.NumVmotions = cs.NumVmotions
320
+ cl.CurrentBalance = cs.CurrentBalance
321
+ cl.TargetBalance = cs.TargetBalance
322
+ cl.DrsScore = cs.DrsScore
323
+ if cs.UsageSummary != nil {
324
+ cl.UsageCpuDemandMhz = cs.UsageSummary.CpuDemandMhz
325
+ cl.UsageMemDemandMB = cs.UsageSummary.MemDemandMB
326
+ cl.UsageCpuEntitledMhz = cs.UsageSummary.CpuEntitledMhz
327
+ cl.UsageMemEntitledMB = cs.UsageSummary.MemEntitledMB
328
+ cl.UsageCpuReservationMhz = cs.UsageSummary.CpuReservationMhz
329
+ cl.UsageMemReservationMB = cs.UsageSummary.MemReservationMB
330
+ cl.UsageTotalVmCount = cs.UsageSummary.TotalVmCount
331
+ cl.UsagePoweredOffVmCount = cs.UsageSummary.PoweredOffVmCount
332
+ }
333
+ }
334
+
335
+ // DRS and HA config from configurationEx
336
+ if cfg, ok := raw.ConfigurationEx.(*types.ClusterConfigInfoEx); ok {
337
+ if cfg.DrsConfig.Enabled != nil {
338
+ cl.DrsEnabled = *cfg.DrsConfig.Enabled
339
+ }
340
+ cl.DrsMode = string(cfg.DrsConfig.DefaultVmBehavior)
341
+ if cfg.DasConfig.Enabled != nil {
342
+ cl.HaEnabled = *cfg.DasConfig.Enabled
343
+ }
344
+ if cfg.DasConfig.AdmissionControlEnabled != nil {
345
+ cl.HaAdmCtrlEnabled = *cfg.DasConfig.AdmissionControlEnabled
346
+ }
347
+ }
348
+}
349
+
350
+func (c *Collector) collectClustersMetrics(mx map[string]int64, metrics []performance.EntityMetric, refreshed map[string]bool) {
351
+ for id := range c.discoveredClusters {
352
+ c.discoveredClusters[id]++
353
+ }
354
+
355
+ for _, cl := range c.resources.Clusters {
356
+ if refreshed[cl.ID] {
357
+ c.discoveredClusters[cl.ID] = 0
358
+ }
359
+ writeClusterPropertyMetrics(mx, cl)
360
+ }
361
+
362
+ for _, metric := range metrics {
363
+ if cl := c.resources.Clusters.Get(metric.Entity.Value); cl != nil {
364
+ c.discoveredClusters[cl.ID] = 0
365
+ c.clusterPerfReceived[cl.ID] = true
366
+ writeClusterPerfMetrics(mx, cl, metric.Value)
367
+ }
368
+ }
369
+}
370
+
371
+func writeClusterPropertyMetrics(mx map[string]int64, cl *rs.Cluster) {
372
+ mx[fmt.Sprintf("%s_num_hosts", cl.ID)] = int64(cl.NumHosts)
373
+ mx[fmt.Sprintf("%s_num_effective_hosts", cl.ID)] = int64(cl.NumEffectiveHosts)
374
+ mx[fmt.Sprintf("%s_total_cpu", cl.ID)] = int64(cl.TotalCpu)
375
+ mx[fmt.Sprintf("%s_effective_cpu", cl.ID)] = int64(cl.EffectiveCpu)
376
+ mx[fmt.Sprintf("%s_total_memory", cl.ID)] = cl.TotalMemory
377
+ // EffectiveMemory is MB from API, convert to bytes for consistency with TotalMemory
378
+ mx[fmt.Sprintf("%s_effective_memory", cl.ID)] = cl.EffectiveMemory * 1024 * 1024
379
+ mx[fmt.Sprintf("%s_num_cpu_cores", cl.ID)] = int64(cl.NumCpuCores)
380
+ mx[fmt.Sprintf("%s_num_cpu_threads", cl.ID)] = int64(cl.NumCpuThreads)
381
+ mx[fmt.Sprintf("%s_num_vmotions", cl.ID)] = int64(cl.NumVmotions)
382
+ mx[fmt.Sprintf("%s_drs_score", cl.ID)] = int64(cl.DrsScore)
383
+ mx[fmt.Sprintf("%s_current_balance", cl.ID)] = int64(cl.CurrentBalance)
384
+ mx[fmt.Sprintf("%s_target_balance", cl.ID)] = int64(cl.TargetBalance)
385
+
386
+ mx[fmt.Sprintf("%s_drs_enabled", cl.ID)] = oldmetrix.Bool(cl.DrsEnabled)
387
+ mx[fmt.Sprintf("%s_ha_enabled", cl.ID)] = oldmetrix.Bool(cl.HaEnabled)
388
+ mx[fmt.Sprintf("%s_ha_adm_ctrl_enabled", cl.ID)] = oldmetrix.Bool(cl.HaAdmCtrlEnabled)
389
+
390
+ mx[fmt.Sprintf("%s_usage_cpu_demand_mhz", cl.ID)] = int64(cl.UsageCpuDemandMhz)
391
+ mx[fmt.Sprintf("%s_usage_mem_demand_mb", cl.ID)] = int64(cl.UsageMemDemandMB)
392
+ mx[fmt.Sprintf("%s_usage_cpu_entitled_mhz", cl.ID)] = int64(cl.UsageCpuEntitledMhz)
393
+ mx[fmt.Sprintf("%s_usage_mem_entitled_mb", cl.ID)] = int64(cl.UsageMemEntitledMB)
394
+ mx[fmt.Sprintf("%s_usage_cpu_reservation_mhz", cl.ID)] = int64(cl.UsageCpuReservationMhz)
395
+ mx[fmt.Sprintf("%s_usage_mem_reservation_mb", cl.ID)] = int64(cl.UsageMemReservationMB)
396
+ mx[fmt.Sprintf("%s_usage_total_vm_count", cl.ID)] = int64(cl.UsageTotalVmCount)
397
+ mx[fmt.Sprintf("%s_usage_powered_off_vm_count", cl.ID)] = int64(cl.UsagePoweredOffVmCount)
398
+
399
+ for _, v := range overallStatuses {
400
+ key := fmt.Sprintf("%s_overall.status.%s", cl.ID, v)
401
+ mx[key] = oldmetrix.Bool(cl.OverallStatus == v)
402
+ }
403
+}
404
+
405
+func writeClusterPerfMetrics(mx map[string]int64, cl *rs.Cluster, metrics []performance.MetricSeries) {
406
+ for _, metric := range metrics {
407
+ if len(metric.Value) == 0 || metric.Value[0] == -1 {
408
+ continue
409
+ }
410
+ key := fmt.Sprintf("%s_%s", cl.ID, metric.Name)
411
+ mx[key] = metric.Value[0]
412
+ }
413
+}
414
+
415
+func (c *Collector) collectResourcePools(mx map[string]int64) {
416
+ if len(c.resources.ResourcePools) == 0 {
417
+ return
418
+ }
419
+
420
+ refreshed := c.refreshResourcePoolProperties()
421
+
422
+ c.collectResourcePoolsMetrics(mx, refreshed)
423
+}
424
+
425
+func (c *Collector) refreshResourcePoolProperties() map[string]bool {
426
+ refreshed := make(map[string]bool)
427
+
428
+ if c.rpPropertyCollector == nil {
429
+ return refreshed
430
+ }
431
+
432
+ refs := make([]types.ManagedObjectReference, 0, len(c.resources.ResourcePools))
433
+ for _, rp := range c.resources.ResourcePools {
434
+ refs = append(refs, rp.Ref)
435
+ }
436
+
437
+ pools, err := c.rpPropertyCollector.ResourcePoolsByRef(refs, "name", "summary", "config", "runtime", "overallStatus")
438
+ if err != nil {
439
+ c.Warningf("failed to refresh resource pool properties: %v", err)
440
+ return refreshed
441
+ }
442
+
443
+ for _, raw := range pools {
444
+ rp := c.resources.ResourcePools.Get(raw.Reference().Value)
445
+ if rp == nil {
446
+ continue
447
+ }
448
+ refreshed[rp.ID] = true
449
+ updateResourcePoolFromProperties(rp, raw)
450
+ }
451
+
452
+ return refreshed
453
+}
454
+
455
+func updateResourcePoolFromProperties(rp *rs.ResourcePool, raw mo.ResourcePool) {
456
+ rp.OverallStatus = string(raw.OverallStatus)
457
+
458
+ // Reset conditional fields to avoid stale values
459
+ rp.OverallCpuUsage = 0
460
+ rp.OverallCpuDemand = 0
461
+ rp.GuestMemoryUsage = 0
462
+ rp.HostMemoryUsage = 0
463
+ rp.DistributedCpuEntitlement = 0
464
+ rp.DistributedMemoryEntitlement = 0
465
+ rp.PrivateMemory = 0
466
+ rp.SharedMemory = 0
467
+ rp.SwappedMemory = 0
468
+ rp.BalloonedMemory = 0
469
+ rp.OverheadMemory = 0
470
+ rp.ConsumedOverheadMemory = 0
471
+ rp.CompressedMemory = 0
472
+
473
+ var qs *types.ResourcePoolQuickStats
474
+ if raw.Summary != nil {
475
+ if s := raw.Summary.GetResourcePoolSummary(); s != nil {
476
+ qs = s.QuickStats
477
+ }
478
+ }
479
+ if qs != nil {
480
+ rp.OverallCpuUsage = qs.OverallCpuUsage
481
+ rp.OverallCpuDemand = qs.OverallCpuDemand
482
+ rp.GuestMemoryUsage = qs.GuestMemoryUsage
483
+ rp.HostMemoryUsage = qs.HostMemoryUsage
484
+ rp.DistributedCpuEntitlement = qs.DistributedCpuEntitlement
485
+ rp.DistributedMemoryEntitlement = qs.DistributedMemoryEntitlement
486
+ rp.PrivateMemory = qs.PrivateMemory
487
+ rp.SharedMemory = qs.SharedMemory
488
+ rp.SwappedMemory = qs.SwappedMemory
489
+ rp.BalloonedMemory = qs.BalloonedMemory
490
+ rp.OverheadMemory = qs.OverheadMemory
491
+ rp.ConsumedOverheadMemory = qs.ConsumedOverheadMemory
492
+ rp.CompressedMemory = qs.CompressedMemory
493
+ }
494
+
495
+ // Runtime resource usage (full "runtime" property requested)
496
+ rp.CpuReservationUsed = raw.Runtime.Cpu.ReservationUsed
497
+ rp.CpuMaxUsage = raw.Runtime.Cpu.MaxUsage
498
+ rp.CpuUnreservedForVm = raw.Runtime.Cpu.UnreservedForVm
499
+ rp.MemReservationUsed = raw.Runtime.Memory.ReservationUsed
500
+ rp.MemMaxUsage = raw.Runtime.Memory.MaxUsage
501
+ rp.MemUnreservedForVm = raw.Runtime.Memory.UnreservedForVm
502
+
503
+ // Config — reservation, limit
504
+ rp.CpuReservation = 0
505
+ rp.CpuLimit = -1
506
+ rp.MemReservation = 0
507
+ rp.MemLimit = -1
508
+ if raw.Config.CpuAllocation.Reservation != nil {
509
+ rp.CpuReservation = *raw.Config.CpuAllocation.Reservation
510
+ }
511
+ if raw.Config.CpuAllocation.Limit != nil {
512
+ rp.CpuLimit = *raw.Config.CpuAllocation.Limit
513
+ }
514
+ if raw.Config.MemoryAllocation.Reservation != nil {
515
+ rp.MemReservation = *raw.Config.MemoryAllocation.Reservation
516
+ }
517
+ if raw.Config.MemoryAllocation.Limit != nil {
518
+ rp.MemLimit = *raw.Config.MemoryAllocation.Limit
519
+ }
520
+
521
+ // CpuSharesLevel / MemSharesLevel are strings (low/normal/high/custom) — not exported as metrics
522
+}
523
+
524
+func (c *Collector) collectResourcePoolsMetrics(mx map[string]int64, refreshed map[string]bool) {
525
+ for id := range c.discoveredResourcePools {
526
+ c.discoveredResourcePools[id]++
527
+ }
528
+
529
+ for _, rp := range c.resources.ResourcePools {
530
+ if refreshed[rp.ID] {
531
+ c.discoveredResourcePools[rp.ID] = 0
532
+ }
533
+ writeResourcePoolMetrics(mx, rp)
534
+ }
535
+}
536
+
537
+func writeResourcePoolMetrics(mx map[string]int64, rp *rs.ResourcePool) {
538
+ mx[fmt.Sprintf("%s_cpu_usage", rp.ID)] = rp.OverallCpuUsage
539
+ mx[fmt.Sprintf("%s_cpu_demand", rp.ID)] = rp.OverallCpuDemand
540
+ mx[fmt.Sprintf("%s_cpu_entitlement_distributed", rp.ID)] = rp.DistributedCpuEntitlement
541
+ mx[fmt.Sprintf("%s_mem_usage_guest", rp.ID)] = rp.GuestMemoryUsage
542
+ mx[fmt.Sprintf("%s_mem_usage_host", rp.ID)] = rp.HostMemoryUsage
543
+ mx[fmt.Sprintf("%s_mem_entitlement_distributed", rp.ID)] = rp.DistributedMemoryEntitlement
544
+
545
+ mx[fmt.Sprintf("%s_mem_private", rp.ID)] = rp.PrivateMemory
546
+ mx[fmt.Sprintf("%s_mem_shared", rp.ID)] = rp.SharedMemory
547
+ mx[fmt.Sprintf("%s_mem_swapped", rp.ID)] = rp.SwappedMemory
548
+ mx[fmt.Sprintf("%s_mem_ballooned", rp.ID)] = rp.BalloonedMemory
549
+ mx[fmt.Sprintf("%s_mem_overhead", rp.ID)] = rp.OverheadMemory
550
+ mx[fmt.Sprintf("%s_mem_consumed_overhead", rp.ID)] = rp.ConsumedOverheadMemory
551
+ mx[fmt.Sprintf("%s_mem_compressed", rp.ID)] = rp.CompressedMemory
552
+
553
+ mx[fmt.Sprintf("%s_cpu_reservation_used", rp.ID)] = rp.CpuReservationUsed
554
+ mx[fmt.Sprintf("%s_cpu_max_usage", rp.ID)] = rp.CpuMaxUsage
555
+ mx[fmt.Sprintf("%s_cpu_unreserved_for_vm", rp.ID)] = rp.CpuUnreservedForVm
556
+ mx[fmt.Sprintf("%s_mem_reservation_used", rp.ID)] = rp.MemReservationUsed
557
+ mx[fmt.Sprintf("%s_mem_max_usage", rp.ID)] = rp.MemMaxUsage
558
+ mx[fmt.Sprintf("%s_mem_unreserved_for_vm", rp.ID)] = rp.MemUnreservedForVm
559
+
560
+ mx[fmt.Sprintf("%s_cpu_reservation", rp.ID)] = rp.CpuReservation
561
+ mx[fmt.Sprintf("%s_cpu_limit", rp.ID)] = rp.CpuLimit
562
+ mx[fmt.Sprintf("%s_mem_reservation", rp.ID)] = rp.MemReservation
563
+ mx[fmt.Sprintf("%s_mem_limit", rp.ID)] = rp.MemLimit
564
+
565
+ for _, v := range overallStatuses {
566
+ key := fmt.Sprintf("%s_overall.status.%s", rp.ID, v)
567
+ mx[key] = oldmetrix.Bool(rp.OverallStatus == v)
568
+ }
569
+}
src/go/plugin/go.d/collector/vsphere/collector.go
+51
-15
@@ -10,6 +10,8 @@ import (
10
"time"
11
12
"github.com/vmware/govmomi/performance"
13
+ mo25 "github.com/vmware/govmomi/vim25/mo"
14
+ "github.com/vmware/govmomi/vim25/types"
15
16
"github.com/netdata/netdata/go/plugins/pkg/confopt"
17
"github.com/netdata/netdata/go/plugins/pkg/web"
@@ -43,12 +45,21 @@ func New() *Collector {
45
DiscoveryInterval: confopt.Duration(time.Minute * 5),
46
HostsInclude: []string{"/*"},
47
VMsInclude: []string{"/*"},
48
+ DatastoresInclude: []string{"/*"},
49
+ ClustersInclude: []string{"/*"},
50
},
47
- collectionLock: &sync.RWMutex{},
48
- charts: &collectorapi.Charts{},
49
- discoveredHosts: make(map[string]int),
50
- discoveredVMs: make(map[string]int),
51
- charted: make(map[string]bool),
51
+ collectionLock: &sync.RWMutex{},
52
+ charts: &collectorapi.Charts{},
53
+ discoveredHosts: make(map[string]int),
54
+ discoveredVMs: make(map[string]int),
55
+ discoveredDatastores: make(map[string]int),
56
+ discoveredClusters: make(map[string]int),
57
+ discoveredResourcePools: make(map[string]int),
58
+ charted: make(map[string]bool),
59
+ datastorePerfReceived: make(map[string]bool),
60
+ datastorePerfCharted: make(map[string]bool),
61
+ clusterPerfReceived: make(map[string]bool),
62
+ clusterPerfCharted: make(map[string]bool),
63
}
64
}
65
@@ -57,9 +68,11 @@ type Config struct {
68
UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
69
AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
70
web.HTTPConfig `yaml:",inline" json:""`
60
- DiscoveryInterval confopt.Duration `yaml:"discovery_interval,omitempty" json:"discovery_interval"`
61
- HostsInclude match.HostIncludes `yaml:"host_include,omitempty" json:"host_include"`
62
- VMsInclude match.VMIncludes `yaml:"vm_include,omitempty" json:"vm_include"`
71
+ DiscoveryInterval confopt.Duration `yaml:"discovery_interval,omitempty" json:"discovery_interval"`
72
+ HostsInclude match.HostIncludes `yaml:"host_include,omitempty" json:"host_include"`
73
+ VMsInclude match.VMIncludes `yaml:"vm_include,omitempty" json:"vm_include"`
74
+ DatastoresInclude match.DatastoreIncludes `yaml:"datastore_include,omitempty" json:"datastore_include"`
75
+ ClustersInclude match.ClusterIncludes `yaml:"cluster_include,omitempty" json:"cluster_include"`
76
}
77
78
type (
@@ -71,13 +84,25 @@ type (
84
85
discoverer
86
scraper
74
-
75
- collectionLock *sync.RWMutex
76
- resources *rs.Resources
77
- discoveryTask *task
78
- discoveredHosts map[string]int
79
- discoveredVMs map[string]int
80
- charted map[string]bool
87
+ dsPropertyCollector
88
+ clusterPropertyCollector
89
+ rpPropertyCollector
90
+
91
+ collectionLock *sync.RWMutex
92
+ resources *rs.Resources
93
+ discoveryTask *task
94
+ discoveredHosts map[string]int
95
+ discoveredVMs map[string]int
96
+ discoveredDatastores map[string]int
97
+ discoveredClusters map[string]int
98
+ discoveredResourcePools map[string]int
99
+ charted map[string]bool
100
+
101
+ // two-phase chart creation: property charts always, perf charts only when data arrives
102
+ datastorePerfReceived map[string]bool
103
+ datastorePerfCharted map[string]bool
104
+ clusterPerfReceived map[string]bool
105
+ clusterPerfCharted map[string]bool
106
}
107
discoverer interface {
108
Discover() (*rs.Resources, error)
@@ -85,6 +110,17 @@ type (
110
scraper interface {
111
ScrapeHosts(rs.Hosts) []performance.EntityMetric
112
ScrapeVMs(rs.VMs) []performance.EntityMetric
113
+ ScrapeDatastores(rs.Datastores) []performance.EntityMetric
114
+ ScrapeClusters(rs.Clusters) []performance.EntityMetric
115
+ }
116
+ dsPropertyCollector interface {
117
+ DatastoresByRef(refs []types.ManagedObjectReference, pathSet ...string) ([]mo25.Datastore, error)
118
+ }
119
+ clusterPropertyCollector interface {
120
+ ClustersByRef(refs []types.ManagedObjectReference, pathSet ...string) ([]mo25.ClusterComputeResource, error)
121
+ }
122
+ rpPropertyCollector interface {
123
+ ResourcePoolsByRef(refs []types.ManagedObjectReference, pathSet ...string) ([]mo25.ResourcePool, error)
124
}
125
)
126
src/go/plugin/go.d/collector/vsphere/collector_test.go
+619
-199
@@ -102,6 +102,16 @@ func TestCollector_Init_ReturnsFalseIfInvalidHostVMIncludeFormat(t *testing.T) {
102
103
collr.VMsInclude = match.VMIncludes{"invalid"}
104
assert.Error(t, collr.Init(context.Background()))
105
+
106
+ collr.VMsInclude = collr.VMsInclude[:0]
107
+
108
+ collr.DatastoresInclude = match.DatastoreIncludes{"invalid"}
109
+ assert.Error(t, collr.Init(context.Background()))
110
+
111
+ collr.DatastoresInclude = collr.DatastoresInclude[:0]
112
+
113
+ collr.ClustersInclude = match.ClusterIncludes{"invalid"}
114
+ assert.Error(t, collr.Init(context.Background()))
115
}
116
117
func TestCollector_Check(t *testing.T) {
@@ -137,198 +147,304 @@ func TestCollector_Collect(t *testing.T) {
147
collr.scraper = mockScraper{collr.scraper}
148
149
expected := map[string]int64{
140
- "host-21_cpu.usage.average": 100,
141
- "host-21_disk.maxTotalLatency.latest": 100,
142
- "host-21_disk.read.average": 100,
143
- "host-21_disk.write.average": 100,
144
- "host-21_mem.active.average": 100,
145
- "host-21_mem.consumed.average": 100,
146
- "host-21_mem.granted.average": 100,
147
- "host-21_mem.shared.average": 100,
148
- "host-21_mem.sharedcommon.average": 100,
149
- "host-21_mem.swapinRate.average": 100,
150
- "host-21_mem.swapoutRate.average": 100,
151
- "host-21_mem.usage.average": 100,
152
- "host-21_net.bytesRx.average": 100,
153
- "host-21_net.bytesTx.average": 100,
154
- "host-21_net.droppedRx.summation": 100,
155
- "host-21_net.droppedTx.summation": 100,
156
- "host-21_net.errorsRx.summation": 100,
157
- "host-21_net.errorsTx.summation": 100,
158
- "host-21_net.packetsRx.summation": 100,
159
- "host-21_net.packetsTx.summation": 100,
160
- "host-21_overall.status.gray": 1,
161
- "host-21_overall.status.green": 0,
162
- "host-21_overall.status.red": 0,
163
- "host-21_overall.status.yellow": 0,
164
- "host-21_sys.uptime.latest": 100,
165
- "host-37_cpu.usage.average": 100,
166
- "host-37_disk.maxTotalLatency.latest": 100,
167
- "host-37_disk.read.average": 100,
168
- "host-37_disk.write.average": 100,
169
- "host-37_mem.active.average": 100,
170
- "host-37_mem.consumed.average": 100,
171
- "host-37_mem.granted.average": 100,
172
- "host-37_mem.shared.average": 100,
173
- "host-37_mem.sharedcommon.average": 100,
174
- "host-37_mem.swapinRate.average": 100,
175
- "host-37_mem.swapoutRate.average": 100,
176
- "host-37_mem.usage.average": 100,
177
- "host-37_net.bytesRx.average": 100,
178
- "host-37_net.bytesTx.average": 100,
179
- "host-37_net.droppedRx.summation": 100,
180
- "host-37_net.droppedTx.summation": 100,
181
- "host-37_net.errorsRx.summation": 100,
182
- "host-37_net.errorsTx.summation": 100,
183
- "host-37_net.packetsRx.summation": 100,
184
- "host-37_net.packetsTx.summation": 100,
185
- "host-37_overall.status.gray": 1,
186
- "host-37_overall.status.green": 0,
187
- "host-37_overall.status.red": 0,
188
- "host-37_overall.status.yellow": 0,
189
- "host-37_sys.uptime.latest": 100,
190
- "host-47_cpu.usage.average": 100,
191
- "host-47_disk.maxTotalLatency.latest": 100,
192
- "host-47_disk.read.average": 100,
193
- "host-47_disk.write.average": 100,
194
- "host-47_mem.active.average": 100,
195
- "host-47_mem.consumed.average": 100,
196
- "host-47_mem.granted.average": 100,
197
- "host-47_mem.shared.average": 100,
198
- "host-47_mem.sharedcommon.average": 100,
199
- "host-47_mem.swapinRate.average": 100,
200
- "host-47_mem.swapoutRate.average": 100,
201
- "host-47_mem.usage.average": 100,
202
- "host-47_net.bytesRx.average": 100,
203
- "host-47_net.bytesTx.average": 100,
204
- "host-47_net.droppedRx.summation": 100,
205
- "host-47_net.droppedTx.summation": 100,
206
- "host-47_net.errorsRx.summation": 100,
207
- "host-47_net.errorsTx.summation": 100,
208
- "host-47_net.packetsRx.summation": 100,
209
- "host-47_net.packetsTx.summation": 100,
210
- "host-47_overall.status.gray": 1,
211
- "host-47_overall.status.green": 0,
212
- "host-47_overall.status.red": 0,
213
- "host-47_overall.status.yellow": 0,
214
- "host-47_sys.uptime.latest": 100,
215
- "host-57_cpu.usage.average": 100,
216
- "host-57_disk.maxTotalLatency.latest": 100,
217
- "host-57_disk.read.average": 100,
218
- "host-57_disk.write.average": 100,
219
- "host-57_mem.active.average": 100,
220
- "host-57_mem.consumed.average": 100,
221
- "host-57_mem.granted.average": 100,
222
- "host-57_mem.shared.average": 100,
223
- "host-57_mem.sharedcommon.average": 100,
224
- "host-57_mem.swapinRate.average": 100,
225
- "host-57_mem.swapoutRate.average": 100,
226
- "host-57_mem.usage.average": 100,
227
- "host-57_net.bytesRx.average": 100,
228
- "host-57_net.bytesTx.average": 100,
229
- "host-57_net.droppedRx.summation": 100,
230
- "host-57_net.droppedTx.summation": 100,
231
- "host-57_net.errorsRx.summation": 100,
232
- "host-57_net.errorsTx.summation": 100,
233
- "host-57_net.packetsRx.summation": 100,
234
- "host-57_net.packetsTx.summation": 100,
235
- "host-57_overall.status.gray": 1,
236
- "host-57_overall.status.green": 0,
237
- "host-57_overall.status.red": 0,
238
- "host-57_overall.status.yellow": 0,
239
- "host-57_sys.uptime.latest": 100,
240
- "vm-62_cpu.usage.average": 200,
241
- "vm-62_disk.maxTotalLatency.latest": 200,
242
- "vm-62_disk.read.average": 200,
243
- "vm-62_disk.write.average": 200,
244
- "vm-62_mem.active.average": 200,
245
- "vm-62_mem.consumed.average": 200,
246
- "vm-62_mem.granted.average": 200,
247
- "vm-62_mem.shared.average": 200,
248
- "vm-62_mem.swapinRate.average": 200,
249
- "vm-62_mem.swapoutRate.average": 200,
250
- "vm-62_mem.swapped.average": 200,
251
- "vm-62_mem.usage.average": 200,
252
- "vm-62_net.bytesRx.average": 200,
253
- "vm-62_net.bytesTx.average": 200,
254
- "vm-62_net.droppedRx.summation": 200,
255
- "vm-62_net.droppedTx.summation": 200,
256
- "vm-62_net.packetsRx.summation": 200,
257
- "vm-62_net.packetsTx.summation": 200,
258
- "vm-62_overall.status.gray": 0,
259
- "vm-62_overall.status.green": 1,
260
- "vm-62_overall.status.red": 0,
261
- "vm-62_overall.status.yellow": 0,
262
- "vm-62_sys.uptime.latest": 200,
263
- "vm-65_cpu.usage.average": 200,
264
- "vm-65_disk.maxTotalLatency.latest": 200,
265
- "vm-65_disk.read.average": 200,
266
- "vm-65_disk.write.average": 200,
267
- "vm-65_mem.active.average": 200,
268
- "vm-65_mem.consumed.average": 200,
269
- "vm-65_mem.granted.average": 200,
270
- "vm-65_mem.shared.average": 200,
271
- "vm-65_mem.swapinRate.average": 200,
272
- "vm-65_mem.swapoutRate.average": 200,
273
- "vm-65_mem.swapped.average": 200,
274
- "vm-65_mem.usage.average": 200,
275
- "vm-65_net.bytesRx.average": 200,
276
- "vm-65_net.bytesTx.average": 200,
277
- "vm-65_net.droppedRx.summation": 200,
278
- "vm-65_net.droppedTx.summation": 200,
279
- "vm-65_net.packetsRx.summation": 200,
280
- "vm-65_net.packetsTx.summation": 200,
281
- "vm-65_overall.status.gray": 0,
282
- "vm-65_overall.status.green": 1,
283
- "vm-65_overall.status.red": 0,
284
- "vm-65_overall.status.yellow": 0,
285
- "vm-65_sys.uptime.latest": 200,
286
- "vm-68_cpu.usage.average": 200,
287
- "vm-68_disk.maxTotalLatency.latest": 200,
288
- "vm-68_disk.read.average": 200,
289
- "vm-68_disk.write.average": 200,
290
- "vm-68_mem.active.average": 200,
291
- "vm-68_mem.consumed.average": 200,
292
- "vm-68_mem.granted.average": 200,
293
- "vm-68_mem.shared.average": 200,
294
- "vm-68_mem.swapinRate.average": 200,
295
- "vm-68_mem.swapoutRate.average": 200,
296
- "vm-68_mem.swapped.average": 200,
297
- "vm-68_mem.usage.average": 200,
298
- "vm-68_net.bytesRx.average": 200,
299
- "vm-68_net.bytesTx.average": 200,
300
- "vm-68_net.droppedRx.summation": 200,
301
- "vm-68_net.droppedTx.summation": 200,
302
- "vm-68_net.packetsRx.summation": 200,
303
- "vm-68_net.packetsTx.summation": 200,
304
- "vm-68_overall.status.gray": 0,
305
- "vm-68_overall.status.green": 1,
306
- "vm-68_overall.status.red": 0,
307
- "vm-68_overall.status.yellow": 0,
308
- "vm-68_sys.uptime.latest": 200,
309
- "vm-71_cpu.usage.average": 200,
310
- "vm-71_disk.maxTotalLatency.latest": 200,
311
- "vm-71_disk.read.average": 200,
312
- "vm-71_disk.write.average": 200,
313
- "vm-71_mem.active.average": 200,
314
- "vm-71_mem.consumed.average": 200,
315
- "vm-71_mem.granted.average": 200,
316
- "vm-71_mem.shared.average": 200,
317
- "vm-71_mem.swapinRate.average": 200,
318
- "vm-71_mem.swapoutRate.average": 200,
319
- "vm-71_mem.swapped.average": 200,
320
- "vm-71_mem.usage.average": 200,
321
- "vm-71_net.bytesRx.average": 200,
322
- "vm-71_net.bytesTx.average": 200,
323
- "vm-71_net.droppedRx.summation": 200,
324
- "vm-71_net.droppedTx.summation": 200,
325
- "vm-71_net.packetsRx.summation": 200,
326
- "vm-71_net.packetsTx.summation": 200,
327
- "vm-71_overall.status.gray": 0,
328
- "vm-71_overall.status.green": 1,
329
- "vm-71_overall.status.red": 0,
330
- "vm-71_overall.status.yellow": 0,
331
- "vm-71_sys.uptime.latest": 200,
150
+ "host-21_cpu.usage.average": 100,
151
+ "host-21_disk.maxTotalLatency.latest": 100,
152
+ "host-21_disk.read.average": 100,
153
+ "host-21_disk.write.average": 100,
154
+ "host-21_mem.active.average": 100,
155
+ "host-21_mem.consumed.average": 100,
156
+ "host-21_mem.granted.average": 100,
157
+ "host-21_mem.shared.average": 100,
158
+ "host-21_mem.sharedcommon.average": 100,
159
+ "host-21_mem.swapinRate.average": 100,
160
+ "host-21_mem.swapoutRate.average": 100,
161
+ "host-21_mem.usage.average": 100,
162
+ "host-21_net.bytesRx.average": 100,
163
+ "host-21_net.bytesTx.average": 100,
164
+ "host-21_net.droppedRx.summation": 100,
165
+ "host-21_net.droppedTx.summation": 100,
166
+ "host-21_net.errorsRx.summation": 100,
167
+ "host-21_net.errorsTx.summation": 100,
168
+ "host-21_net.packetsRx.summation": 100,
169
+ "host-21_net.packetsTx.summation": 100,
170
+ "host-21_overall.status.gray": 1,
171
+ "host-21_overall.status.green": 0,
172
+ "host-21_overall.status.red": 0,
173
+ "host-21_overall.status.yellow": 0,
174
+ "host-21_sys.uptime.latest": 100,
175
+ "host-37_cpu.usage.average": 100,
176
+ "host-37_disk.maxTotalLatency.latest": 100,
177
+ "host-37_disk.read.average": 100,
178
+ "host-37_disk.write.average": 100,
179
+ "host-37_mem.active.average": 100,
180
+ "host-37_mem.consumed.average": 100,
181
+ "host-37_mem.granted.average": 100,
182
+ "host-37_mem.shared.average": 100,
183
+ "host-37_mem.sharedcommon.average": 100,
184
+ "host-37_mem.swapinRate.average": 100,
185
+ "host-37_mem.swapoutRate.average": 100,
186
+ "host-37_mem.usage.average": 100,
187
+ "host-37_net.bytesRx.average": 100,
188
+ "host-37_net.bytesTx.average": 100,
189
+ "host-37_net.droppedRx.summation": 100,
190
+ "host-37_net.droppedTx.summation": 100,
191
+ "host-37_net.errorsRx.summation": 100,
192
+ "host-37_net.errorsTx.summation": 100,
193
+ "host-37_net.packetsRx.summation": 100,
194
+ "host-37_net.packetsTx.summation": 100,
195
+ "host-37_overall.status.gray": 1,
196
+ "host-37_overall.status.green": 0,
197
+ "host-37_overall.status.red": 0,
198
+ "host-37_overall.status.yellow": 0,
199
+ "host-37_sys.uptime.latest": 100,
200
+ "host-47_cpu.usage.average": 100,
201
+ "host-47_disk.maxTotalLatency.latest": 100,
202
+ "host-47_disk.read.average": 100,
203
+ "host-47_disk.write.average": 100,
204
+ "host-47_mem.active.average": 100,
205
+ "host-47_mem.consumed.average": 100,
206
+ "host-47_mem.granted.average": 100,
207
+ "host-47_mem.shared.average": 100,
208
+ "host-47_mem.sharedcommon.average": 100,
209
+ "host-47_mem.swapinRate.average": 100,
210
+ "host-47_mem.swapoutRate.average": 100,
211
+ "host-47_mem.usage.average": 100,
212
+ "host-47_net.bytesRx.average": 100,
213
+ "host-47_net.bytesTx.average": 100,
214
+ "host-47_net.droppedRx.summation": 100,
215
+ "host-47_net.droppedTx.summation": 100,
216
+ "host-47_net.errorsRx.summation": 100,
217
+ "host-47_net.errorsTx.summation": 100,
218
+ "host-47_net.packetsRx.summation": 100,
219
+ "host-47_net.packetsTx.summation": 100,
220
+ "host-47_overall.status.gray": 1,
221
+ "host-47_overall.status.green": 0,
222
+ "host-47_overall.status.red": 0,
223
+ "host-47_overall.status.yellow": 0,
224
+ "host-47_sys.uptime.latest": 100,
225
+ "host-57_cpu.usage.average": 100,
226
+ "host-57_disk.maxTotalLatency.latest": 100,
227
+ "host-57_disk.read.average": 100,
228
+ "host-57_disk.write.average": 100,
229
+ "host-57_mem.active.average": 100,
230
+ "host-57_mem.consumed.average": 100,
231
+ "host-57_mem.granted.average": 100,
232
+ "host-57_mem.shared.average": 100,
233
+ "host-57_mem.sharedcommon.average": 100,
234
+ "host-57_mem.swapinRate.average": 100,
235
+ "host-57_mem.swapoutRate.average": 100,
236
+ "host-57_mem.usage.average": 100,
237
+ "host-57_net.bytesRx.average": 100,
238
+ "host-57_net.bytesTx.average": 100,
239
+ "host-57_net.droppedRx.summation": 100,
240
+ "host-57_net.droppedTx.summation": 100,
241
+ "host-57_net.errorsRx.summation": 100,
242
+ "host-57_net.errorsTx.summation": 100,
243
+ "host-57_net.packetsRx.summation": 100,
244
+ "host-57_net.packetsTx.summation": 100,
245
+ "host-57_overall.status.gray": 1,
246
+ "host-57_overall.status.green": 0,
247
+ "host-57_overall.status.red": 0,
248
+ "host-57_overall.status.yellow": 0,
249
+ "host-57_sys.uptime.latest": 100,
250
+ "vm-62_cpu.usage.average": 200,
251
+ "vm-62_disk.maxTotalLatency.latest": 200,
252
+ "vm-62_disk.read.average": 200,
253
+ "vm-62_disk.write.average": 200,
254
+ "vm-62_mem.active.average": 200,
255
+ "vm-62_mem.consumed.average": 200,
256
+ "vm-62_mem.granted.average": 200,
257
+ "vm-62_mem.shared.average": 200,
258
+ "vm-62_mem.swapinRate.average": 200,
259
+ "vm-62_mem.swapoutRate.average": 200,
260
+ "vm-62_mem.swapped.average": 200,
261
+ "vm-62_mem.usage.average": 200,
262
+ "vm-62_net.bytesRx.average": 200,
263
+ "vm-62_net.bytesTx.average": 200,
264
+ "vm-62_net.droppedRx.summation": 200,
265
+ "vm-62_net.droppedTx.summation": 200,
266
+ "vm-62_net.packetsRx.summation": 200,
267
+ "vm-62_net.packetsTx.summation": 200,
268
+ "vm-62_overall.status.gray": 0,
269
+ "vm-62_overall.status.green": 1,
270
+ "vm-62_overall.status.red": 0,
271
+ "vm-62_overall.status.yellow": 0,
272
+ "vm-62_sys.uptime.latest": 200,
273
+ "vm-65_cpu.usage.average": 200,
274
+ "vm-65_disk.maxTotalLatency.latest": 200,
275
+ "vm-65_disk.read.average": 200,
276
+ "vm-65_disk.write.average": 200,
277
+ "vm-65_mem.active.average": 200,
278
+ "vm-65_mem.consumed.average": 200,
279
+ "vm-65_mem.granted.average": 200,
280
+ "vm-65_mem.shared.average": 200,
281
+ "vm-65_mem.swapinRate.average": 200,
282
+ "vm-65_mem.swapoutRate.average": 200,
283
+ "vm-65_mem.swapped.average": 200,
284
+ "vm-65_mem.usage.average": 200,
285
+ "vm-65_net.bytesRx.average": 200,
286
+ "vm-65_net.bytesTx.average": 200,
287
+ "vm-65_net.droppedRx.summation": 200,
288
+ "vm-65_net.droppedTx.summation": 200,
289
+ "vm-65_net.packetsRx.summation": 200,
290
+ "vm-65_net.packetsTx.summation": 200,
291
+ "vm-65_overall.status.gray": 0,
292
+ "vm-65_overall.status.green": 1,
293
+ "vm-65_overall.status.red": 0,
294
+ "vm-65_overall.status.yellow": 0,
295
+ "vm-65_sys.uptime.latest": 200,
296
+ "vm-68_cpu.usage.average": 200,
297
+ "vm-68_disk.maxTotalLatency.latest": 200,
298
+ "vm-68_disk.read.average": 200,
299
+ "vm-68_disk.write.average": 200,
300
+ "vm-68_mem.active.average": 200,
301
+ "vm-68_mem.consumed.average": 200,
302
+ "vm-68_mem.granted.average": 200,
303
+ "vm-68_mem.shared.average": 200,
304
+ "vm-68_mem.swapinRate.average": 200,
305
+ "vm-68_mem.swapoutRate.average": 200,
306
+ "vm-68_mem.swapped.average": 200,
307
+ "vm-68_mem.usage.average": 200,
308
+ "vm-68_net.bytesRx.average": 200,
309
+ "vm-68_net.bytesTx.average": 200,
310
+ "vm-68_net.droppedRx.summation": 200,
311
+ "vm-68_net.droppedTx.summation": 200,
312
+ "vm-68_net.packetsRx.summation": 200,
313
+ "vm-68_net.packetsTx.summation": 200,
314
+ "vm-68_overall.status.gray": 0,
315
+ "vm-68_overall.status.green": 1,
316
+ "vm-68_overall.status.red": 0,
317
+ "vm-68_overall.status.yellow": 0,
318
+ "vm-68_sys.uptime.latest": 200,
319
+ "vm-71_cpu.usage.average": 200,
320
+ "vm-71_disk.maxTotalLatency.latest": 200,
321
+ "vm-71_disk.read.average": 200,
322
+ "vm-71_disk.write.average": 200,
323
+ "vm-71_mem.active.average": 200,
324
+ "vm-71_mem.consumed.average": 200,
325
+ "vm-71_mem.granted.average": 200,
326
+ "vm-71_mem.shared.average": 200,
327
+ "vm-71_mem.swapinRate.average": 200,
328
+ "vm-71_mem.swapoutRate.average": 200,
329
+ "vm-71_mem.swapped.average": 200,
330
+ "vm-71_mem.usage.average": 200,
331
+ "vm-71_net.bytesRx.average": 200,
332
+ "vm-71_net.bytesTx.average": 200,
333
+ "vm-71_net.droppedRx.summation": 200,
334
+ "vm-71_net.droppedTx.summation": 200,
335
+ "vm-71_net.packetsRx.summation": 200,
336
+ "vm-71_net.packetsTx.summation": 200,
337
+ "vm-71_overall.status.gray": 0,
338
+ "vm-71_overall.status.green": 1,
339
+ "vm-71_overall.status.red": 0,
340
+ "vm-71_overall.status.yellow": 0,
341
+ "vm-71_sys.uptime.latest": 200,
342
+ "datastore-59_capacity": 4398046511104,
343
+ "datastore-59_free_space": 4355096838144,
344
+ "datastore-59_used_space": 42949672960,
345
+ "datastore-59_used_space_pct": 97,
346
+ "datastore-59_overall.status.green": 1,
347
+ "datastore-59_overall.status.gray": 0,
348
+ "datastore-59_overall.status.red": 0,
349
+ "datastore-59_overall.status.yellow": 0,
350
+ "datastore-59_datastore.numberReadAveraged.average": 300,
351
+ "datastore-59_datastore.numberWriteAveraged.average": 300,
352
+ "datastore-59_datastore.read.average": 300,
353
+ "datastore-59_datastore.write.average": 300,
354
+ "datastore-59_datastore.totalReadLatency.average": 300,
355
+ "datastore-59_datastore.totalWriteLatency.average": 300,
356
+ // Cluster property metrics (domain-c28)
357
+ "domain-c28_num_hosts": 3,
358
+ "domain-c28_num_effective_hosts": 3,
359
+ "domain-c28_total_cpu": 6882,
360
+ "domain-c28_effective_cpu": 6882,
361
+ "domain-c28_total_memory": 12883292160,
362
+ "domain-c28_effective_memory": 13509110959964160,
363
+ "domain-c28_num_cpu_cores": 6,
364
+ "domain-c28_num_cpu_threads": 6,
365
+ "domain-c28_num_vmotions": 0,
366
+ "domain-c28_drs_score": 0,
367
+ "domain-c28_current_balance": 0,
368
+ "domain-c28_target_balance": 0,
369
+ "domain-c28_drs_enabled": 1,
370
+ "domain-c28_ha_enabled": 0,
371
+ "domain-c28_ha_adm_ctrl_enabled": 0,
372
+ "domain-c28_usage_cpu_demand_mhz": 0,
373
+ "domain-c28_usage_mem_demand_mb": 0,
374
+ "domain-c28_usage_cpu_entitled_mhz": 0,
375
+ "domain-c28_usage_mem_entitled_mb": 0,
376
+ "domain-c28_usage_cpu_reservation_mhz": 0,
377
+ "domain-c28_usage_mem_reservation_mb": 0,
378
+ "domain-c28_usage_total_vm_count": 0,
379
+ "domain-c28_usage_powered_off_vm_count": 0,
380
+ "domain-c28_overall.status.green": 1,
381
+ "domain-c28_overall.status.gray": 0,
382
+ "domain-c28_overall.status.red": 0,
383
+ "domain-c28_overall.status.yellow": 0,
384
+ // Cluster perf metrics (domain-c28)
385
+ "domain-c28_clusterServices.cpufairness.latest": 400,
386
+ "domain-c28_clusterServices.effectivecpu.average": 400,
387
+ "domain-c28_clusterServices.effectivemem.average": 400,
388
+ "domain-c28_clusterServices.failover.latest": 400,
389
+ "domain-c28_clusterServices.memfairness.latest": 400,
390
+ "domain-c28_cpu.totalmhz.average": 400,
391
+ "domain-c28_cpu.usage.average": 400,
392
+ "domain-c28_cpu.usagemhz.average": 400,
393
+ "domain-c28_mem.active.average": 400,
394
+ "domain-c28_mem.consumed.average": 400,
395
+ "domain-c28_mem.granted.average": 400,
396
+ "domain-c28_mem.overhead.average": 400,
397
+ "domain-c28_mem.shared.average": 400,
398
+ "domain-c28_mem.swapused.average": 400,
399
+ "domain-c28_mem.usage.average": 400,
400
+ "domain-c28_vmop.numChangeDS.latest": 400,
401
+ "domain-c28_vmop.numChangeHost.latest": 400,
402
+ "domain-c28_vmop.numChangeHostDS.latest": 400,
403
+ "domain-c28_vmop.numClone.latest": 400,
404
+ "domain-c28_vmop.numCreate.latest": 400,
405
+ "domain-c28_vmop.numDeploy.latest": 400,
406
+ "domain-c28_vmop.numDestroy.latest": 400,
407
+ "domain-c28_vmop.numPoweroff.latest": 400,
408
+ "domain-c28_vmop.numPoweron.latest": 400,
409
+ "domain-c28_vmop.numRebootGuest.latest": 400,
410
+ "domain-c28_vmop.numReconfigure.latest": 400,
411
+ "domain-c28_vmop.numRegister.latest": 400,
412
+ "domain-c28_vmop.numReset.latest": 400,
413
+ "domain-c28_vmop.numSVMotion.latest": 400,
414
+ "domain-c28_vmop.numShutdownGuest.latest": 400,
415
+ "domain-c28_vmop.numStandbyGuest.latest": 400,
416
+ "domain-c28_vmop.numSuspend.latest": 400,
417
+ "domain-c28_vmop.numUnregister.latest": 400,
418
+ "domain-c28_vmop.numVMotion.latest": 400,
419
+ "domain-c28_vmop.numXVMotion.latest": 400,
420
+ // Resource pool metrics (resgroup-27)
421
+ "resgroup-27_cpu_usage": 0,
422
+ "resgroup-27_cpu_demand": 0,
423
+ "resgroup-27_cpu_entitlement_distributed": 0,
424
+ "resgroup-27_mem_usage_guest": 0,
425
+ "resgroup-27_mem_usage_host": 0,
426
+ "resgroup-27_mem_entitlement_distributed": 0,
427
+ "resgroup-27_mem_private": 0,
428
+ "resgroup-27_mem_shared": 0,
429
+ "resgroup-27_mem_swapped": 0,
430
+ "resgroup-27_mem_ballooned": 0,
431
+ "resgroup-27_mem_overhead": 0,
432
+ "resgroup-27_mem_consumed_overhead": 0,
433
+ "resgroup-27_mem_compressed": 0,
434
+ "resgroup-27_cpu_reservation_used": 0,
435
+ "resgroup-27_cpu_max_usage": 4121,
436
+ "resgroup-27_cpu_unreserved_for_vm": 4121,
437
+ "resgroup-27_mem_reservation_used": 0,
438
+ "resgroup-27_mem_max_usage": 1007681536,
439
+ "resgroup-27_mem_unreserved_for_vm": 1007681536,
440
+ "resgroup-27_cpu_reservation": 4121,
441
+ "resgroup-27_cpu_limit": 4121,
442
+ "resgroup-27_mem_reservation": 961,
443
+ "resgroup-27_mem_limit": 961,
444
+ "resgroup-27_overall.status.green": 1,
445
+ "resgroup-27_overall.status.gray": 0,
446
+ "resgroup-27_overall.status.red": 0,
447
+ "resgroup-27_overall.status.yellow": 0,
448
}
449
450
mx := collr.Collect(context.Background())
@@ -338,9 +454,18 @@ func TestCollector_Collect(t *testing.T) {
454
count := model.Count()
455
assert.Len(t, collr.discoveredHosts, count.Host)
456
assert.Len(t, collr.discoveredVMs, count.Machine)
341
- assert.Len(t, collr.charted, count.Host+count.Machine)
342
-
343
- assert.Len(t, *collr.Charts(), count.Host*len(hostChartsTmpl)+count.Machine*len(vmChartsTmpl))
457
+ assert.Len(t, collr.discoveredDatastores, count.Datastore)
458
+
459
+ numClusters := len(collr.discoveredClusters)
460
+ numResourcePools := len(collr.discoveredResourcePools)
461
+ assert.Len(t, collr.charted, count.Host+count.Machine+count.Datastore+numClusters+numResourcePools)
462
+
463
+ assert.Len(t, *collr.Charts(),
464
+ count.Host*len(hostChartsTmpl)+
465
+ count.Machine*len(vmChartsTmpl)+
466
+ count.Datastore*(len(datastorePropertyChartsTmpl)+len(datastorePerfChartsTmpl))+
467
+ numClusters*(len(clusterPropertyChartsTmpl)+len(clusterPerfChartsTmpl))+
468
+ numResourcePools*len(resourcePoolChartsTmpl))
469
collecttest.TestMetricsHasAllChartsDims(t, collr.Charts(), mx)
470
}
471
@@ -388,10 +513,11 @@ func TestCollector_Collect_RemoveHostsVMsInRuntime(t *testing.T) {
513
514
assert.Len(t, collr.discoveredHosts, 1)
515
assert.Len(t, collr.discoveredVMs, 1)
391
- assert.Len(t, collr.charted, 2)
516
+ assert.Len(t, collr.charted, 2+len(collr.discoveredDatastores)+len(collr.discoveredClusters)+len(collr.discoveredResourcePools))
517
518
for _, c := range *collr.Charts() {
394
- if strings.HasPrefix(c.ID, okHostId) || strings.HasPrefix(c.ID, okVmId) {
519
+ if strings.HasPrefix(c.ID, okHostId) || strings.HasPrefix(c.ID, okVmId) ||
520
+ strings.HasPrefix(c.ID, "datastore-") || strings.HasPrefix(c.ID, "domain-") || strings.HasPrefix(c.ID, "resgroup-") {
521
assert.False(t, c.Obsolete)
522
} else {
523
assert.True(t, c.Obsolete)
@@ -418,8 +544,17 @@ func TestCollector_Collect_Run(t *testing.T) {
544
count := model.Count()
545
assert.Len(t, collr.discoveredHosts, count.Host)
546
assert.Len(t, collr.discoveredVMs, count.Machine)
421
- assert.Len(t, collr.charted, count.Host+count.Machine)
422
- assert.Len(t, *collr.charts, count.Host*len(hostChartsTmpl)+count.Machine*len(vmChartsTmpl))
547
+ assert.Len(t, collr.discoveredDatastores, count.Datastore)
548
+
549
+ numClusters := len(collr.discoveredClusters)
550
+ numResourcePools := len(collr.discoveredResourcePools)
551
+ assert.Len(t, collr.charted, count.Host+count.Machine+count.Datastore+numClusters+numResourcePools)
552
+ assert.Len(t, *collr.charts,
553
+ count.Host*len(hostChartsTmpl)+
554
+ count.Machine*len(vmChartsTmpl)+
555
+ count.Datastore*(len(datastorePropertyChartsTmpl)+len(datastorePerfChartsTmpl))+
556
+ numClusters*(len(clusterPropertyChartsTmpl)+len(clusterPerfChartsTmpl))+
557
+ numResourcePools*len(resourcePoolChartsTmpl))
558
}
559
560
func prepareVSphereSim(t *testing.T) (collr *Collector, model *simulator.Model, teardown func()) {
@@ -455,6 +590,14 @@ func (s mockScraper) ScrapeVMs(vms rs.VMs) []performance.EntityMetric {
590
ms := s.scraper.ScrapeVMs(vms)
591
return populateMetrics(ms, 200)
592
}
593
+func (s mockScraper) ScrapeDatastores(datastores rs.Datastores) []performance.EntityMetric {
594
+ ms := s.scraper.ScrapeDatastores(datastores)
595
+ return populateMetrics(ms, 300)
596
+}
597
+func (s mockScraper) ScrapeClusters(clusters rs.Clusters) []performance.EntityMetric {
598
+ ms := s.scraper.ScrapeClusters(clusters)
599
+ return populateMetrics(ms, 400)
600
+}
601
602
func populateMetrics(ms []performance.EntityMetric, value int64) []performance.EntityMetric {
603
for i := range ms {
@@ -475,3 +618,280 @@ type mockVMMatcher struct{ name string }
618
619
func (m mockHostMatcher) Match(host *rs.Host) bool { return m.name == host.ID }
620
func (m mockVMMatcher) Match(vm *rs.VM) bool { return m.name == vm.ID }
621
+
622
+// mockScraperNoDSPerf wraps a scraper but returns no perf data for datastores (simulates vSAN).
623
+type mockScraperNoDSPerf struct {
624
+ scraper
625
+}
626
+
627
+func (s mockScraperNoDSPerf) ScrapeHosts(hosts rs.Hosts) []performance.EntityMetric {
628
+ return populateMetrics(s.scraper.ScrapeHosts(hosts), 100)
629
+}
630
+func (s mockScraperNoDSPerf) ScrapeVMs(vms rs.VMs) []performance.EntityMetric {
631
+ return populateMetrics(s.scraper.ScrapeVMs(vms), 200)
632
+}
633
+func (s mockScraperNoDSPerf) ScrapeDatastores(_ rs.Datastores) []performance.EntityMetric {
634
+ return nil
635
+}
636
+func (s mockScraperNoDSPerf) ScrapeClusters(clusters rs.Clusters) []performance.EntityMetric {
637
+ return populateMetrics(s.scraper.ScrapeClusters(clusters), 400)
638
+}
639
+
640
+func TestCollector_Collect_DatastoreNoPerfData(t *testing.T) {
641
+ collr, model, teardown := prepareVSphereSim(t)
642
+ defer teardown()
643
+
644
+ require.NoError(t, collr.Init(context.Background()))
645
+
646
+ collr.scraper = mockScraperNoDSPerf{collr.scraper}
647
+
648
+ mx := collr.Collect(context.Background())
649
+ require.NotNil(t, mx)
650
+
651
+ count := model.Count()
652
+
653
+ // Property metrics should be present.
654
+ for _, ds := range collr.resources.Datastores {
655
+ assert.Contains(t, mx, ds.ID+"_capacity")
656
+ assert.Contains(t, mx, ds.ID+"_free_space")
657
+ assert.Contains(t, mx, ds.ID+"_used_space")
658
+ assert.Contains(t, mx, ds.ID+"_used_space_pct")
659
+ assert.Contains(t, mx, ds.ID+"_overall.status.green")
660
+ }
661
+
662
+ // Perf metrics should NOT be present.
663
+ for _, ds := range collr.resources.Datastores {
664
+ assert.NotContains(t, mx, ds.ID+"_datastore.read.average")
665
+ assert.NotContains(t, mx, ds.ID+"_datastore.numberReadAveraged.average")
666
+ }
667
+
668
+ numClusters := len(collr.discoveredClusters)
669
+ numResourcePools := len(collr.discoveredResourcePools)
670
+
671
+ // Only property charts created for datastores, no perf charts.
672
+ assert.Len(t, *collr.Charts(),
673
+ count.Host*len(hostChartsTmpl)+
674
+ count.Machine*len(vmChartsTmpl)+
675
+ count.Datastore*len(datastorePropertyChartsTmpl)+
676
+ numClusters*(len(clusterPropertyChartsTmpl)+len(clusterPerfChartsTmpl))+
677
+ numResourcePools*len(resourcePoolChartsTmpl))
678
+
679
+ // datastorePerfReceived should be empty.
680
+ assert.Empty(t, collr.datastorePerfReceived)
681
+ assert.Empty(t, collr.datastorePerfCharted)
682
+
683
+ // Now switch to a scraper that returns perf data — perf charts should appear.
684
+ collr.scraper = mockScraper{collr.scraper.(mockScraperNoDSPerf).scraper}
685
+
686
+ mx = collr.Collect(context.Background())
687
+ require.NotNil(t, mx)
688
+
689
+ // Perf metrics should now be present.
690
+ for _, ds := range collr.resources.Datastores {
691
+ assert.Contains(t, mx, ds.ID+"_datastore.read.average")
692
+ }
693
+
694
+ // Both property and perf charts now for datastores.
695
+ assert.Len(t, *collr.Charts(),
696
+ count.Host*len(hostChartsTmpl)+
697
+ count.Machine*len(vmChartsTmpl)+
698
+ count.Datastore*(len(datastorePropertyChartsTmpl)+len(datastorePerfChartsTmpl))+
699
+ numClusters*(len(clusterPropertyChartsTmpl)+len(clusterPerfChartsTmpl))+
700
+ numResourcePools*len(resourcePoolChartsTmpl))
701
+
702
+ assert.Len(t, collr.datastorePerfReceived, count.Datastore)
703
+ assert.Len(t, collr.datastorePerfCharted, count.Datastore)
704
+}
705
+
706
+// mockScraperNoClusterPerf wraps a scraper but returns no perf data for clusters.
707
+type mockScraperNoClusterPerf struct {
708
+ scraper
709
+}
710
+
711
+func (s mockScraperNoClusterPerf) ScrapeHosts(hosts rs.Hosts) []performance.EntityMetric {
712
+ return populateMetrics(s.scraper.ScrapeHosts(hosts), 100)
713
+}
714
+func (s mockScraperNoClusterPerf) ScrapeVMs(vms rs.VMs) []performance.EntityMetric {
715
+ return populateMetrics(s.scraper.ScrapeVMs(vms), 200)
716
+}
717
+func (s mockScraperNoClusterPerf) ScrapeDatastores(datastores rs.Datastores) []performance.EntityMetric {
718
+ return populateMetrics(s.scraper.ScrapeDatastores(datastores), 300)
719
+}
720
+func (s mockScraperNoClusterPerf) ScrapeClusters(_ rs.Clusters) []performance.EntityMetric {
721
+ return nil
722
+}
723
+
724
+func TestCollector_Collect_ClusterNoPerfData(t *testing.T) {
725
+ collr, model, teardown := prepareVSphereSim(t)
726
+ defer teardown()
727
+
728
+ require.NoError(t, collr.Init(context.Background()))
729
+
730
+ collr.scraper = mockScraperNoClusterPerf{collr.scraper}
731
+
732
+ mx := collr.Collect(context.Background())
733
+ require.NotNil(t, mx)
734
+
735
+ count := model.Count()
736
+
737
+ // Cluster property metrics should be present.
738
+ for _, cl := range collr.resources.Clusters {
739
+ assert.Contains(t, mx, cl.ID+"_num_hosts")
740
+ assert.Contains(t, mx, cl.ID+"_total_cpu")
741
+ assert.Contains(t, mx, cl.ID+"_overall.status.green")
742
+ }
743
+
744
+ // Cluster perf metrics should NOT be present.
745
+ for _, cl := range collr.resources.Clusters {
746
+ assert.NotContains(t, mx, cl.ID+"_cpu.usage.average")
747
+ assert.NotContains(t, mx, cl.ID+"_clusterServices.cpufairness.latest")
748
+ }
749
+
750
+ numClusters := len(collr.discoveredClusters)
751
+ numResourcePools := len(collr.discoveredResourcePools)
752
+
753
+ // Only property charts created for clusters, no perf charts.
754
+ assert.Len(t, *collr.Charts(),
755
+ count.Host*len(hostChartsTmpl)+
756
+ count.Machine*len(vmChartsTmpl)+
757
+ count.Datastore*(len(datastorePropertyChartsTmpl)+len(datastorePerfChartsTmpl))+
758
+ numClusters*len(clusterPropertyChartsTmpl)+
759
+ numResourcePools*len(resourcePoolChartsTmpl))
760
+
761
+ // clusterPerfReceived should be empty.
762
+ assert.Empty(t, collr.clusterPerfReceived)
763
+ assert.Empty(t, collr.clusterPerfCharted)
764
+
765
+ // Now switch to a scraper that returns perf data — perf charts should appear.
766
+ collr.scraper = mockScraper{collr.scraper.(mockScraperNoClusterPerf).scraper}
767
+
768
+ mx = collr.Collect(context.Background())
769
+ require.NotNil(t, mx)
770
+
771
+ // Perf metrics should now be present.
772
+ for _, cl := range collr.resources.Clusters {
773
+ assert.Contains(t, mx, cl.ID+"_cpu.usage.average")
774
+ }
775
+
776
+ // Both property and perf charts now for clusters.
777
+ assert.Len(t, *collr.Charts(),
778
+ count.Host*len(hostChartsTmpl)+
779
+ count.Machine*len(vmChartsTmpl)+
780
+ count.Datastore*(len(datastorePropertyChartsTmpl)+len(datastorePerfChartsTmpl))+
781
+ numClusters*(len(clusterPropertyChartsTmpl)+len(clusterPerfChartsTmpl))+
782
+ numResourcePools*len(resourcePoolChartsTmpl))
783
+
784
+ assert.Len(t, collr.clusterPerfReceived, numClusters)
785
+ assert.Len(t, collr.clusterPerfCharted, numClusters)
786
+}
787
+
788
+func TestCollector_Collect_ClusterEvictionCleansUpMaps(t *testing.T) {
789
+ collr, _, teardown := prepareVSphereSim(t)
790
+ defer teardown()
791
+
792
+ require.NoError(t, collr.Init(context.Background()))
793
+
794
+ collr.scraper = mockScraper{collr.scraper}
795
+
796
+ // First collect — creates all charts including cluster perf charts.
797
+ mx := collr.Collect(context.Background())
798
+ require.NotNil(t, mx)
799
+
800
+ assert.NotEmpty(t, collr.discoveredClusters)
801
+ assert.NotEmpty(t, collr.clusterPerfReceived)
802
+ assert.NotEmpty(t, collr.clusterPerfCharted)
803
+
804
+ // Simulate eviction: disable property collector and perf scraper so the counter isn't reset,
805
+ // then set the failure counter to the eviction threshold.
806
+ collr.clusterPropertyCollector = nil
807
+ collr.scraper = mockScraperNoClusterPerf{collr.scraper.(mockScraper).scraper}
808
+ for id := range collr.discoveredClusters {
809
+ collr.discoveredClusters[id] = failedUpdatesLimit
810
+ }
811
+
812
+ // Next collect increments counter past the limit, triggering eviction in updateCharts.
813
+ collr.Collect(context.Background())
814
+
815
+ assert.Empty(t, collr.discoveredClusters)
816
+ assert.Empty(t, collr.clusterPerfReceived)
817
+ assert.Empty(t, collr.clusterPerfCharted)
818
+
819
+ // Cluster charts should be marked obsolete.
820
+ for _, c := range *collr.Charts() {
821
+ if strings.HasPrefix(c.ID, "domain-") {
822
+ assert.True(t, c.Obsolete, "chart %s should be obsolete", c.ID)
823
+ }
824
+ }
825
+}
826
+
827
+func TestCollector_Collect_ResourcePoolEvictionCleansUpMaps(t *testing.T) {
828
+ collr, _, teardown := prepareVSphereSim(t)
829
+ defer teardown()
830
+
831
+ require.NoError(t, collr.Init(context.Background()))
832
+
833
+ collr.scraper = mockScraper{collr.scraper}
834
+
835
+ // First collect — creates resource pool charts.
836
+ mx := collr.Collect(context.Background())
837
+ require.NotNil(t, mx)
838
+
839
+ assert.NotEmpty(t, collr.discoveredResourcePools)
840
+
841
+ // Simulate eviction: disable property collector so the counter isn't reset.
842
+ collr.rpPropertyCollector = nil
843
+ for id := range collr.discoveredResourcePools {
844
+ collr.discoveredResourcePools[id] = failedUpdatesLimit
845
+ }
846
+
847
+ // Next collect increments counter past the limit, triggering eviction.
848
+ collr.Collect(context.Background())
849
+
850
+ assert.Empty(t, collr.discoveredResourcePools)
851
+
852
+ // Resource pool charts should be marked obsolete.
853
+ for _, c := range *collr.Charts() {
854
+ if strings.HasPrefix(c.ID, "resgroup-") {
855
+ assert.True(t, c.Obsolete, "chart %s should be obsolete", c.ID)
856
+ }
857
+ }
858
+}
859
+
860
+func TestCollector_Collect_DatastoreEvictionCleansUpMaps(t *testing.T) {
861
+ collr, _, teardown := prepareVSphereSim(t)
862
+ defer teardown()
863
+
864
+ require.NoError(t, collr.Init(context.Background()))
865
+
866
+ collr.scraper = mockScraper{collr.scraper}
867
+
868
+ // First collect — creates all charts including datastore perf charts.
869
+ mx := collr.Collect(context.Background())
870
+ require.NotNil(t, mx)
871
+
872
+ assert.NotEmpty(t, collr.discoveredDatastores)
873
+ assert.NotEmpty(t, collr.datastorePerfReceived)
874
+ assert.NotEmpty(t, collr.datastorePerfCharted)
875
+
876
+ // Simulate eviction: disable property collector and perf scraper so the counter isn't reset,
877
+ // then set the failure counter to the eviction threshold.
878
+ collr.dsPropertyCollector = nil
879
+ collr.scraper = mockScraperNoDSPerf{collr.scraper.(mockScraper).scraper}
880
+ for id := range collr.discoveredDatastores {
881
+ collr.discoveredDatastores[id] = failedUpdatesLimit
882
+ }
883
+
884
+ // Next collect increments counter past the limit, triggering eviction in updateCharts.
885
+ collr.Collect(context.Background())
886
+
887
+ assert.Empty(t, collr.discoveredDatastores)
888
+ assert.Empty(t, collr.datastorePerfReceived)
889
+ assert.Empty(t, collr.datastorePerfCharted)
890
+
891
+ // Datastore charts should be marked obsolete.
892
+ for _, c := range *collr.Charts() {
893
+ if strings.HasPrefix(c.ID, "datastore-") {
894
+ assert.True(t, c.Obsolete, "chart %s should be obsolete", c.ID)
895
+ }
896
+ }
897
+}
src/go/plugin/go.d/collector/vsphere/config_schema.json
+52
-4
@@ -33,7 +33,7 @@
33
},
34
"discovery_interval": {
35
"title": "Discovery interval",
36
- "description": "Hosts and VMs discovery interval in seconds.",
36
+ "description": "Hosts, VMs, datastores, clusters, and resource pools discovery interval in seconds.",
37
"type": "number",
38
"minimum": 60,
39
"default": 300
@@ -86,6 +86,44 @@
86
"/*"
87
]
88
},
89
+ "datastore_include": {
90
+ "title": "Datastore selectors",
91
+ "description": "Configuration for monitoring specific datastores. The selector format follows the pattern `/Datacenter/Datastore`, where each value can be set using [Netdata simple patterns](https://github.com/netdata/netdata/tree/master/src/libnetdata/simple_pattern#readme).",
92
+ "type": [
93
+ "array",
94
+ "null"
95
+ ],
96
+ "uniqueItems": true,
97
+ "items": {
98
+ "title": "Datastore selector",
99
+ "description": "",
100
+ "type": "string",
101
+ "default": "/*/*",
102
+ "pattern": "^$|^/"
103
+ },
104
+ "default": [
105
+ "/*"
106
+ ]
107
+ },
108
+ "cluster_include": {
109
+ "title": "Cluster selectors",
110
+ "description": "Configuration for monitoring specific clusters and their resource pools. The selector format follows the pattern `/Datacenter/Cluster`, where each value can be set using [Netdata simple patterns](https://github.com/netdata/netdata/tree/master/src/libnetdata/simple_pattern#readme). Resource pools follow their owning cluster.",
111
+ "type": [
112
+ "array",
113
+ "null"
114
+ ],
115
+ "uniqueItems": true,
116
+ "items": {
117
+ "title": "Cluster selector",
118
+ "description": "",
119
+ "type": "string",
120
+ "default": "/*/*",
121
+ "pattern": "^$|^/"
122
+ },
123
+ "default": [
124
+ "/*"
125
+ ]
126
+ },
127
"username": {
128
"title": "Username",
129
"description": "The username for basic authentication.",
@@ -173,7 +211,9 @@
211
"username",
212
"password",
213
"host_include",
176
- "vm_include"
214
+ "vm_include",
215
+ "datastore_include",
216
+ "cluster_include"
217
]
218
},
219
"uiSchema": {
@@ -196,10 +236,12 @@
236
]
237
},
238
{
199
- "title": "Hosts & VMs selector",
239
+ "title": "Hosts, VMs, Datastores & Clusters selector",
240
"fields": [
241
"host_include",
202
- "vm_include"
242
+ "vm_include",
243
+ "datastore_include",
244
+ "cluster_include"
245
]
246
},
247
{
@@ -268,6 +310,12 @@
310
"vm_include": {
311
"ui:listFlavour": "list"
312
},
313
+ "datastore_include": {
314
+ "ui:listFlavour": "list"
315
+ },
316
+ "cluster_include": {
317
+ "ui:listFlavour": "list"
318
+ },
319
"username": {
320
"ui:placeholder": "admin@vsphere.local",
321
"ui:widget": "password"
src/go/plugin/go.d/collector/vsphere/discover/build.go
+65
-1
@@ -21,8 +21,10 @@ func (d Discoverer) build(raw *resources) *rs.Resources {
21
fixClustersParentID(&res)
22
res.Hosts = d.buildHosts(raw.hosts)
23
res.VMs = d.buildVMs(raw.vms)
24
+ res.Datastores = d.buildDatastores(raw.datastores)
25
+ res.ResourcePools = d.buildResourcePools(raw.resourcePools, res.Clusters)
26
25
- d.Infof("discovering : building : built %d/%d dcs, %d/%d folders, %d/%d clusters, %d/%d hosts, %d/%d vms, process took %s",
27
+ d.Infof("discovering : building : built %d/%d dcs, %d/%d folders, %d/%d clusters, %d/%d hosts, %d/%d vms, %d/%d datastores, %d/%d resource pools, process took %s",
28
len(res.DataCenters),
29
len(raw.dcs),
30
len(res.Folders),
@@ -33,6 +35,10 @@ func (d Discoverer) build(raw *resources) *rs.Resources {
35
len(raw.hosts),
36
len(res.VMs),
37
len(raw.vms),
38
+ len(res.Datastores),
39
+ len(raw.datastores),
40
+ len(res.ResourcePools),
41
+ len(raw.resourcePools),
42
time.Since(t),
43
)
44
return &res
@@ -106,6 +112,7 @@ func newCluster(raw mo.ComputeResource) *rs.Cluster {
112
Name: raw.Name,
113
ID: raw.Reference().Value,
114
ParentID: raw.Parent.Value,
115
+ Ref: raw.Reference(),
116
}
117
}
118
@@ -178,3 +185,60 @@ func newVM(raw mo.VirtualMachine) *rs.VM {
185
Ref: raw.Reference(),
186
}
187
}
188
+
189
+func (d Discoverer) buildDatastores(raw []mo.Datastore) rs.Datastores {
190
+ var num int
191
+ datastores := make(rs.Datastores)
192
+ for _, ds := range raw {
193
+ if !ds.Summary.Accessible {
194
+ num++
195
+ continue
196
+ }
197
+ datastores.Put(newDatastore(ds))
198
+ }
199
+ if num > 0 {
200
+ d.Infof("discovering : building : removed %d datastores (not accessible)", num)
201
+ }
202
+ return datastores
203
+}
204
+
205
+func newDatastore(raw mo.Datastore) *rs.Datastore {
206
+ return &rs.Datastore{
207
+ Name: raw.Name,
208
+ ID: raw.Reference().Value,
209
+ ParentID: raw.Parent.Value,
210
+ OverallStatus: string(raw.OverallStatus),
211
+ Type: raw.Summary.Type,
212
+ Capacity: raw.Summary.Capacity,
213
+ FreeSpace: raw.Summary.FreeSpace,
214
+ Accessible: raw.Summary.Accessible,
215
+ Ref: raw.Reference(),
216
+ }
217
+}
218
+
219
+func (d Discoverer) buildResourcePools(raw []mo.ResourcePool, clusters rs.Clusters) rs.ResourcePools {
220
+ pools := make(rs.ResourcePools)
221
+ for _, rp := range raw {
222
+ // owner is the cluster that owns this pool
223
+ ownerID := rp.Owner.Value
224
+ // skip pools whose owner is a dummy cluster (standalone host)
225
+ if isDummyCluster(ownerID) {
226
+ continue
227
+ }
228
+ // skip pools whose owner cluster wasn't discovered
229
+ if clusters.Get(ownerID) == nil {
230
+ continue
231
+ }
232
+ pools.Put(newResourcePool(rp))
233
+ }
234
+ return pools
235
+}
236
+
237
+func newResourcePool(raw mo.ResourcePool) *rs.ResourcePool {
238
+ return &rs.ResourcePool{
239
+ Name: raw.Name,
240
+ ID: raw.Reference().Value,
241
+ ParentID: raw.Owner.Value, // owner cluster ref
242
+ Ref: raw.Reference(),
243
+ }
244
+}
src/go/plugin/go.d/collector/vsphere/discover/discover.go
+62
-20
@@ -21,6 +21,8 @@ type Client interface {
21
ComputeResources(pathSet ...string) ([]mo.ComputeResource, error)
22
Hosts(pathSet ...string) ([]mo.HostSystem, error)
23
VirtualMachines(pathSet ...string) ([]mo.VirtualMachine, error)
24
+ Datastores(pathSet ...string) ([]mo.Datastore, error)
25
+ ResourcePools(pathSet ...string) ([]mo.ResourcePool, error)
26
27
CounterInfoByName() (map[string]*types.PerfCounterInfo, error)
28
}
@@ -36,14 +38,18 @@ type Discoverer struct {
38
Client
39
match.HostMatcher
40
match.VMMatcher
41
+ match.DatastoreMatcher
42
+ match.ClusterMatcher
43
}
44
45
type resources struct {
42
- dcs []mo.Datacenter
43
- folders []mo.Folder
44
- clusters []mo.ComputeResource
45
- hosts []mo.HostSystem
46
- vms []mo.VirtualMachine
46
+ dcs []mo.Datacenter
47
+ folders []mo.Folder
48
+ clusters []mo.ComputeResource
49
+ hosts []mo.HostSystem
50
+ vms []mo.VirtualMachine
51
+ datastores []mo.Datastore
52
+ resourcePools []mo.ResourcePool
53
}
54
55
func (d Discoverer) Discover() (*rs.Resources, error) {
@@ -61,11 +67,13 @@ func (d Discoverer) Discover() (*rs.Resources, error) {
67
d.Error(err)
68
}
69
70
+ numC := len(res.Clusters)
71
numH := len(res.Hosts)
72
numV := len(res.VMs)
73
+ numD := len(res.Datastores)
74
removed := d.removeUnmatched(res)
67
- if removed == (numH + numV) {
68
- return nil, fmt.Errorf("all resoursces were filtered (%d hosts, %d vms)", numH, numV)
75
+ if removed == (numC + numH + numV + numD) {
76
+ return nil, fmt.Errorf("all resources were filtered (%d clusters, %d hosts, %d vms, %d datastores)", numC, numH, numV, numD)
77
}
78
79
err = d.collectMetricLists(res)
@@ -73,11 +81,16 @@ func (d Discoverer) Discover() (*rs.Resources, error) {
81
return nil, fmt.Errorf("collecting metric lists : %v", err)
82
}
83
76
- d.Infof("discovering : discovered %d/%d hosts, %d/%d vms, the whole process took %s",
84
+ d.Infof("discovering : discovered %d/%d clusters, %d/%d hosts, %d/%d vms, %d/%d datastores, %d resource pools, the whole process took %s",
85
+ len(res.Clusters),
86
+ numOfRealClusters(raw.clusters),
87
len(res.Hosts),
88
len(raw.hosts),
89
len(res.VMs),
90
len(raw.vms),
91
+ len(res.Datastores),
92
+ len(raw.datastores),
93
+ len(res.ResourcePools),
94
time.Since(startTime))
95
96
return res, nil
@@ -85,11 +98,13 @@ func (d Discoverer) Discover() (*rs.Resources, error) {
98
99
var (
100
// properties to set
88
- datacenterPathSet = []string{"name", "parent"}
89
- folderPathSet = []string{"name", "parent"}
90
- clusterPathSet = []string{"name", "parent"}
91
- hostPathSet = []string{"name", "parent", "runtime.powerState", "summary.overallStatus"}
92
- vmPathSet = []string{"name", "runtime.host", "runtime.powerState", "summary.overallStatus"}
101
+ datacenterPathSet = []string{"name", "parent"}
102
+ folderPathSet = []string{"name", "parent"}
103
+ clusterPathSet = []string{"name", "parent"}
104
+ hostPathSet = []string{"name", "parent", "runtime.powerState", "summary.overallStatus"}
105
+ vmPathSet = []string{"name", "runtime.host", "runtime.powerState", "summary.overallStatus"}
106
+ datastorePathSet = []string{"name", "parent", "summary", "overallStatus"}
107
+ resourcePoolPathSet = []string{"name", "owner"}
108
)
109
110
func (d Discoverer) discover() (*resources, error) {
@@ -129,23 +144,41 @@ func (d Discoverer) discover() (*resources, error) {
144
if err != nil {
145
return nil, err
146
}
132
- d.Debugf("discovering : found %d vms, process took %s", len(hosts), time.Since(t))
147
+ d.Debugf("discovering : found %d vms, process took %s", len(vms), time.Since(t))
148
+
149
+ t = time.Now()
150
+ datastores, err := d.Datastores(datastorePathSet...)
151
+ if err != nil {
152
+ return nil, err
153
+ }
154
+ d.Debugf("discovering : found %d datastores, process took %s", len(datastores), time.Since(t))
155
+
156
+ t = time.Now()
157
+ resourcePools, err := d.ResourcePools(resourcePoolPathSet...)
158
+ if err != nil {
159
+ return nil, err
160
+ }
161
+ d.Debugf("discovering : found %d resource pools, process took %s", len(resourcePools), time.Since(t))
162
163
raw := resources{
135
- dcs: datacenters,
136
- folders: folders,
137
- clusters: clusters,
138
- hosts: hosts,
139
- vms: vms,
164
+ dcs: datacenters,
165
+ folders: folders,
166
+ clusters: clusters,
167
+ hosts: hosts,
168
+ vms: vms,
169
+ datastores: datastores,
170
+ resourcePools: resourcePools,
171
}
172
142
- d.Infof("discovering : found %d dcs, %d folders, %d clusters (%d dummy), %d hosts, %d vms, process took %s",
173
+ d.Infof("discovering : found %d dcs, %d folders, %d clusters (%d dummy), %d hosts, %d vms, %d datastores, %d resource pools, process took %s",
174
len(raw.dcs),
175
len(raw.folders),
176
len(clusters),
177
numOfDummyClusters(clusters),
178
len(raw.hosts),
179
len(raw.vms),
180
+ len(raw.datastores),
181
+ len(raw.resourcePools),
182
time.Since(start),
183
)
184
@@ -161,3 +194,12 @@ func numOfDummyClusters(clusters []mo.ComputeResource) (num int) {
194
}
195
return num
196
}
197
+
198
+func numOfRealClusters(clusters []mo.ComputeResource) int {
199
+ return len(clusters) - numOfDummyClusters(clusters)
200
+}
201
+
202
+// isDummyCluster returns true for standalone-host placeholder clusters (domain-s*).
203
+func isDummyCluster(id string) bool {
204
+ return strings.HasPrefix(id, "domain-s")
205
+}
src/go/plugin/go.d/collector/vsphere/discover/discover_test.go
+21
-3
@@ -29,6 +29,7 @@ func TestDiscoverer_Discover(t *testing.T) {
29
assert.True(t, len(res.Clusters) > 0)
30
assert.True(t, len(res.Hosts) > 0)
31
assert.True(t, len(res.VMs) > 0)
32
+ assert.True(t, len(res.Datastores) > 0)
33
assert.True(t, isHierarchySet(res))
34
assert.True(t, isMetricListsCollected(res))
35
}
@@ -46,7 +47,8 @@ func TestDiscoverer_discover(t *testing.T) {
47
dummyClusters := model.Host * count.Datacenter
48
assert.Lenf(t, raw.clusters, count.Cluster+dummyClusters, "clusters")
49
assert.Lenf(t, raw.hosts, count.Host, "hosts")
49
- assert.Lenf(t, raw.vms, count.Machine, "hosts")
50
+ assert.Lenf(t, raw.vms, count.Machine, "vms")
51
+ assert.Lenf(t, raw.datastores, count.Datastore, "datastores")
52
}
53
54
func TestDiscoverer_build(t *testing.T) {
@@ -62,7 +64,8 @@ func TestDiscoverer_build(t *testing.T) {
64
assert.Lenf(t, res.Folders, len(raw.folders), "folders")
65
assert.Lenf(t, res.Clusters, len(raw.clusters), "clusters")
66
assert.Lenf(t, res.Hosts, len(raw.hosts), "hosts")
65
- assert.Lenf(t, res.VMs, len(raw.vms), "hosts")
67
+ assert.Lenf(t, res.VMs, len(raw.vms), "vms")
68
+ assert.Lenf(t, res.Datastores, len(raw.datastores), "datastores")
69
}
70
71
func TestDiscoverer_setHierarchy(t *testing.T) {
@@ -89,8 +92,12 @@ func TestDiscoverer_removeUnmatched(t *testing.T) {
92
require.NoError(t, err)
93
res := d.build(raw)
94
95
+ numClusters := len(res.Clusters)
96
numVMs, numHosts := len(res.VMs), len(res.Hosts)
93
- assert.Equal(t, numVMs+numHosts, d.removeUnmatched(res))
97
+ removed := d.removeUnmatched(res)
98
+ // dummy clusters are removed + all hosts/VMs rejected by matchers
99
+ remainingClusters := len(res.Clusters)
100
+ assert.Equal(t, (numClusters-remainingClusters)+numVMs+numHosts, removed)
101
assert.Lenf(t, res.Hosts, 0, "hosts")
102
assert.Lenf(t, res.VMs, 0, "vms")
103
}
@@ -153,6 +160,16 @@ func isHierarchySet(res *rs.Resources) bool {
160
return false
161
}
162
}
163
+ for _, ds := range res.Datastores {
164
+ if !ds.Hier.IsSet() {
165
+ return false
166
+ }
167
+ }
168
+ for _, rp := range res.ResourcePools {
169
+ if !rp.Hier.IsSet() {
170
+ return false
171
+ }
172
+ }
173
return true
174
}
175
@@ -167,6 +184,7 @@ func isMetricListsCollected(res *rs.Resources) bool {
184
return false
185
}
186
}
187
+ // Datastore metric lists may be empty if perf counters are not available (e.g., vSAN)
188
return true
189
}
190
src/go/plugin/go.d/collector/vsphere/discover/filter.go
+64
-2
@@ -25,14 +25,22 @@ func (d Discoverer) matchVM(vm *rs.VM) bool {
25
func (d Discoverer) removeUnmatched(res *rs.Resources) (removed int) {
26
d.Debug("discovering : filtering : starting filtering resources process")
27
t := time.Now()
28
- numH, numV := len(res.Hosts), len(res.VMs)
28
+ numC, numH, numV, numD := len(res.Clusters), len(res.Hosts), len(res.VMs), len(res.Datastores)
29
+ removed += d.removeUnmatchedClusters(res.Clusters)
30
+ d.removeOrphanedResourcePools(res.ResourcePools, res.Clusters)
31
removed += d.removeUnmatchedHosts(res.Hosts)
32
removed += d.removeUnmatchedVMs(res.VMs)
31
- d.Infof("discovering : filtering : filtered %d/%d hosts, %d/%d vms, process took %s",
33
+ removed += d.removeUnmatchedDatastores(res.Datastores)
34
+ d.Infof("discovering : filtering : filtered %d/%d clusters, %d/%d hosts, %d/%d vms, %d/%d datastores, %d resource pools remaining, process took %s",
35
+ numC-len(res.Clusters),
36
+ numC,
37
numH-len(res.Hosts),
38
numH,
39
numV-len(res.VMs),
40
numV,
41
+ numD-len(res.Datastores),
42
+ numD,
43
+ len(res.ResourcePools),
44
time.Since(t))
45
return
46
}
@@ -58,3 +66,57 @@ func (d Discoverer) removeUnmatchedVMs(vms rs.VMs) (removed int) {
66
d.Debugf("discovering : filtering : removed %d unmatched vms", removed)
67
return removed
68
}
69
+
70
+func (d Discoverer) matchDatastore(ds *rs.Datastore) bool {
71
+ if d.DatastoreMatcher == nil {
72
+ return true
73
+ }
74
+ return d.DatastoreMatcher.Match(ds)
75
+}
76
+
77
+func (d Discoverer) removeUnmatchedDatastores(datastores rs.Datastores) (removed int) {
78
+ for _, ds := range datastores {
79
+ if !d.matchDatastore(ds) {
80
+ removed++
81
+ datastores.Remove(ds.ID)
82
+ }
83
+ }
84
+ d.Debugf("discovering : filtering : removed %d unmatched datastores", removed)
85
+ return removed
86
+}
87
+
88
+func (d Discoverer) matchCluster(cluster *rs.Cluster) bool {
89
+ // dummy clusters (standalone host placeholders) are always excluded
90
+ if isDummyCluster(cluster.ID) {
91
+ return false
92
+ }
93
+ if d.ClusterMatcher == nil {
94
+ return true
95
+ }
96
+ return d.ClusterMatcher.Match(cluster)
97
+}
98
+
99
+func (d Discoverer) removeUnmatchedClusters(clusters rs.Clusters) (removed int) {
100
+ for _, c := range clusters {
101
+ if !d.matchCluster(c) {
102
+ removed++
103
+ clusters.Remove(c.ID)
104
+ }
105
+ }
106
+ d.Debugf("discovering : filtering : removed %d unmatched/dummy clusters", removed)
107
+ return removed
108
+}
109
+
110
+// removeOrphanedResourcePools removes pools whose owner cluster was filtered out.
111
+func (d Discoverer) removeOrphanedResourcePools(pools rs.ResourcePools, clusters rs.Clusters) {
112
+ var removed int
113
+ for _, rp := range pools {
114
+ if clusters.Get(rp.ParentID) == nil {
115
+ removed++
116
+ pools.Remove(rp.ID)
117
+ }
118
+ }
119
+ if removed > 0 {
120
+ d.Debugf("discovering : filtering : removed %d orphaned resource pools", removed)
121
+ }
122
+}
src/go/plugin/go.d/collector/vsphere/discover/hierarchy.go
+58
-2
@@ -15,12 +15,15 @@ func (d Discoverer) setHierarchy(res *rs.Resources) error {
15
c := d.setClustersHierarchy(res)
16
h := d.setHostsHierarchy(res)
17
v := d.setVMsHierarchy(res)
18
+ ds := d.setDatastoresHierarchy(res)
19
+ rp := d.setResourcePoolsHierarchy(res)
20
19
- // notSet := len(res.Clusters) + len(res.Hosts) + len(res.VMs) - (c + h + v)
20
- d.Infof("discovering : hierarchy : set %d/%d clusters, %d/%d hosts, %d/%d vms, process took %s",
21
+ d.Infof("discovering : hierarchy : set %d/%d clusters, %d/%d hosts, %d/%d vms, %d/%d datastores, %d/%d resource pools, process took %s",
22
c, len(res.Clusters),
23
h, len(res.Hosts),
24
v, len(res.VMs),
25
+ ds, len(res.Datastores),
26
+ rp, len(res.ResourcePools),
27
time.Since(t),
28
)
29
@@ -98,3 +101,56 @@ func setVMHierarchy(vm *rs.VM, res *rs.Resources) bool {
101
vm.Hier.DC.Set(dc.ID, dc.Name)
102
return vm.Hier.IsSet()
103
}
104
+
105
+func (d Discoverer) setDatastoresHierarchy(res *rs.Resources) (set int) {
106
+ for _, ds := range res.Datastores {
107
+ if setDatastoreHierarchy(ds, res) {
108
+ set++
109
+ }
110
+ }
111
+ return set
112
+}
113
+
114
+// Datastore parent is a folder (group-s*) which resolves to a datacenter.
115
+func setDatastoreHierarchy(ds *rs.Datastore, res *rs.Resources) bool {
116
+ dcID := findDatastoreDcID(ds.ParentID, res.Folders)
117
+ dc := res.DataCenters.Get(dcID)
118
+ if dc == nil {
119
+ return false
120
+ }
121
+ ds.Hier.DC.Set(dc.ID, dc.Name)
122
+ return ds.Hier.IsSet()
123
+}
124
+
125
+func findDatastoreDcID(parentID string, folders rs.Folders) string {
126
+ f := folders.Get(parentID)
127
+ if f == nil {
128
+ return parentID
129
+ }
130
+ return findDatastoreDcID(f.ParentID, folders)
131
+}
132
+
133
+func (d Discoverer) setResourcePoolsHierarchy(res *rs.Resources) (set int) {
134
+ for _, rp := range res.ResourcePools {
135
+ if setResourcePoolHierarchy(rp, res) {
136
+ set++
137
+ }
138
+ }
139
+ return set
140
+}
141
+
142
+// Resource pool owner is a cluster. Resolve DC from the cluster.
143
+func setResourcePoolHierarchy(rp *rs.ResourcePool, res *rs.Resources) bool {
144
+ cr := res.Clusters.Get(rp.ParentID)
145
+ if cr == nil {
146
+ return false
147
+ }
148
+ rp.Hier.Cluster.Set(cr.ID, cr.Name)
149
+
150
+ dc := res.DataCenters.Get(cr.ParentID)
151
+ if dc == nil {
152
+ return false
153
+ }
154
+ rp.Hier.DC.Set(dc.ID, dc.Name)
155
+ return rp.Hier.IsSet()
156
+}
src/go/plugin/go.d/collector/vsphere/discover/metric_lists.go
+79
-3
@@ -28,12 +28,20 @@ func (d Discoverer) collectMetricLists(res *rs.Resources) error {
28
for _, v := range res.VMs {
29
v.MetricList = vmML
30
}
31
+ dsML := simpleDatastoreMetricList(perfCounters)
32
+ for _, ds := range res.Datastores {
33
+ ds.MetricList = dsML
34
+ }
35
+ clusterML := simpleClusterMetricList(perfCounters)
36
+ for _, c := range res.Clusters {
37
+ c.MetricList = clusterML
38
+ }
39
32
- d.Infof("discovering : metric lists : collected metric lists for %d/%d hosts, %d/%d vms, process took %s",
33
- len(res.Hosts),
40
+ d.Infof("discovering : metric lists : collected metric lists for %d clusters, %d hosts, %d vms, %d datastores, process took %s",
41
+ len(res.Clusters),
42
len(res.Hosts),
43
len(res.VMs),
36
- len(res.VMs),
44
+ len(res.Datastores),
45
time.Since(t),
46
)
47
@@ -48,6 +56,14 @@ func simpleVMMetricList(pci map[string]*types.PerfCounterInfo) performance.Metri
56
return simpleMetricList(vmMetrics, pci)
57
}
58
59
+func simpleDatastoreMetricList(pci map[string]*types.PerfCounterInfo) performance.MetricList {
60
+ return simpleMetricList(datastoreMetrics, pci)
61
+}
62
+
63
+func simpleClusterMetricList(pci map[string]*types.PerfCounterInfo) performance.MetricList {
64
+ return simpleMetricList(clusterMetrics, pci)
65
+}
66
+
67
func simpleMetricList(metrics []string, pci map[string]*types.PerfCounterInfo) performance.MetricList {
68
sort.Strings(metrics)
69
@@ -103,6 +119,18 @@ var (
119
"sys.uptime.latest",
120
}
121
122
+ datastoreMetrics = []string{
123
+ // Level 1 - available at default vCenter settings
124
+ "datastore.numberReadAveraged.average",
125
+ "datastore.numberWriteAveraged.average",
126
+ "datastore.totalReadLatency.average",
127
+ "datastore.totalWriteLatency.average",
128
+
129
+ // Level 2 - requires elevated stats level
130
+ "datastore.read.average",
131
+ "datastore.write.average",
132
+ }
133
+
134
hostMetrics = []string{
135
"cpu.usage.average",
136
@@ -132,4 +160,52 @@ var (
160
161
"sys.uptime.latest",
162
}
163
+
164
+ // All Level 1 (available at default vCenter settings), IntervalId=300 (historical)
165
+ clusterMetrics = []string{
166
+ // clusterServices counters
167
+ "clusterServices.effectivecpu.average",
168
+ "clusterServices.effectivemem.average",
169
+ "clusterServices.cpufairness.latest",
170
+ "clusterServices.memfairness.latest",
171
+ "clusterServices.failover.latest",
172
+
173
+ // cpu/mem aggregate counters
174
+ "cpu.usage.average",
175
+ "cpu.usagemhz.average",
176
+ "cpu.totalmhz.average",
177
+ "mem.usage.average",
178
+ "mem.consumed.average",
179
+ "mem.overhead.average",
180
+ "mem.active.average",
181
+ "mem.granted.average",
182
+ "mem.shared.average",
183
+ "mem.swapused.average",
184
+
185
+ // vmop counters — VM operations per cluster
186
+ "vmop.numVMotion.latest",
187
+ "vmop.numSVMotion.latest",
188
+ "vmop.numXVMotion.latest",
189
+ "vmop.numPoweron.latest",
190
+ "vmop.numPoweroff.latest",
191
+ "vmop.numCreate.latest",
192
+ "vmop.numDestroy.latest",
193
+ "vmop.numClone.latest",
194
+ "vmop.numDeploy.latest",
195
+ "vmop.numReset.latest",
196
+ "vmop.numSuspend.latest",
197
+ "vmop.numReconfigure.latest",
198
+ "vmop.numRegister.latest",
199
+ "vmop.numUnregister.latest",
200
+ "vmop.numChangeDS.latest",
201
+ "vmop.numChangeHost.latest",
202
+ "vmop.numChangeHostDS.latest",
203
+ "vmop.numRebootGuest.latest",
204
+ "vmop.numShutdownGuest.latest",
205
+ "vmop.numStandbyGuest.latest",
206
+
207
+ // vSphere 7.0+ only — automatically skipped if not available
208
+ "clusterServices.clusterDrsScore.latest",
209
+ "clusterServices.vmDrsScore.latest",
210
+ }
211
)
src/go/plugin/go.d/collector/vsphere/init.go
+18
@@ -54,6 +54,21 @@ func (c *Collector) initDiscoverer(cli *client.Client) error {
54
if vmm != nil {
55
d.VMMatcher = vmm
56
}
57
+ dsm, err := c.DatastoresInclude.Parse()
58
+ if err != nil {
59
+ return err
60
+ }
61
+ if dsm != nil {
62
+ d.DatastoreMatcher = dsm
63
+ }
64
+
65
+ cm, err := c.ClustersInclude.Parse()
66
+ if err != nil {
67
+ return err
68
+ }
69
+ if cm != nil {
70
+ d.ClusterMatcher = cm
71
+ }
72
73
c.discoverer = d
74
return nil
@@ -63,4 +78,7 @@ func (c *Collector) initScraper(cli *client.Client) {
78
ms := scrape.New(cli)
79
ms.Logger = c.Logger
80
c.scraper = ms
81
+ c.dsPropertyCollector = cli
82
+ c.clusterPropertyCollector = cli
83
+ c.rpPropertyCollector = cli
84
}
src/go/plugin/go.d/collector/vsphere/match/match.go
+178
-2
@@ -18,6 +18,14 @@ type VMMatcher interface {
18
Match(*rs.VM) bool
19
}
20
21
+type DatastoreMatcher interface {
22
+ Match(*rs.Datastore) bool
23
+}
24
+
25
+type ClusterMatcher interface {
26
+ Match(*rs.Cluster) bool
27
+}
28
+
29
type (
30
hostDCMatcher struct{ m matcher.Matcher }
31
hostClusterMatcher struct{ m matcher.Matcher }
@@ -84,11 +92,57 @@ func newOrVMMatcher(lhs, rhs VMMatcher, others ...VMMatcher) orVMMatcher {
92
}
93
}
94
95
+func newOrDSMatcher(lhs, rhs DatastoreMatcher, others ...DatastoreMatcher) orDSMatcher {
96
+ m := orDSMatcher{lhs: lhs, rhs: rhs}
97
+ switch len(others) {
98
+ case 0:
99
+ return m
100
+ default:
101
+ return newOrDSMatcher(m, others[0], others[1:]...)
102
+ }
103
+}
104
+
105
+type (
106
+ VMIncludes []string
107
+ HostIncludes []string
108
+ DatastoreIncludes []string
109
+ ClusterIncludes []string
110
+)
111
+
112
+type (
113
+ dsDCMatcher struct{ m matcher.Matcher }
114
+ dsDSMatcher struct{ m matcher.Matcher }
115
+ orDSMatcher struct{ lhs, rhs DatastoreMatcher }
116
+ andDSMatcher struct{ lhs, rhs DatastoreMatcher }
117
+)
118
+
119
+func (m dsDCMatcher) Match(ds *rs.Datastore) bool { return m.m.MatchString(ds.Hier.DC.Name) }
120
+func (m dsDSMatcher) Match(ds *rs.Datastore) bool { return m.m.MatchString(ds.Name) }
121
+func (m orDSMatcher) Match(ds *rs.Datastore) bool { return m.lhs.Match(ds) || m.rhs.Match(ds) }
122
+func (m andDSMatcher) Match(ds *rs.Datastore) bool { return m.lhs.Match(ds) && m.rhs.Match(ds) }
123
+
124
type (
88
- VMIncludes []string
89
- HostIncludes []string
125
+ clusterDCMatcher struct{ m matcher.Matcher }
126
+ clusterNameMatcher struct{ m matcher.Matcher }
127
+ orClusterMatcher struct{ lhs, rhs ClusterMatcher }
128
+ andClusterMatcher struct{ lhs, rhs ClusterMatcher }
129
)
130
131
+func (m clusterDCMatcher) Match(c *rs.Cluster) bool { return m.m.MatchString(c.Hier.DC.Name) }
132
+func (m clusterNameMatcher) Match(c *rs.Cluster) bool { return m.m.MatchString(c.Name) }
133
+func (m orClusterMatcher) Match(c *rs.Cluster) bool { return m.lhs.Match(c) || m.rhs.Match(c) }
134
+func (m andClusterMatcher) Match(c *rs.Cluster) bool { return m.lhs.Match(c) && m.rhs.Match(c) }
135
+
136
+func newOrClusterMatcher(lhs, rhs ClusterMatcher, others ...ClusterMatcher) orClusterMatcher {
137
+ m := orClusterMatcher{lhs: lhs, rhs: rhs}
138
+ switch len(others) {
139
+ case 0:
140
+ return m
141
+ default:
142
+ return newOrClusterMatcher(m, others[0], others[1:]...)
143
+ }
144
+}
145
+
146
func (vi VMIncludes) Parse() (VMMatcher, error) {
147
var ms []VMMatcher
148
for _, v := range vi {
@@ -135,6 +189,29 @@ func (hi HostIncludes) Parse() (HostMatcher, error) {
189
}
190
}
191
192
+func (di DatastoreIncludes) Parse() (DatastoreMatcher, error) {
193
+ var ms []DatastoreMatcher
194
+ for _, v := range di {
195
+ m, err := parseDatastoreInclude(v)
196
+ if err != nil {
197
+ return nil, err
198
+ }
199
+ if m == nil {
200
+ continue
201
+ }
202
+ ms = append(ms, m)
203
+ }
204
+
205
+ switch len(ms) {
206
+ case 0:
207
+ return nil, nil
208
+ case 1:
209
+ return ms[0], nil
210
+ default:
211
+ return newOrDSMatcher(ms[0], ms[1], ms[2:]...), nil
212
+ }
213
+}
214
+
215
const (
216
datacenterIdx = iota
217
clusterIdx
@@ -231,3 +308,102 @@ func parseSubInclude(sub string) (matcher.Matcher, error) {
308
func isIncludeFormatValid(line string) bool {
309
return strings.HasPrefix(line, "/")
310
}
311
+
312
+func (ci ClusterIncludes) Parse() (ClusterMatcher, error) {
313
+ var ms []ClusterMatcher
314
+ for _, v := range ci {
315
+ m, err := parseClusterInclude(v)
316
+ if err != nil {
317
+ return nil, err
318
+ }
319
+ if m == nil {
320
+ continue
321
+ }
322
+ ms = append(ms, m)
323
+ }
324
+
325
+ switch len(ms) {
326
+ case 0:
327
+ return nil, nil
328
+ case 1:
329
+ return ms[0], nil
330
+ default:
331
+ return newOrClusterMatcher(ms[0], ms[1], ms[2:]...), nil
332
+ }
333
+}
334
+
335
+const (
336
+ clDCIdx = iota
337
+ clClusterIdx
338
+)
339
+
340
+func parseClusterInclude(include string) (ClusterMatcher, error) {
341
+ if !isIncludeFormatValid(include) {
342
+ return nil, fmt.Errorf("bad include format: %s", include)
343
+ }
344
+
345
+ include = cleanInclude(include)
346
+ parts := strings.Split(include, "/") // /dc/cluster
347
+ var ms []ClusterMatcher
348
+
349
+ for i, v := range parts {
350
+ m, err := parseSubInclude(v)
351
+ if err != nil {
352
+ return nil, err
353
+ }
354
+ switch i {
355
+ case clDCIdx:
356
+ ms = append(ms, clusterDCMatcher{m})
357
+ case clClusterIdx:
358
+ ms = append(ms, clusterNameMatcher{m})
359
+ default:
360
+ }
361
+ }
362
+
363
+ switch len(ms) {
364
+ case 0:
365
+ return nil, nil
366
+ case 1:
367
+ return ms[0], nil
368
+ default:
369
+ return andClusterMatcher{lhs: ms[0], rhs: ms[1]}, nil
370
+ }
371
+}
372
+
373
+const (
374
+ dsDatacenterIdx = iota
375
+ dsDatastoreIdx
376
+)
377
+
378
+func parseDatastoreInclude(include string) (DatastoreMatcher, error) {
379
+ if !isIncludeFormatValid(include) {
380
+ return nil, fmt.Errorf("bad include format: %s", include)
381
+ }
382
+
383
+ include = cleanInclude(include)
384
+ parts := strings.Split(include, "/") // /dc/datastore
385
+ var ms []DatastoreMatcher
386
+
387
+ for i, v := range parts {
388
+ m, err := parseSubInclude(v)
389
+ if err != nil {
390
+ return nil, err
391
+ }
392
+ switch i {
393
+ case dsDatacenterIdx:
394
+ ms = append(ms, dsDCMatcher{m})
395
+ case dsDatastoreIdx:
396
+ ms = append(ms, dsDSMatcher{m})
397
+ default:
398
+ }
399
+ }
400
+
401
+ switch len(ms) {
402
+ case 0:
403
+ return nil, nil
404
+ case 1:
405
+ return ms[0], nil
406
+ default:
407
+ return andDSMatcher{lhs: ms[0], rhs: ms[1]}, nil
408
+ }
409
+}
src/go/plugin/go.d/collector/vsphere/match/match_test.go
+224
@@ -17,6 +17,10 @@ var (
17
falseHostDC = hostDCMatcher{matcher.FALSE()}
18
trueVMDC = vmDCMatcher{matcher.TRUE()}
19
falseVMDC = vmDCMatcher{matcher.FALSE()}
20
+ trueDSDC = dsDCMatcher{matcher.TRUE()}
21
+ falseDSDC = dsDCMatcher{matcher.FALSE()}
22
+ trueClDC = clusterDCMatcher{matcher.TRUE()}
23
+ falseClDC = clusterDCMatcher{matcher.FALSE()}
24
)
25
26
func TestOrHostMatcher_Match(t *testing.T) {
@@ -277,6 +281,226 @@ func TestVMIncludes_Parse(t *testing.T) {
281
}
282
}
283
284
+func TestOrDSMatcher_Match(t *testing.T) {
285
+ tests := map[string]struct {
286
+ expected bool
287
+ lhs DatastoreMatcher
288
+ rhs DatastoreMatcher
289
+ }{
290
+ "true, true": {expected: true, lhs: trueDSDC, rhs: trueDSDC},
291
+ "true, false": {expected: true, lhs: trueDSDC, rhs: falseDSDC},
292
+ "false, true": {expected: true, lhs: falseDSDC, rhs: trueDSDC},
293
+ "false, false": {expected: false, lhs: falseDSDC, rhs: falseDSDC},
294
+ }
295
+
296
+ var ds resources.Datastore
297
+ for name, test := range tests {
298
+ t.Run(name, func(t *testing.T) {
299
+ m := orDSMatcher{lhs: test.lhs, rhs: test.rhs}
300
+ assert.Equal(t, test.expected, m.Match(&ds))
301
+ })
302
+ }
303
+}
304
+
305
+func TestAndDSMatcher_Match(t *testing.T) {
306
+ tests := map[string]struct {
307
+ expected bool
308
+ lhs DatastoreMatcher
309
+ rhs DatastoreMatcher
310
+ }{
311
+ "true, true": {expected: true, lhs: trueDSDC, rhs: trueDSDC},
312
+ "true, false": {expected: false, lhs: trueDSDC, rhs: falseDSDC},
313
+ "false, true": {expected: false, lhs: falseDSDC, rhs: trueDSDC},
314
+ "false, false": {expected: false, lhs: falseDSDC, rhs: falseDSDC},
315
+ }
316
+
317
+ var ds resources.Datastore
318
+ for name, test := range tests {
319
+ t.Run(name, func(t *testing.T) {
320
+ m := andDSMatcher{lhs: test.lhs, rhs: test.rhs}
321
+ assert.Equal(t, test.expected, m.Match(&ds))
322
+ })
323
+ }
324
+}
325
+
326
+func TestDatastoreIncludes_Parse(t *testing.T) {
327
+ tests := map[string]struct {
328
+ valid bool
329
+ expected DatastoreMatcher
330
+ }{
331
+ "": {valid: false},
332
+ "*/DS1": {valid: false},
333
+ "/": {valid: true, expected: falseDSDC},
334
+ "/*": {valid: true, expected: trueDSDC},
335
+ "/!*": {valid: true, expected: falseDSDC},
336
+ "/!*/": {valid: true, expected: falseDSDC},
337
+ "/!*/ ": {
338
+ valid: true,
339
+ expected: andDSMatcher{
340
+ lhs: falseDSDC,
341
+ rhs: dsDSMatcher{matcher.FALSE()},
342
+ },
343
+ },
344
+ "/DC1*/DS*": {
345
+ valid: true,
346
+ expected: andDSMatcher{
347
+ lhs: dsDCMatcher{mustSP("DC1*")},
348
+ rhs: dsDSMatcher{mustSP("DS*")},
349
+ },
350
+ },
351
+ "/*/*/extra": {
352
+ valid: true,
353
+ expected: andDSMatcher{
354
+ lhs: trueDSDC,
355
+ rhs: dsDSMatcher{matcher.TRUE()},
356
+ },
357
+ },
358
+ "[/DC1*,/DC2*]": {
359
+ valid: true,
360
+ expected: orDSMatcher{
361
+ lhs: dsDCMatcher{mustSP("DC1*")},
362
+ rhs: dsDCMatcher{mustSP("DC2*")},
363
+ },
364
+ },
365
+ "[/DC1*,/DC2*,/DC3*/DS*]": {
366
+ valid: true,
367
+ expected: orDSMatcher{
368
+ lhs: orDSMatcher{
369
+ lhs: dsDCMatcher{mustSP("DC1*")},
370
+ rhs: dsDCMatcher{mustSP("DC2*")},
371
+ },
372
+ rhs: andDSMatcher{
373
+ lhs: dsDCMatcher{mustSP("DC3*")},
374
+ rhs: dsDSMatcher{mustSP("DS*")},
375
+ },
376
+ },
377
+ },
378
+ }
379
+
380
+ for name, test := range tests {
381
+ t.Run(name, func(t *testing.T) {
382
+ includes := prepareIncludes(name)
383
+ m, err := DatastoreIncludes(includes).Parse()
384
+
385
+ if !test.valid {
386
+ assert.Error(t, err)
387
+ } else {
388
+ assert.Equal(t, test.expected, m)
389
+ }
390
+ })
391
+ }
392
+}
393
+
394
+func TestOrClusterMatcher_Match(t *testing.T) {
395
+ tests := map[string]struct {
396
+ expected bool
397
+ lhs ClusterMatcher
398
+ rhs ClusterMatcher
399
+ }{
400
+ "true, true": {expected: true, lhs: trueClDC, rhs: trueClDC},
401
+ "true, false": {expected: true, lhs: trueClDC, rhs: falseClDC},
402
+ "false, true": {expected: true, lhs: falseClDC, rhs: trueClDC},
403
+ "false, false": {expected: false, lhs: falseClDC, rhs: falseClDC},
404
+ }
405
+
406
+ var cl resources.Cluster
407
+ for name, test := range tests {
408
+ t.Run(name, func(t *testing.T) {
409
+ m := orClusterMatcher{lhs: test.lhs, rhs: test.rhs}
410
+ assert.Equal(t, test.expected, m.Match(&cl))
411
+ })
412
+ }
413
+}
414
+
415
+func TestAndClusterMatcher_Match(t *testing.T) {
416
+ tests := map[string]struct {
417
+ expected bool
418
+ lhs ClusterMatcher
419
+ rhs ClusterMatcher
420
+ }{
421
+ "true, true": {expected: true, lhs: trueClDC, rhs: trueClDC},
422
+ "true, false": {expected: false, lhs: trueClDC, rhs: falseClDC},
423
+ "false, true": {expected: false, lhs: falseClDC, rhs: trueClDC},
424
+ "false, false": {expected: false, lhs: falseClDC, rhs: falseClDC},
425
+ }
426
+
427
+ var cl resources.Cluster
428
+ for name, test := range tests {
429
+ t.Run(name, func(t *testing.T) {
430
+ m := andClusterMatcher{lhs: test.lhs, rhs: test.rhs}
431
+ assert.Equal(t, test.expected, m.Match(&cl))
432
+ })
433
+ }
434
+}
435
+
436
+func TestClusterIncludes_Parse(t *testing.T) {
437
+ tests := map[string]struct {
438
+ valid bool
439
+ expected ClusterMatcher
440
+ }{
441
+ "": {valid: false},
442
+ "*/C1": {valid: false},
443
+ "/": {valid: true, expected: falseClDC},
444
+ "/*": {valid: true, expected: trueClDC},
445
+ "/!*": {valid: true, expected: falseClDC},
446
+ "/!*/": {valid: true, expected: falseClDC},
447
+ "/!*/ ": {
448
+ valid: true,
449
+ expected: andClusterMatcher{
450
+ lhs: falseClDC,
451
+ rhs: clusterNameMatcher{matcher.FALSE()},
452
+ },
453
+ },
454
+ "/DC1*/Cluster*": {
455
+ valid: true,
456
+ expected: andClusterMatcher{
457
+ lhs: clusterDCMatcher{mustSP("DC1*")},
458
+ rhs: clusterNameMatcher{mustSP("Cluster*")},
459
+ },
460
+ },
461
+ "/*/*/extra": {
462
+ valid: true,
463
+ expected: andClusterMatcher{
464
+ lhs: trueClDC,
465
+ rhs: clusterNameMatcher{matcher.TRUE()},
466
+ },
467
+ },
468
+ "[/DC1*,/DC2*]": {
469
+ valid: true,
470
+ expected: orClusterMatcher{
471
+ lhs: clusterDCMatcher{mustSP("DC1*")},
472
+ rhs: clusterDCMatcher{mustSP("DC2*")},
473
+ },
474
+ },
475
+ "[/DC1*,/DC2*,/DC3*/Cluster*]": {
476
+ valid: true,
477
+ expected: orClusterMatcher{
478
+ lhs: orClusterMatcher{
479
+ lhs: clusterDCMatcher{mustSP("DC1*")},
480
+ rhs: clusterDCMatcher{mustSP("DC2*")},
481
+ },
482
+ rhs: andClusterMatcher{
483
+ lhs: clusterDCMatcher{mustSP("DC3*")},
484
+ rhs: clusterNameMatcher{mustSP("Cluster*")},
485
+ },
486
+ },
487
+ },
488
+ }
489
+
490
+ for name, test := range tests {
491
+ t.Run(name, func(t *testing.T) {
492
+ includes := prepareIncludes(name)
493
+ m, err := ClusterIncludes(includes).Parse()
494
+
495
+ if !test.valid {
496
+ assert.Error(t, err)
497
+ } else {
498
+ assert.Equal(t, test.expected, m)
499
+ }
500
+ })
501
+ }
502
+}
503
+
504
func prepareIncludes(include string) []string {
505
trimmed := strings.Trim(include, "[]")
506
return strings.Split(trimmed, ",")
src/go/plugin/go.d/collector/vsphere/metadata.yaml
+383
-6
@@ -22,7 +22,7 @@ modules:
22
overview:
23
data_collection:
24
metrics_description: |
25
- This collector monitors hosts and vms performance statistics from `vCenter` servers.
25
+ This collector monitors hosts, VMs, datastores, clusters, and resource pools from `vCenter` servers.
26
27
> **Warning**: The `vsphere` collector cannot re-login and continue collecting metrics after a vCenter reboot.
28
> go.d.plugin needs to be restarted.
@@ -43,6 +43,8 @@ modules:
43
The default `update_every` is 20 seconds, and it doesn't make sense to decrease the value.
44
**VMware real-time statistics are generated at the 20-second specificity**.
45
46
+ **Note**: Datastore and cluster performance metrics use 300-second (5-minute) historical intervals because VMware does not support real-time statistics for these entity types. Datastore capacity/status, cluster properties, and resource pool statistics are updated every collection cycle. Host and VM metrics use real-time 20-second intervals.
47
+
48
It is likely that 20 seconds is not enough for big installations and the value should be tuned.
49
50
To get a better view we recommend running the collector in debug mode and seeing how much time it will take to collect metrics.
@@ -96,7 +98,7 @@ modules:
98
list:
99
- name: update_every
100
description: Data collection interval (seconds).
99
- default_value: 1
101
+ default_value: 20
102
required: false
103
group: Collection
104
@@ -108,17 +110,17 @@ modules:
110
111
- name: url
112
description: Target endpoint URL.
111
- default_value: http://127.0.0.1/server-status?auto
113
+ default_value: https://vcenter.local
114
required: true
115
group: Target
116
- name: timeout
117
description: HTTP request timeout (seconds).
116
- default_value: 1
118
+ default_value: 20
119
required: false
120
group: Target
121
122
- name: discovery_interval
121
- description: Hosts and VMs discovery interval (seconds).
123
+ description: Hosts, VMs, datastores, clusters, and resource pools discovery interval (seconds).
124
default_value: 300
125
required: false
126
group: Discovery
@@ -148,7 +150,7 @@ modules:
150
group: Filters
151
detailed_description: |
152
Metrics of VMs matching the selector will be collected.
151
-
153
+
154
- Include pattern syntax: "/Datacenter pattern/Cluster pattern/Host pattern/VM pattern".
155
- Match pattern syntax: [simple patterns](/src/libnetdata/simple_pattern/README.md#simple-patterns).
156
- Syntax:
@@ -159,6 +161,40 @@ modules:
161
- '/DC2/*/*/!VM2 *' # all VMs from DC2 except VM2
162
- '/DC3/Cluster3/*' # all VMs from DC3, cluster Cluster3
163
```
164
+ - name: datastore_include
165
+ description: Datastore selector (filter).
166
+ default_value: "/*"
167
+ required: false
168
+ group: Filters
169
+ detailed_description: |
170
+ Metrics of datastores matching the selector will be collected.
171
+
172
+ - Include pattern syntax: "/Datacenter pattern/Datastore pattern".
173
+ - Match pattern syntax: [simple patterns](/src/libnetdata/simple_pattern/README.md#simple-patterns).
174
+ - Syntax:
175
+
176
+ ```yaml
177
+ datastore_include:
178
+ - '/DC1/*' # all datastores from datacenter DC1
179
+ - '/DC2/!DS2 *' # all datastores from DC2 except DS2
180
+ ```
181
+ - name: cluster_include
182
+ description: Cluster selector (filter). Resource pools follow their owning cluster.
183
+ default_value: "/*"
184
+ required: false
185
+ group: Filters
186
+ detailed_description: |
187
+ Metrics of clusters and their resource pools matching the selector will be collected.
188
+
189
+ - Include pattern syntax: "/Datacenter pattern/Cluster pattern".
190
+ - Match pattern syntax: [simple patterns](/src/libnetdata/simple_pattern/README.md#simple-patterns).
191
+ - Syntax:
192
+
193
+ ```yaml
194
+ cluster_include:
195
+ - '/DC1/*' # all clusters from datacenter DC1
196
+ - '/DC2/!Cluster2 *' # all clusters from DC2 except Cluster2
197
+ ```
198
199
- name: username
200
description: Username for Basic HTTP authentication.
@@ -492,3 +528,344 @@ modules:
528
chart_type: line
529
dimensions:
530
- name: uptime
531
+ - name: datastore
532
+ description: These metrics refer to the Datastore.
533
+ labels:
534
+ - name: datacenter
535
+ description: Datacenter name
536
+ - name: datastore
537
+ description: Datastore name
538
+ - name: type
539
+ description: "Datastore type (VMFS, NFS, NFS41, vsan, VVOL, PMEM)"
540
+ metrics:
541
+ - name: vsphere.datastore_disk_io
542
+ description: Datastore disk IO
543
+ unit: KiB/s
544
+ chart_type: area
545
+ dimensions:
546
+ - name: read
547
+ - name: write
548
+ - name: vsphere.datastore_disk_iops
549
+ description: Datastore disk IOPS
550
+ unit: operations/s
551
+ chart_type: line
552
+ dimensions:
553
+ - name: reads
554
+ - name: writes
555
+ - name: vsphere.datastore_disk_latency
556
+ description: Datastore disk latency
557
+ unit: milliseconds
558
+ chart_type: line
559
+ dimensions:
560
+ - name: read
561
+ - name: write
562
+ - name: vsphere.datastore_space_utilization
563
+ description: Datastore space utilization
564
+ unit: percentage
565
+ chart_type: line
566
+ dimensions:
567
+ - name: used
568
+ - name: vsphere.datastore_space_usage
569
+ description: Datastore space usage
570
+ unit: bytes
571
+ chart_type: line
572
+ dimensions:
573
+ - name: capacity
574
+ - name: free
575
+ - name: used
576
+ - name: vsphere.datastore_overall_status
577
+ description: Datastore overall alarm status
578
+ unit: status
579
+ chart_type: line
580
+ dimensions:
581
+ - name: green
582
+ - name: red
583
+ - name: yellow
584
+ - name: gray
585
+ - name: cluster
586
+ description: These metrics refer to the vSphere Cluster.
587
+ labels:
588
+ - name: datacenter
589
+ description: Datacenter name
590
+ - name: cluster
591
+ description: Cluster name
592
+ metrics:
593
+ - name: vsphere.cluster_hosts
594
+ description: Cluster host count
595
+ unit: hosts
596
+ chart_type: line
597
+ dimensions:
598
+ - name: total
599
+ - name: effective
600
+ - name: vsphere.cluster_cpu_capacity
601
+ description: Cluster CPU capacity
602
+ unit: MHz
603
+ chart_type: line
604
+ dimensions:
605
+ - name: total
606
+ - name: effective
607
+ - name: vsphere.cluster_mem_capacity
608
+ description: Cluster memory capacity
609
+ unit: bytes
610
+ chart_type: line
611
+ dimensions:
612
+ - name: total
613
+ - name: effective
614
+ - name: vsphere.cluster_cpu_topology
615
+ description: Cluster CPU topology
616
+ unit: count
617
+ chart_type: line
618
+ dimensions:
619
+ - name: cores
620
+ - name: threads
621
+ - name: vsphere.cluster_drs_config
622
+ description: Cluster DRS enabled
623
+ unit: status
624
+ chart_type: line
625
+ dimensions:
626
+ - name: enabled
627
+ - name: vsphere.cluster_ha_config
628
+ description: Cluster HA configuration
629
+ unit: status
630
+ chart_type: line
631
+ dimensions:
632
+ - name: enabled
633
+ - name: admission_control
634
+ - name: vsphere.cluster_overall_status
635
+ description: Cluster overall alarm status
636
+ unit: status
637
+ chart_type: line
638
+ dimensions:
639
+ - name: green
640
+ - name: red
641
+ - name: yellow
642
+ - name: gray
643
+ - name: vsphere.cluster_vmotions
644
+ description: Cluster cumulative vMotion count
645
+ unit: migrations
646
+ chart_type: line
647
+ dimensions:
648
+ - name: vmotions
649
+ - name: vsphere.cluster_drs_score
650
+ description: Cluster DRS score
651
+ unit: percentage
652
+ chart_type: line
653
+ dimensions:
654
+ - name: score
655
+ - name: vsphere.cluster_drs_balance
656
+ description: Cluster DRS load balance
657
+ unit: score
658
+ chart_type: line
659
+ dimensions:
660
+ - name: current
661
+ - name: target
662
+ - name: vsphere.cluster_vm_count
663
+ description: Cluster VM count
664
+ unit: VMs
665
+ chart_type: line
666
+ dimensions:
667
+ - name: total
668
+ - name: powered_off
669
+ - name: vsphere.cluster_usage_cpu
670
+ description: Cluster DRS CPU usage summary
671
+ unit: MHz
672
+ chart_type: line
673
+ dimensions:
674
+ - name: demand
675
+ - name: entitled
676
+ - name: reserved
677
+ - name: vsphere.cluster_usage_mem
678
+ description: Cluster DRS memory usage summary
679
+ unit: MB
680
+ chart_type: line
681
+ dimensions:
682
+ - name: demand
683
+ - name: entitled
684
+ - name: reserved
685
+ - name: vsphere.cluster_cpu_utilization
686
+ description: Cluster CPU utilization
687
+ unit: percentage
688
+ chart_type: line
689
+ dimensions:
690
+ - name: used
691
+ - name: vsphere.cluster_cpu_usage
692
+ description: Cluster CPU usage
693
+ unit: MHz
694
+ chart_type: line
695
+ dimensions:
696
+ - name: used
697
+ - name: total
698
+ - name: vsphere.cluster_mem_utilization
699
+ description: Cluster memory utilization
700
+ unit: percentage
701
+ chart_type: line
702
+ dimensions:
703
+ - name: used
704
+ - name: vsphere.cluster_mem_usage
705
+ description: Cluster memory usage
706
+ unit: KiB
707
+ chart_type: line
708
+ dimensions:
709
+ - name: consumed
710
+ - name: active
711
+ - name: granted
712
+ - name: shared
713
+ - name: overhead
714
+ - name: swap_used
715
+ - name: vsphere.cluster_services_fairness
716
+ description: Cluster DRS resource distribution fairness
717
+ unit: score
718
+ chart_type: line
719
+ dimensions:
720
+ - name: cpu
721
+ - name: memory
722
+ - name: vsphere.cluster_services_effective_cpu
723
+ description: Cluster effective CPU capacity
724
+ unit: MHz
725
+ chart_type: line
726
+ dimensions:
727
+ - name: effective_cpu
728
+ - name: vsphere.cluster_services_effective_mem
729
+ description: Cluster effective memory capacity
730
+ unit: MB
731
+ chart_type: line
732
+ dimensions:
733
+ - name: effective_mem
734
+ - name: vsphere.cluster_services_failover
735
+ description: Cluster HA failover capacity
736
+ unit: failures
737
+ chart_type: line
738
+ dimensions:
739
+ - name: failures_tolerable
740
+ - name: vsphere.cluster_vm_migrations
741
+ description: Cluster VM migration operations
742
+ unit: operations
743
+ chart_type: line
744
+ dimensions:
745
+ - name: vmotion
746
+ - name: svmotion
747
+ - name: xvmotion
748
+ - name: vsphere.cluster_vm_lifecycle
749
+ description: Cluster VM lifecycle operations
750
+ unit: operations
751
+ chart_type: line
752
+ dimensions:
753
+ - name: poweron
754
+ - name: poweroff
755
+ - name: create
756
+ - name: destroy
757
+ - name: clone
758
+ - name: deploy
759
+ - name: vsphere.cluster_vm_management
760
+ description: Cluster VM management operations
761
+ unit: operations
762
+ chart_type: line
763
+ dimensions:
764
+ - name: reconfigure
765
+ - name: reset
766
+ - name: suspend
767
+ - name: register
768
+ - name: unregister
769
+ - name: vsphere.cluster_vm_guest_ops
770
+ description: Cluster VM guest operations
771
+ unit: operations
772
+ chart_type: line
773
+ dimensions:
774
+ - name: reboot
775
+ - name: shutdown
776
+ - name: standby
777
+ - name: vsphere.cluster_vm_cold_migrations
778
+ description: Cluster VM cold migration operations
779
+ unit: operations
780
+ chart_type: line
781
+ dimensions:
782
+ - name: change_ds
783
+ - name: change_host
784
+ - name: change_host_ds
785
+ - name: resource pool
786
+ description: These metrics refer to the vSphere Resource Pool.
787
+ labels:
788
+ - name: datacenter
789
+ description: Datacenter name
790
+ - name: cluster
791
+ description: Cluster name
792
+ - name: resource_pool
793
+ description: Resource Pool name
794
+ metrics:
795
+ - name: vsphere.resource_pool_cpu_usage
796
+ description: Resource Pool CPU usage vs demand
797
+ unit: MHz
798
+ chart_type: line
799
+ dimensions:
800
+ - name: usage
801
+ - name: demand
802
+ - name: vsphere.resource_pool_cpu_entitlement
803
+ description: Resource Pool CPU entitlement
804
+ unit: MHz
805
+ chart_type: line
806
+ dimensions:
807
+ - name: distributed
808
+ - name: vsphere.resource_pool_cpu_allocation
809
+ description: Resource Pool CPU allocation
810
+ unit: MHz
811
+ chart_type: line
812
+ dimensions:
813
+ - name: reservation_used
814
+ - name: unreserved_for_vm
815
+ - name: max_usage
816
+ - name: vsphere.resource_pool_mem_usage
817
+ description: Resource Pool memory usage
818
+ unit: MB
819
+ chart_type: line
820
+ dimensions:
821
+ - name: host
822
+ - name: guest
823
+ - name: vsphere.resource_pool_mem_entitlement
824
+ description: Resource Pool memory entitlement
825
+ unit: MB
826
+ chart_type: line
827
+ dimensions:
828
+ - name: distributed
829
+ - name: vsphere.resource_pool_mem_allocation
830
+ description: Resource Pool memory allocation
831
+ unit: bytes
832
+ chart_type: line
833
+ dimensions:
834
+ - name: reservation_used
835
+ - name: unreserved_for_vm
836
+ - name: max_usage
837
+ - name: vsphere.resource_pool_mem_breakdown
838
+ description: Resource Pool memory state breakdown
839
+ unit: MB
840
+ chart_type: line
841
+ dimensions:
842
+ - name: private
843
+ - name: shared
844
+ - name: swapped
845
+ - name: ballooned
846
+ - name: overhead
847
+ - name: consumed_overhead
848
+ - name: compressed
849
+ - name: vsphere.resource_pool_cpu_config
850
+ description: Resource Pool CPU configured reservation and limit
851
+ unit: MHz
852
+ chart_type: line
853
+ dimensions:
854
+ - name: reservation
855
+ - name: limit
856
+ - name: vsphere.resource_pool_mem_config
857
+ description: Resource Pool memory configured reservation and limit
858
+ unit: MB
859
+ chart_type: line
860
+ dimensions:
861
+ - name: reservation
862
+ - name: limit
863
+ - name: vsphere.resource_pool_overall_status
864
+ description: Resource Pool overall alarm status
865
+ unit: status
866
+ chart_type: line
867
+ dimensions:
868
+ - name: green
869
+ - name: red
870
+ - name: yellow
871
+ - name: gray
src/go/plugin/go.d/collector/vsphere/resources/resources.go
+124
-27
@@ -44,11 +44,13 @@ Virtual Datacenter Architecture Representation (partial).
44
*/
45
46
type Resources struct {
47
- DataCenters DataCenters
48
- Folders Folders
49
- Clusters Clusters
50
- Hosts Hosts
51
- VMs VMs
47
+ DataCenters DataCenters
48
+ Folders Folders
49
+ Clusters Clusters
50
+ Hosts Hosts
51
+ VMs VMs
52
+ Datastores Datastores
53
+ ResourcePools ResourcePools
54
}
55
56
type (
@@ -71,10 +73,77 @@ type (
73
DC HierarchyValue
74
}
75
Cluster struct {
76
+ Name string
77
+ ID string
78
+ ParentID string
79
+ Hier ClusterHierarchy
80
+ OverallStatus string
81
+ NumHosts int32
82
+ NumEffectiveHosts int32
83
+ TotalCpu int32 // MHz
84
+ TotalMemory int64 // bytes
85
+ EffectiveCpu int32 // MHz
86
+ EffectiveMemory int64 // MB
87
+ NumCpuCores int16
88
+ NumCpuThreads int16
89
+ NumVmotions int32 // cumulative count
90
+ DrsEnabled bool
91
+ DrsMode string // fullyAutomated/partiallyAutomated/manual
92
+ DrsScore int32 // 0-100, vSphere 7.0+, 0 if unavailable
93
+ CurrentBalance int32 // thousandths of std dev
94
+ TargetBalance int32 // thousandths of std dev
95
+ HaEnabled bool
96
+ HaAdmCtrlEnabled bool
97
+ // UsageSummary fields (nil when DRS disabled)
98
+ UsageCpuDemandMhz int32
99
+ UsageMemDemandMB int32
100
+ UsageCpuEntitledMhz int32
101
+ UsageMemEntitledMB int32
102
+ UsageCpuReservationMhz int32
103
+ UsageMemReservationMB int32
104
+ UsageTotalVmCount int32
105
+ UsagePoweredOffVmCount int32
106
+ MetricList performance.MetricList
107
+ Ref types.ManagedObjectReference
108
+ }
109
+
110
+ ResourcePoolHierarchy struct {
111
+ DC HierarchyValue
112
+ Cluster HierarchyValue
113
+ }
114
+ ResourcePool struct {
115
Name string
116
ID string
76
- ParentID string
77
- Hier ClusterHierarchy
117
+ ParentID string // owner cluster ref value
118
+ Hier ResourcePoolHierarchy
119
+ // QuickStats (polled via PropertyCollector)
120
+ OverallCpuUsage int64 // MHz
121
+ OverallCpuDemand int64 // MHz
122
+ GuestMemoryUsage int64 // MB
123
+ HostMemoryUsage int64 // MB
124
+ DistributedCpuEntitlement int64 // MHz
125
+ DistributedMemoryEntitlement int64 // MB
126
+ PrivateMemory int64 // MB
127
+ SharedMemory int64 // MB
128
+ SwappedMemory int64 // MB
129
+ BalloonedMemory int64 // MB
130
+ OverheadMemory int64 // MB
131
+ ConsumedOverheadMemory int64 // MB
132
+ CompressedMemory int64 // KB
133
+ // Runtime
134
+ CpuReservationUsed int64 // MHz
135
+ CpuMaxUsage int64 // MHz
136
+ CpuUnreservedForVm int64 // MHz
137
+ MemReservationUsed int64 // bytes
138
+ MemMaxUsage int64 // bytes
139
+ MemUnreservedForVm int64 // bytes
140
+ // Config
141
+ CpuReservation int64 // MHz, 0 if nil
142
+ CpuLimit int64 // MHz, -1 = unlimited
143
+ MemReservation int64 // MB, 0 if nil
144
+ MemLimit int64 // MB, -1 = unlimited
145
+ OverallStatus string
146
+ Ref types.ManagedObjectReference
147
}
148
149
HostHierarchy struct {
@@ -106,32 +175,60 @@ type (
175
MetricList performance.MetricList
176
Ref types.ManagedObjectReference
177
}
178
+
179
+ DatastoreHierarchy struct {
180
+ DC HierarchyValue
181
+ }
182
+ Datastore struct {
183
+ Name string
184
+ ID string
185
+ ParentID string
186
+ Hier DatastoreHierarchy
187
+ OverallStatus string
188
+ Type string // VMFS, NFS, NFS41, vsan, VVOL, PMEM
189
+ Capacity int64 // bytes
190
+ FreeSpace int64 // bytes
191
+ Accessible bool
192
+ MetricList performance.MetricList
193
+ Ref types.ManagedObjectReference
194
+ }
195
)
196
197
func (v *HierarchyValue) IsSet() bool { return v.ID != "" && v.Name != "" }
198
func (v *HierarchyValue) Set(id, name string) { v.ID = id; v.Name = name }
199
114
-func (h ClusterHierarchy) IsSet() bool { return h.DC.IsSet() }
115
-func (h HostHierarchy) IsSet() bool { return h.DC.IsSet() && h.Cluster.IsSet() }
116
-func (h VMHierarchy) IsSet() bool { return h.DC.IsSet() && h.Cluster.IsSet() && h.Host.IsSet() }
200
+func (h ClusterHierarchy) IsSet() bool { return h.DC.IsSet() }
201
+func (h HostHierarchy) IsSet() bool { return h.DC.IsSet() && h.Cluster.IsSet() }
202
+func (h VMHierarchy) IsSet() bool { return h.DC.IsSet() && h.Cluster.IsSet() && h.Host.IsSet() }
203
+func (h DatastoreHierarchy) IsSet() bool { return h.DC.IsSet() }
204
+func (h ResourcePoolHierarchy) IsSet() bool { return h.DC.IsSet() && h.Cluster.IsSet() }
205
206
type (
119
- DataCenters map[string]*Datacenter
120
- Folders map[string]*Folder
121
- Clusters map[string]*Cluster
122
- Hosts map[string]*Host
123
- VMs map[string]*VM
207
+ DataCenters map[string]*Datacenter
208
+ Folders map[string]*Folder
209
+ Clusters map[string]*Cluster
210
+ Hosts map[string]*Host
211
+ VMs map[string]*VM
212
+ Datastores map[string]*Datastore
213
+ ResourcePools map[string]*ResourcePool
214
)
215
126
-func (dcs DataCenters) Put(dc *Datacenter) { dcs[dc.ID] = dc }
127
-func (dcs DataCenters) Get(id string) *Datacenter { return dcs[id] }
128
-func (fs Folders) Put(folder *Folder) { fs[folder.ID] = folder }
129
-func (fs Folders) Get(id string) *Folder { return fs[id] }
130
-func (cs Clusters) Put(cluster *Cluster) { cs[cluster.ID] = cluster }
131
-func (cs Clusters) Get(id string) *Cluster { return cs[id] }
132
-func (hs Hosts) Put(host *Host) { hs[host.ID] = host }
133
-func (hs Hosts) Remove(id string) { delete(hs, id) }
134
-func (hs Hosts) Get(id string) *Host { return hs[id] }
135
-func (vs VMs) Put(vm *VM) { vs[vm.ID] = vm }
136
-func (vs VMs) Remove(id string) { delete(vs, id) }
137
-func (vs VMs) Get(id string) *VM { return vs[id] }
216
+func (dcs DataCenters) Put(dc *Datacenter) { dcs[dc.ID] = dc }
217
+func (dcs DataCenters) Get(id string) *Datacenter { return dcs[id] }
218
+func (fs Folders) Put(folder *Folder) { fs[folder.ID] = folder }
219
+func (fs Folders) Get(id string) *Folder { return fs[id] }
220
+func (cs Clusters) Put(cluster *Cluster) { cs[cluster.ID] = cluster }
221
+func (cs Clusters) Remove(id string) { delete(cs, id) }
222
+func (cs Clusters) Get(id string) *Cluster { return cs[id] }
223
+func (hs Hosts) Put(host *Host) { hs[host.ID] = host }
224
+func (hs Hosts) Remove(id string) { delete(hs, id) }
225
+func (hs Hosts) Get(id string) *Host { return hs[id] }
226
+func (vs VMs) Put(vm *VM) { vs[vm.ID] = vm }
227
+func (vs VMs) Remove(id string) { delete(vs, id) }
228
+func (vs VMs) Get(id string) *VM { return vs[id] }
229
+func (ds Datastores) Put(d *Datastore) { ds[d.ID] = d }
230
+func (ds Datastores) Remove(id string) { delete(ds, id) }
231
+func (ds Datastores) Get(id string) *Datastore { return ds[id] }
232
+func (rp ResourcePools) Put(p *ResourcePool) { rp[p.ID] = p }
233
+func (rp ResourcePools) Remove(id string) { delete(rp, id) }
234
+func (rp ResourcePools) Get(id string) *ResourcePool { return rp[id] }
src/go/plugin/go.d/collector/vsphere/scrape/scrape.go
+64
@@ -67,6 +67,30 @@ func (s *Scraper) ScrapeVMs(vms rs.VMs) []performance.EntityMetric {
67
return ms
68
}
69
70
+func (s *Scraper) ScrapeDatastores(datastores rs.Datastores) []performance.EntityMetric {
71
+ t := time.Now()
72
+ pqs := newDatastoresPerfQuerySpecs(datastores)
73
+ ms := s.scrapeMetrics(pqs)
74
+ s.Debugf("scraping : scraped metrics for %d/%d datastores, process took %s",
75
+ len(ms),
76
+ len(datastores),
77
+ time.Since(t),
78
+ )
79
+ return ms
80
+}
81
+
82
+func (s *Scraper) ScrapeClusters(clusters rs.Clusters) []performance.EntityMetric {
83
+ t := time.Now()
84
+ pqs := newClustersPerfQuerySpecs(clusters)
85
+ ms := s.scrapeMetrics(pqs)
86
+ s.Debugf("scraping : scraped metrics for %d/%d clusters, process took %s",
87
+ len(ms),
88
+ len(clusters),
89
+ time.Since(t),
90
+ )
91
+ return ms
92
+}
93
+
94
func (s *Scraper) scrapeMetrics(pqs []types.PerfQuerySpec) []performance.EntityMetric {
95
tc := newThrottledCaller(5)
96
var ms []performance.EntityMetric
@@ -144,6 +168,46 @@ func newVMsPerfQuerySpecs(vms rs.VMs) []types.PerfQuerySpec {
168
return pqs
169
}
170
171
+// Datastores, clusters, and resource pools do not support real-time (20s) collection.
172
+// Minimum supported interval is 300s (5 min historical).
173
+const pqsHistoricalIntervalID = 300
174
+
175
+func newDatastoresPerfQuerySpecs(datastores rs.Datastores) []types.PerfQuerySpec {
176
+ pqs := make([]types.PerfQuerySpec, 0, len(datastores))
177
+ for _, ds := range datastores {
178
+ if len(ds.MetricList) == 0 {
179
+ continue
180
+ }
181
+ pq := types.PerfQuerySpec{
182
+ Entity: ds.Ref,
183
+ MaxSample: pqsMaxSample,
184
+ MetricId: ds.MetricList,
185
+ IntervalId: pqsHistoricalIntervalID,
186
+ Format: pqsFormat,
187
+ }
188
+ pqs = append(pqs, pq)
189
+ }
190
+ return pqs
191
+}
192
+
193
+func newClustersPerfQuerySpecs(clusters rs.Clusters) []types.PerfQuerySpec {
194
+ pqs := make([]types.PerfQuerySpec, 0, len(clusters))
195
+ for _, c := range clusters {
196
+ if len(c.MetricList) == 0 {
197
+ continue
198
+ }
199
+ pq := types.PerfQuerySpec{
200
+ Entity: c.Ref,
201
+ MaxSample: pqsMaxSample,
202
+ MetricId: c.MetricList,
203
+ IntervalId: pqsHistoricalIntervalID,
204
+ Format: pqsFormat,
205
+ }
206
+ pqs = append(pqs, pq)
207
+ }
208
+ return pqs
209
+}
210
+
211
func parseVersion(version string) (major, minor int, err error) {
212
parts := strings.Split(version, ".")
213
if len(parts) < 2 {
src/go/plugin/go.d/collector/vsphere/testdata/config.json
+6
@@ -27,5 +27,11 @@
27
],
28
"vm_include": [
29
"ok"
30
+ ],
31
+ "datastore_include": [
32
+ "ok"
33
+ ],
34
+ "cluster_include": [
35
+ "ok"
36
]
37
}
src/go/plugin/go.d/collector/vsphere/testdata/config.yaml
+4
@@ -24,3 +24,7 @@ host_include:
24
- "ok"
25
vm_include:
26
- "ok"
27
+datastore_include:
28
+ - "ok"
29
+cluster_include:
30
+ - "ok"