181 lines
5.0 KiB
Go
181 lines
5.0 KiB
Go
// host.go — what the ESXi hosts are doing.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/vmware/govmomi/units"
|
|
"github.com/vmware/govmomi/vim25/types"
|
|
)
|
|
|
|
// hoststat prints one line per host: CPU load, memory, how many machines it
|
|
// carries and how many of them run.
|
|
//
|
|
// Every number here comes out of host.Summary, and vSphere leaves parts of that
|
|
// out for a host it cannot currently talk to. Reading it unguarded meant that a
|
|
// single host in maintenance took down the whole report — including the healthy
|
|
// hosts, which is the very thing one wants to see at that moment.
|
|
func hoststat(vc VCenter, telemetry string) error {
|
|
s, err := connect(vc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer s.close()
|
|
|
|
hosts, err := s.hosts("name", "vm", "overallStatus", "runtime.connectionState", "summary")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
running, err := powerStates(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printRow(hostColumns, "", nil) // the heading, from the same widths as the rows
|
|
for _, host := range hosts {
|
|
hn := shortHost(host.Name)
|
|
total := len(host.Vm)
|
|
on := countOn(host.Vm, running)
|
|
|
|
usedMem := int64(host.Summary.QuickStats.OverallMemoryUsage) * 1024 * 1024
|
|
totalMem := int64(0)
|
|
cpu, cpuKnown := 0.0, false
|
|
if hw := host.Summary.Hardware; hw != nil {
|
|
totalMem = hw.MemorySize
|
|
cpu, cpuKnown = cpuPercent(hw, host.Summary.QuickStats)
|
|
}
|
|
cpuText, cpuCol := "-", colOff
|
|
if cpuKnown {
|
|
cpuText, cpuCol = SF("%.2f", cpu), loadColor(cpu, true)
|
|
}
|
|
|
|
printRow(hostColumns, "", []cell{
|
|
{hn, cWhite.fg()},
|
|
{cpuText, cpuCol},
|
|
{units.ByteSize(usedMem).String(), colSize},
|
|
{units.ByteSize(totalMem).String(), colSize},
|
|
{Itoa(total), colSize},
|
|
{Itoa(on), colSize},
|
|
{string(host.OverallStatus), statusColor(host.OverallStatus)},
|
|
{string(host.Runtime.ConnectionState), colAside},
|
|
})
|
|
|
|
if telemetry != "" {
|
|
post(telemetry, SF("vm,%s,%.2f,%d,%d,%d,%d,%s,%s",
|
|
hn, cpu, usedMem, totalMem, total, on,
|
|
host.OverallStatus, host.Runtime.ConnectionState))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// vmstat is the short form: how many machines each host carries, and how many of
|
|
// them are powered on.
|
|
func vmstat(vc VCenter) error {
|
|
s, err := connect(vc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer s.close()
|
|
|
|
hosts, err := s.hosts("name", "vm")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
running, err := powerStates(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printRow(countColumns, "", nil)
|
|
for _, host := range hosts {
|
|
printRow(countColumns, "", []cell{
|
|
{host.Name, cWhite.fg()},
|
|
{Itoa(len(host.Vm)), colSize},
|
|
{Itoa(countOn(host.Vm, running)), colSize},
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// powerStates maps every machine in the inventory to whether it is running, so
|
|
// the hosts can be counted without asking per machine.
|
|
func powerStates(s *session) (map[types.ManagedObjectReference]bool, error) {
|
|
vms, err := s.vms("runtime.powerState")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
on := make(map[types.ManagedObjectReference]bool, len(vms))
|
|
for _, vm := range vms {
|
|
on[vm.Reference()] = vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn
|
|
}
|
|
return on, nil
|
|
}
|
|
|
|
// countOn counts a host's running machines. Counting straight off host.Vm is
|
|
// what keeps the columns honest: the old version filled two maps keyed by
|
|
// host.Name and read them back keyed by host.Summary.Config.Name, two different
|
|
// vSphere properties, so a host added by address and renamed later reported 0
|
|
// machines while running dozens.
|
|
func countOn(refs []types.ManagedObjectReference, running map[types.ManagedObjectReference]bool) int {
|
|
n := 0
|
|
for _, ref := range refs {
|
|
if running[ref] {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// cpuPercent is the host's CPU load in percent. It reports false when the
|
|
// hardware summary has no usable clock or core count, rather than dividing by
|
|
// zero and printing "+Inf" in a column six characters wide.
|
|
func cpuPercent(hw *types.HostHardwareSummary, qs types.HostListSummaryQuickStats) (float64, bool) {
|
|
totalMHz := int64(hw.CpuMhz) * int64(hw.NumCpuCores)
|
|
if totalMHz <= 0 {
|
|
return 0, false
|
|
}
|
|
return 100.0 / float64(totalMHz) * float64(qs.OverallCpuUsage), true
|
|
}
|
|
|
|
// statusColor: vSphere's own words for how a host is doing, in the palette's.
|
|
func statusColor(st types.ManagedEntityStatus) string {
|
|
switch string(st) {
|
|
case "green":
|
|
return colOK
|
|
case "yellow":
|
|
return colBusy
|
|
case "red":
|
|
return colFull
|
|
}
|
|
return colAside
|
|
}
|
|
|
|
// shortHost is the hostname without its domain, which is all the first column
|
|
// has room for.
|
|
func shortHost(name string) string {
|
|
if i := strings.IndexByte(name, '.'); i > 0 {
|
|
return name[:i]
|
|
}
|
|
return name
|
|
}
|
|
|
|
// post hands one line to the monitoring server. A failure is worth a word but
|
|
// not the report: the numbers on screen are the point, the telemetry is a copy.
|
|
func post(url, data string) {
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Post(url, "application/data", bytes.NewReader([]byte(data)))
|
|
if err != nil {
|
|
PE("telemetry", err.Error())
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
PE("telemetry", fmt.Sprintf("%s said %s", url, resp.Status))
|
|
}
|
|
}
|