Custom Component - ESXi Stats

2019-08-15 EDIT: Huge thank you to @ludeeus, the component can now be configured from the Integrations menu!

I wanted a way to have a quick look at my virtual environment from home assistant so I made a esxi_stats custom component. It is read only and does not make any changes, although it is possible.

Right now it can pull various information about a host, datastores and VMs. Data is presented as a sensor for each monitored condition, so if you monitor hosts and VMs it creates 2 sensors, each containing relevant data in attributes. Right now I’m relying on other custom cards to present data, but a custom card to accompany the component would be ideal. More information on what stats are monitored is available on the GitHub page.

The component works with a single ESXi host and should work with a vCenter, as well. However, I don’t have access to a vCenter right now so that is not tested. It can be installed via HACS by importing git url or manually.

15 Likes

I love it, even if its coming so late (ive been waiting for such plugin for so long, that i have already migrated away from ESXi)

great work. thanks, gonna test it right now!

Thanks for sharing!

I can confirm that works with Virtual Center 6.7

This is fricken awesome!!! thanks

Awesome work! Thanks!

Thank you for confirming!

The sensors work great!

I Use the lovelace by interface and cant get the button-card working.
I have included the resources. created a empty view and from then I’m lost. what must I paste where?
can somebody give a few steps that I can do to get it done?

Oh man, I love button-card, but it can get quite complex as the number of different options is overwhelming. Please visit button-card and get familiar with their documentation. There is a lot of information on their page. (https://github.com/custom-cards/button-card) and trying to summarize everything here is not realistic.

For the most part you should be able to copy/paste the from the example yaml files I provide on the github page and then replace the placeholder names with actual object names. Placeholder names are <HOST_NAME_HERE>, <DATASTORE_NAME_HERE>, and <VM_NAME_HERE>

So for-displaying HOSTS based on the templates i included in the github

  1. Install button-card
  2. add templates in ui-lovelace.yaml file
  3. Paste your “buttons” wherever in your lovelace UI you see fit
  4. search/replace the placeholders with actual host names for each button

Nice!

Do you have any plans on adding service calls for start/stop/restart?
And potentially configuration with config_flow? :smiley:

@wxt9861

Hey. Lovely component.

You mention something about “You can break out the data into separate sensors - See home-assistant documentation”

I don’t like the Lovelace frontend. Can you point out how to properly format/split the sensors data into more smaller sensors?

1 Like

@wxt9861 I have many questions for you!

First: how to you monitor multiple hosts? If this component used - platform: esxi_stats I know I could have multiple instances…

Second question: this is not working for me. What am I doing wrong?

Server running: ESXi-6.7.0-8169922-standard (VMware, Inc.)

Here is the exception I am getting:

Traceback (most recent call last):
  File "/root/homeassistant/lib/python3.6/site-packages/homeassistant/helpers/entity_platform.py", line 261, in _async_add_entity
    await entity.async_device_update(warning=False)
  File "/root/homeassistant/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 378, in async_device_update
    await self.async_update()
  File "/root/.homeassistant/custom_components/esxi_stats/sensor.py", line 30, in async_update
    await self.hass.data[DOMAIN_DATA]["client"].update_data()
  File "/root/.homeassistant/custom_components/esxi_stats/__init__.py", line 169, in update_data
    self.hass.data[DOMAIN_DATA]["vms"][vm_name] = get_vm_info(vm)
  File "/root/.homeassistant/custom_components/esxi_stats/esxi.py", line 91, in get_vm_info
    ((vm_sum.quickStats.overallCpuUsage / vm_run.maxCpuUsage) * 100), 0
TypeError: unsupported operand type(s) for /: 'NoneType' and 'int'

I added some logs and got this for _LOGGER.info(vm_sum) (quickStats towards the bottom):

2019-08-14 23:52:53 INFO (MainThread) [custom_components.esxi_stats.esxi] (vim.vm.Summary) {
   dynamicType = <unset>,
   dynamicProperty = (vmodl.DynamicProperty) [],
   vm = 'vim.VirtualMachine:1',
   runtime = (vim.vm.RuntimeInfo) {
      dynamicType = <unset>,
      dynamicProperty = (vmodl.DynamicProperty) [],
      device = (vim.vm.DeviceRuntimeInfo) [
         (vim.vm.DeviceRuntimeInfo) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            runtimeState = (vim.vm.DeviceRuntimeInfo.VirtualEthernetCardRuntimeState) {
               dynamicType = <unset>,
               dynamicProperty = (vmodl.DynamicProperty) [],
               vmDirectPathGen2Active = false,
               vmDirectPathGen2InactiveReasonVm = (str) [],
               vmDirectPathGen2InactiveReasonOther = (str) [
                  'vmNptIncompatibleNetwork'
               ],
               vmDirectPathGen2InactiveReasonExtended = <unset>,
               reservationStatus = <unset>,
               attachmentStatus = '',
               featureRequirement = (vim.vm.FeatureRequirement) []
            },
            key = 4000
         }
      ],
      host = 'vim.HostSystem:ha-host',
      connectionState = 'connected',
      powerState = 'poweredOn',
      faultToleranceState = 'notConfigured',
      dasVmProtection = <unset>,
      toolsInstallerMounted = false,
      suspendTime = <unset>,
      bootTime = 2019-04-26T09:21:39.837314Z,
      suspendInterval = 0,
      question = <unset>,
      memoryOverhead = <unset>,
      maxCpuUsage = 9575,
      maxMemoryUsage = 4096,
      numMksConnections = 0,
      recordReplayState = 'inactive',
      cleanPowerOff = <unset>,
      needSecondaryReason = <unset>,
      onlineStandby = false,
      minRequiredEVCModeKey = <unset>,
      consolidationNeeded = false,
      offlineFeatureRequirement = (vim.vm.FeatureRequirement) [
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.lm',
            featureName = 'cpuid.lm',
            value = 'Bool:Min:1'
         }
      ],
      featureRequirement = (vim.vm.FeatureRequirement) [
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.SSE3',
            featureName = 'cpuid.SSE3',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.PCLMULQDQ',
            featureName = 'cpuid.PCLMULQDQ',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.SSSE3',
            featureName = 'cpuid.SSSE3',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.CMPXCHG16B',
            featureName = 'cpuid.CMPXCHG16B',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.SSE41',
            featureName = 'cpuid.SSE41',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.SSE42',
            featureName = 'cpuid.SSE42',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.POPCNT',
            featureName = 'cpuid.POPCNT',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.AES',
            featureName = 'cpuid.AES',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.SS',
            featureName = 'cpuid.SS',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.LAHF64',
            featureName = 'cpuid.LAHF64',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.NX',
            featureName = 'cpuid.NX',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.RDTSCP',
            featureName = 'cpuid.RDTSCP',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.LM',
            featureName = 'cpuid.LM',
            value = 'Bool:Min:1'
         },
         (vim.vm.FeatureRequirement) {
            dynamicType = <unset>,
            dynamicProperty = (vmodl.DynamicProperty) [],
            key = 'cpuid.Intel',
            featureName = 'cpuid.Intel',
            value = 'Bool:Min:1'
         }
      ],
      featureMask = (vim.host.FeatureMask) [],
      vFlashCacheAllocation = 0,
      paused = false,
      snapshotInBackground = false,
      quiescedForkParent = <unset>,
      instantCloneFrozen = false,
      cryptoState = <unset>
   },
   guest = (vim.vm.Summary.GuestSummary) {
      dynamicType = <unset>,
      dynamicProperty = (vmodl.DynamicProperty) [],
      guestId = 'ubuntu64Guest',
      guestFullName = 'Ubuntu Linux (64-bit)',
      toolsStatus = 'toolsOk',
      toolsVersionStatus = 'guestToolsUnmanaged',
      toolsVersionStatus2 = 'guestToolsUnmanaged',
      toolsRunningStatus = 'guestToolsRunning',
      hostName = 'ubi1',
      ipAddress = '192.168.1.209'
   },
   config = (vim.vm.Summary.ConfigSummary) {
      dynamicType = <unset>,
      dynamicProperty = (vmodl.DynamicProperty) [],
      name = 'ubi1',
      template = false,
      vmPathName = '[datastore1] ubi1/ubi1.vmx',
      memorySizeMB = 4096,
      cpuReservation = 0,
      memoryReservation = 0,
      numCpu = 4,
      numEthernetCards = 1,
      numVirtualDisks = 1,
      uuid = '564d3649-de04-f415-674e-0677fac35318',
      instanceUuid = '52055b81-7ef0-a171-2e7f-a424fbf5172c',
      guestId = 'ubuntu64Guest',
      guestFullName = 'Ubuntu Linux (64-bit)',
      annotation = '',
      product = <unset>,
      installBootRequired = <unset>,
      ftInfo = <unset>,
      managedBy = <unset>,
      tpmPresent = false,
      numVmiopBackings = 0
   },
   storage = (vim.vm.Summary.StorageSummary) {
      dynamicType = <unset>,
      dynamicProperty = (vmodl.DynamicProperty) [],
      committed = 111785995493,
      uncommitted = 500,
      unshared = 111785995493,
      timestamp = 2019-08-14T02:07:58.912464Z
   },
   quickStats = (vim.vm.Summary.QuickStats) {
      dynamicType = <unset>,
      dynamicProperty = (vmodl.DynamicProperty) [],
      overallCpuUsage = <unset>,
      overallCpuDemand = <unset>,
      guestMemoryUsage = <unset>,
      hostMemoryUsage = -1,
      guestHeartbeatStatus = 'green',
      distributedCpuEntitlement = <unset>,
      distributedMemoryEntitlement = <unset>,
      staticCpuEntitlement = <unset>,
      staticMemoryEntitlement = <unset>,
      privateMemory = 0,
      sharedMemory = <unset>,
      swappedMemory = <unset>,
      balloonedMemory = <unset>,
      consumedOverheadMemory = <unset>,
      ftLogBandwidth = <unset>,
      ftSecondaryLatency = <unset>,
      ftLatencyStatus = <unset>,
      compressedMemory = <unset>,
      uptimeSeconds = <unset>,
      ssdSwappedMemory = <unset>
   },
   overallStatus = 'green',
   customValue = (vim.CustomFieldsManager.Value) []
}

Sorry about the extra work. I’m not sure my ESXI server is returning quite the info I would need…

For some reason i’m not able to install the repository in the hacs…

@Fusion thank you for the logs, I see the problem. For some reason the host is not reporting VM CPU usage (overallCpuUsage is unset under quickStates) and we’re expecting a value to do some math for CPU usage %. That’s strange. Can you see usage statistics from the GUI? Anyway, I will fix this today and add more debugging info as well.

For your first question - right now only 1 host or vCenter is supported, but I will work on having an option to monitor multiple in future releases.

@Fusion I just pushed a new release that should address the issues you ran into. Let me know if it helps.

@wxt9861 What’s the reason behind not exposing everything by default?
And only have the user set in monitored_condition in the cases he/she want to hide something?

@ludeeus, thanks! I am still thinking about adding service calls. Is that something the community wants?

My next step are to

  1. present alarm/config issue information and allow a user to clear an alarm directly from home assistant.
  2. allow monitoring only powered on VMs (?)
  3. Ultimately i want to implement config_flow to allow for easier setup, also

The reason I didn’t expose everything at once is because there is a lot of data. monitored_conditions allows a bit more control on whats exposed, just like you said.

4 Likes

I want that! :smiley: If you could also add inn a service call to create a snapshot of a VM that would be perfect, since we could then automate that with HA :smiley:

And if you do… esxi_stats is no longer “correct”, so maybe just esxi ?
Would be a breaking change, but it’s better to do that in the start when few folks are using it.

Yes, and that’s a good point. Snapshot handling/monitoring is a good idea. For example, send an alert/notification if your snapshot is x days old, point out when vm disk consolidation is needed, quickly snapshot before an upgrade, etc.

1 Like

I like it!

I have been wanting to make an ESXi integration for some time now, and now I don’t have too :see_no_evil: so thanks for that!

Anyone can provide some information on how to split up the sensor data into more specific sensors?
Thanks!