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  )