bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/scollector/collectors/vsphere.go (about) 1 package collectors 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/url" 8 "time" 9 10 "bosun.org/slog" 11 12 "github.com/vmware/govmomi/view" 13 14 "bosun.org/metadata" 15 "bosun.org/opentsdb" 16 "bosun.org/util" 17 "github.com/vmware/govmomi" 18 "github.com/vmware/govmomi/vim25/mo" 19 ) 20 21 // Vsphere registers a vSphere collector. 22 func Vsphere(user, pwd, host string) error { 23 if host == "" || user == "" || pwd == "" { 24 return fmt.Errorf("empty Host, User, or Password in Vsphere") 25 } 26 cpuIntegrators := make(map[string]tsIntegrator) 27 collectors = append(collectors, &IntervalCollector{ 28 F: func() (opentsdb.MultiDataPoint, error) { 29 return c_vsphere(user, pwd, host, cpuIntegrators) 30 }, 31 name: fmt.Sprintf("vsphere-%s", host), 32 }) 33 return nil 34 } 35 36 func c_vsphere(user, pwd, vHost string, cpuIntegrators map[string]tsIntegrator) (opentsdb.MultiDataPoint, error) { 37 var md opentsdb.MultiDataPoint 38 39 ctx := context.Background() 40 41 // Make a client 42 client, err := govmomi.NewClient(ctx, &url.URL{Scheme: "https", Host: vHost, Path: "/sdk"}, true) 43 if err != nil { 44 return nil, err 45 } 46 47 // Login with the client 48 err = client.Login(ctx, url.UserPassword(user, pwd)) 49 if err != nil { 50 return nil, err 51 } 52 53 // Get data about Host Systems (Hypervisors) 54 hostSystems, err := hostSystemData(ctx, client) 55 if err != nil { 56 return md, nil 57 } 58 59 // A map of Keys to the Host name, so VirtualMachine.Runtime.Host can be identified 60 hostKeys := make(map[string]string) 61 62 // Data for Hosts (Hypervisors) 63 for _, host := range hostSystems { 64 name, err := util.GetHostManager().GetNameProcessor().FormatName(host.Name) 65 if err != nil { 66 slog.Errorf("Skipping vSphere hypervisor with an unformattable name of '%s': %v", host.Name, err) 67 continue 68 } 69 70 hostKeys[host.Self.Value] = name 71 tags := opentsdb.TagSet{"host": name} 72 73 // Memory 74 memTotal := host.Summary.Hardware.MemorySize 75 Add(&md, osMemTotal, memTotal, tags, metadata.Gauge, metadata.Bytes, osMemTotalDesc) 76 memUsed := int64(host.Summary.QuickStats.OverallMemoryUsage) 77 memUsed = memUsed * 1024 * 1024 // MegaBytes to Bytes 78 Add(&md, osMemUsed, memUsed, tags, metadata.Gauge, metadata.Bytes, osMemUsedDesc) 79 if memTotal > 0 && memUsed > 0 { 80 memFree := memTotal - memUsed 81 Add(&md, osMemFree, memFree, tags, metadata.Gauge, metadata.Bytes, osMemFreeDesc) 82 Add(&md, osMemPctFree, float64(memFree)/float64(memTotal)*100, tags, metadata.Gauge, metadata.Pct, osMemPctFreeDesc) 83 } 84 85 // CPU 86 cpuUse := int64(host.Summary.QuickStats.OverallCpuUsage) 87 Add(&md, "vsphere.cpu", cpuUse, opentsdb.TagSet{"host": name, "type": "usage"}, metadata.Gauge, metadata.MHz, "") 88 cpuMhz := int64(host.Summary.Hardware.CpuMhz) 89 cpuCores := int64(host.Summary.Hardware.NumCpuCores) 90 if cpuMhz > 0 && cpuUse > 0 && cpuCores > 0 { 91 cpuTotal := cpuMhz * cpuCores 92 Add(&md, "vsphere.cpu", cpuTotal-cpuUse, opentsdb.TagSet{"host": name, "type": "idle"}, metadata.Gauge, metadata.MHz, "") 93 pct := float64(cpuUse) / float64(cpuTotal) * 100 94 Add(&md, "vsphere.cpu.pct", pct, tags, metadata.Gauge, metadata.Pct, "") 95 if _, ok := cpuIntegrators[name]; !ok { 96 cpuIntegrators[name] = getTsIntegrator() 97 } 98 Add(&md, osCPU, cpuIntegrators[name](time.Now().Unix(), pct), tags, metadata.Counter, metadata.Pct, "") 99 } 100 101 // Uptime 102 Add(&md, osSystemUptime, host.Summary.QuickStats.Uptime, tags, metadata.Gauge, metadata.Second, osSystemUptimeDesc) 103 104 // Hardware Information 105 metadata.AddMeta("", tags, "model", host.Summary.Hardware.Model, false) 106 var lastServiceTag string 107 for _, x := range host.Summary.Hardware.OtherIdentifyingInfo { 108 if x.IdentifierType.GetElementDescription().Key == "ServiceTag" { 109 lastServiceTag = x.IdentifierValue 110 } 111 } 112 if lastServiceTag != "" { 113 metadata.AddMeta("", tags, "serialNumber", lastServiceTag, false) 114 } 115 116 } 117 118 // Get information for Virtual Machines 119 vms, err := vmData(ctx, client) 120 if err != nil { 121 return md, nil 122 } 123 124 // Data for Virtual Machines 125 for _, vm := range vms { 126 name, err := util.GetHostManager().GetNameProcessor().FormatName(vm.Name) 127 if err != nil { 128 slog.Errorf("Skipping vSphere VM with an unformattable name of '%s': %v", vm.Name, err) 129 continue 130 } 131 132 tags := opentsdb.TagSet{"host": vHost, "guest": name} 133 134 // Identify VM Host (Hypervisor) 135 if v, ok := hostKeys[vm.Runtime.Host.Value]; ok { 136 metadata.AddMeta("", opentsdb.TagSet{"host": name}, "hypervisor", v, false) 137 } 138 139 // Memory 140 memTotal := int64(vm.Summary.Config.MemorySizeMB) * 1024 * 1024 141 Add(&md, "vsphere.guest.mem.total", memTotal, tags, metadata.Gauge, metadata.Bytes, "") 142 Add(&md, "vsphere.guest.mem.host", int64(vm.Summary.QuickStats.HostMemoryUsage)*1024*1024, tags, metadata.Gauge, metadata.Bytes, descVsphereGuestMemHost) 143 memUsed := int64(vm.Summary.QuickStats.HostMemoryUsage) * 1024 * 1024 144 Add(&md, "vsphere.guest.mem.used", memUsed, tags, metadata.Gauge, metadata.Bytes, descVsphereGuestMemUsed) 145 Add(&md, "vsphere.guest.mem.ballooned", int64(vm.Summary.QuickStats.BalloonedMemory)*1024*1024, tags, metadata.Gauge, metadata.Bytes, descVsphereGuestMemBallooned) 146 if memTotal > 0 && memUsed > 0 { 147 memFree := memTotal - memUsed 148 Add(&md, "vsphere.guest.mem.free", memFree, tags, metadata.Gauge, metadata.Bytes, "") 149 Add(&md, "vsphere.guest.mem.percent_free", float64(memFree)/float64(memTotal)*100, tags, metadata.Gauge, metadata.Pct, "") 150 } 151 152 // CPU 153 Add(&md, "vsphere.guest.cpu", vm.Summary.QuickStats.OverallCpuUsage, tags, metadata.Gauge, metadata.MHz, "") 154 155 // Power State 156 var pState int 157 var missing bool 158 switch vm.Runtime.PowerState { 159 case "poweredOn": 160 pState = 0 161 case "poweredOff": 162 pState = 1 163 case "suspended": 164 pState = 2 165 default: 166 missing = true 167 slog.Errorf("did not recognize %s as a valid value for vsphere.guest.powered_state", vm.Runtime.PowerState) 168 } 169 if !missing { 170 Add(&md, "vsphere.guest.powered_state", pState, tags, metadata.Gauge, metadata.StatusCode, descVsphereGuestPoweredState) 171 } 172 173 // Connection State 174 missing = false 175 var cState int 176 switch vm.Runtime.ConnectionState { 177 case "connected": 178 cState = 0 179 case "disconnected": 180 cState = 1 181 case "inaccessible": 182 cState = 2 183 case "invalid": 184 cState = 3 185 case "orphaned": 186 cState = 4 187 default: 188 missing = true 189 slog.Errorf("did not recognize %s as a valid value for vsphere.guest.connection_state", vm.Runtime.ConnectionState) 190 } 191 if !missing { 192 Add(&md, "vsphere.guest.connection_state", cState, tags, metadata.Gauge, metadata.StatusCode, descVsphereGuestConnectionState) 193 } 194 } 195 // Get information for Data Stores 196 197 // host to mounted data stores 198 hostStores := make(map[string][]string) 199 200 dataStores, err := vmDataStoreData(ctx, client) 201 if err != nil { 202 return md, nil 203 } 204 205 for _, ds := range dataStores { 206 name, err := util.GetHostManager().GetNameProcessor().FormatName(ds.Name) 207 if err != nil { 208 slog.Errorf("Skipping vSphere data source with an unformattable name of '%s': %v", ds.Name, err) 209 continue 210 } 211 212 tags := opentsdb.TagSet{ 213 "disk": name, 214 "host": "", 215 } 216 217 // Diskspace 218 diskTotal := ds.Summary.Capacity 219 Add(&md, osDiskTotal, diskTotal, tags, metadata.Gauge, metadata.Bytes, "") 220 Add(&md, "vsphere.disk.space_total", diskTotal, tags, metadata.Gauge, metadata.Bytes, "") 221 diskFree := ds.Summary.FreeSpace 222 Add(&md, "vsphere.disk.space_free", diskFree, tags, metadata.Gauge, metadata.Bytes, "") 223 if diskTotal > 0 && diskFree > 0 { 224 diskUsed := diskTotal - diskFree 225 Add(&md, "vsphere.disk.space_used", diskUsed, tags, metadata.Gauge, metadata.Bytes, "") 226 Add(&md, osDiskUsed, diskUsed, tags, metadata.Gauge, metadata.Bytes, "") 227 Add(&md, osDiskPctFree, float64(diskFree)/float64(diskTotal)*100, tags, metadata.Gauge, metadata.Pct, "") 228 } 229 230 for _, hostMount := range ds.Host { 231 if host, ok := hostKeys[hostMount.Key.Value]; ok { 232 if *hostMount.MountInfo.Mounted && *hostMount.MountInfo.Accessible { 233 hostStores[host] = append(hostStores[host], name) 234 } 235 } 236 } 237 238 for host, stores := range hostStores { 239 j, err := json.Marshal(stores) 240 if err != nil { 241 slog.Errorf("error marshaling datastores for host %v: %v", host, err) 242 } 243 metadata.AddMeta("", opentsdb.TagSet{"host": host}, "dataStores", string(j), false) 244 } 245 246 } 247 248 return md, nil 249 250 } 251 252 // hostSystemData uses the client to get the 'name' and 'summary' sections of the HostSystem Type 253 func hostSystemData(ctx context.Context, client *govmomi.Client) ([]mo.HostSystem, error) { 254 m := view.NewManager(client.Client) 255 hostSystems := []mo.HostSystem{} 256 view, err := m.CreateContainerView(ctx, client.ServiceContent.RootFolder, []string{"HostSystem"}, true) 257 if err != nil { 258 return hostSystems, err 259 } 260 261 defer view.Destroy(ctx) 262 263 err = view.Retrieve(ctx, []string{"HostSystem"}, []string{"name", "summary"}, &hostSystems) 264 if err != nil { 265 return hostSystems, err 266 } 267 return hostSystems, nil 268 } 269 270 // vmData uses the client to get the 'name', 'summary', and 'runtime' sections of the VirtualMachine Type 271 func vmData(ctx context.Context, client *govmomi.Client) ([]mo.VirtualMachine, error) { 272 m := view.NewManager(client.Client) 273 vms := []mo.VirtualMachine{} 274 view, err := m.CreateContainerView(ctx, client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true) 275 if err != nil { 276 return vms, err 277 } 278 279 defer view.Destroy(ctx) 280 281 err = view.Retrieve(ctx, []string{"VirtualMachine"}, []string{"name", "summary", "runtime"}, &vms) 282 if err != nil { 283 return vms, err 284 } 285 return vms, nil 286 } 287 288 // vmDataStoreData uses the client to get the 'name', 'summary', and 'runtime' sections of the Datastore Type 289 func vmDataStoreData(ctx context.Context, client *govmomi.Client) ([]mo.Datastore, error) { 290 m := view.NewManager(client.Client) 291 ds := []mo.Datastore{} 292 view, err := m.CreateContainerView(ctx, client.ServiceContent.RootFolder, []string{"Datastore"}, true) 293 if err != nil { 294 return ds, err 295 } 296 297 defer view.Destroy(ctx) 298 299 err = view.Retrieve(ctx, []string{"Datastore"}, []string{"name", "host", "summary"}, &ds) 300 if err != nil { 301 return ds, err 302 } 303 return ds, nil 304 } 305 306 const ( 307 descVsphereGuestMemHost = "Host memory utilization, also known as consumed host memory. Includes the overhead memory of the VM." 308 descVsphereGuestMemUsed = "Guest memory utilization statistics, also known as active guest memory." 309 descVsphereGuestMemBallooned = "The size of the balloon driver in the VM. The host will inflate the balloon driver to reclaim physical memory from the VM. This is a sign that there is memory pressure on the host." 310 descVsphereGuestPoweredState = "PowerState defines a simple set of states for a virtual machine: poweredOn (0), poweredOff (1), and suspended (2). If the virtual machine is in a state with a task in progress, this transitions to a new state when the task completes." 311 descVsphereGuestConnectionState = "The connectivity state of the virtual machine: Connected (0) means the server has access to the virtual machine, Disconnected (1) means the server is currently disconnected from the virtual machine, Inaccessible (2) means one or more of the virtual machine configuration files are inaccessible, Invalid (3) means the virtual machine configuration format is invalid, and Orphanded (4) means the virtual machine is no longer registered on the host it is associated with." 312 )