@cryptotaxi247 / netdata-1 / commits / d2d04ab8d

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"