En la primera parte de la serie trate los aspectos básicos del cmdlet Get-VM. En esta segunda voy a profundizar en su rendimiento en diferentes usos.
- GET-VM PARTE I: LO BASICO
- GET-VM PARTE II: RENDIMIENTO
- GET-VIEW PARTE I: LO NO TAN BASICO
- GET-VIEW PARTE II: CASO PRÁCTICO
- GET-VIEW PARTE III: RENDIMIENTO
- GET-VM VS GET-VIEW: CONCLUSIONES
Para medir el rendimiento voy a usar el comando Measure-Command, de serie en PowerShell. Este cmdlet retorna el tiempo que ha tardado en ejecutarse el scriptblock (el codigo entre llaves) que contenga.
Los resultados obtenidos pueden ser muy diferentes en la ejecución de la misma sentencia. Dependerá de muchos factores: Si ya tiene la conexión o los objetos creados, la resultados cacheados, el estado de carga de la máquina, de la red o del servidor vCenter. En todas las pruebas ejecutare el comando dos veces y mostrando los resultados en milisegundos de la última. Así tendremos una medida con la que comparar los resultados de cada prueba.
Aunque en el artículo me basare exclusivamente en Get-VM, tal y como describí en el primer artículo, todos los cmdlets que retornan un objeto VIObject se comportan igual.
Empezamos!
1. Seleccionando Propiedades
En todos los test voy a considerar 3 tipos de propiedades de un objeto maquina:
- Propiedades por defecto, aquellas que devuelve por defecto sin seleccionarlas.
- Propiedades básicas de la máquina, que obtenemos directamente sin tener que navegar por el objeto. Por ejemplo: Notas
- Propiedades de tipo objeto: Las que hay que navegar mediante «.» para expandir su contenido. Por ejemplo: ExtensionData.
Y cada una de estas propiedades voy a repetir las pruebas para:
- 1 maquina
- 4 maquinas
- Toda las maquinas: en mi entorno ahora mismo son 5424.
1.1 Propiedades por defecto
1.1.1 Test una maquina
Obtener una maquina sin especificar ninguna propiedad
Measure-Command {$vm = get-vm bi-lab001} |Select TotalMilliseconds

Resultado: 2 segundos
1.1.2 Test varias máquinas
Ahora pruebo con 4 máquinas:
Measure-Command {$vm = get-vm bi-lab001, bi-lab002, bi-lab003, bi-lab004} |Select TotalMilliseconds

Resultado: 2 segundos
1.1.3 Test todas las maquinas
Si no se especifica ninguna máquina, nos devuelve TODAS las máquinas de vCenter
Measure-Command {$vm = get-vm} |Select TotalMilliseconds

Resultado: 2 segundos
Primera sorpresa. Tarda casi lo mismo para una que para 5424 máquinas. ¿Y dices que es lento?
1.2 Propiedades básicas
Voy a seleccionar solo dos propiedades básicas: Nombre y Notas
1.2.1 Test una máquina
Measure-Command {$vm = get-vm bi-lab001 |select name, notes} |Select TotalMilliseconds

Resultado: 2 segundos
1.2.2 Test varias maquinas
$maquinas = "bi-lab001", "bi-lab002", "bi-lab003", "bi-lab004" Measure-Command {$vm = get-vm $maquinas | select name, notes} | Select TotalMilliseconds

Resultado: 2 segundos
1.2.3 Test todas las maquinas
Measure-Command {$vm = get-vm | select name, notes} | Select TotalMilliseconds

Resultado: 14 segundos
1.3 Propiedades de tipo objeto
Para esta prueba voy a seleccionar 20 propiedades de todo tipo de varios subojectos diferentes.
1.3.1 Test una maquina
Measure-Command { $vms = get-vm Bi-Lab001 | select name, @{n="vCenter";e={$_.Uid.Split("|")[0].Split("@")[1]}}, @{n="Cluster";e={$_.VMHost.Parent.Name}}, @{n="Host";e={$_.VMHost.Name}}, @{n="Folder";e={$_.Folder.Name}}, @{n="Tools Status";e={$_.Guest.ExtensionData.ToolsStatus}}, @{n="Tools Version";e={$_.guest.ExtensionData.ToolsVersion}}, @{n="IPAddress";e={$_.Guest.IPAddress}}, @{n="CustomFields";e={$_.CustomFields}}, @{n="Notas";e={$_.Notes}}, @{n="UUID";e={$_.ExtensionData.Config.InstanceUuid}}, @{n="MoRef";e={$_.Id}}, @{n="HardwareVersion";e={$_.HardwareVersion}}, @{n="DNSName";e={$_.Guest.HostName}}, @{n="SO";e={$_.Guest.OSFullName}}, @{n="UsedSpaceGB";e={$_.UsedSpaceGB}}, @{n="MemoryGB";e={$_.MemoryGB}}, @{n="NumCpu";e={$_.NumCpu}}, @{n="CreateDate";e={$_.CreateDate}} } | select TotalMilliseconds

Resultado: 2,7 segundos
1.3.2 Test varias maquinas
$maquinas = "bi-lab001", "bi-lab002", "bi-lab003", "bi-lab004" Measure-Command { $vms = get-vm $maquinas | select name, @{n="vCenter";e={$_.Uid.Split("|")[0].Split("@")[1]}}, @{n="Cluster";e={$_.VMHost.Parent.Name}}, @{n="Host";e={$_.VMHost.Name}}, @{n="Folder";e={$_.Folder.Name}}, @{n="Tools Status";e={$_.Guest.ExtensionData.ToolsStatus}}, @{n="Tools Version";e={$_.guest.ExtensionData.ToolsVersion}}, @{n="IPAddress";e={$_.Guest.IPAddress}}, @{n="CustomFields";e={$_.CustomFields}}, @{n="Notas";e={$_.Notes}}, @{n="UUID";e={$_.ExtensionData.Config.InstanceUuid}}, @{n="MoRef";e={$_.Id}}, @{n="HardwareVersion";e={$_.HardwareVersion}}, @{n="DNSName";e={$_.Guest.HostName}}, @{n="SO";e={$_.Guest.OSFullName}}, @{n="UsedSpaceGB";e={$_.UsedSpaceGB}}, @{n="MemoryGB";e={$_.MemoryGB}}, @{n="NumCpu";e={$_.NumCpu}}, @{n="CreateDate";e={$_.CreateDate}} } | select TotalMilliseconds

Resultado: 2,7 segundos
1.3.3 Test todas las maquinas
Measure-Command { $vms = get-vm | select name, @{n="vCenter";e={$_.Uid.Split("|")[0].Split("@")[1]}}, @{n="Cluster";e={$_.VMHost.Parent.Name}}, @{n="Host";e={$_.VMHost.Name}}, @{n="Folder";e={$_.Folder.Name}}, @{n="Tools Status";e={$_.Guest.ExtensionData.ToolsStatus}}, @{n="Tools Version";e={$_.guest.ExtensionData.ToolsVersion}}, @{n="IPAddress";e={$_.Guest.IPAddress}}, @{n="CustomFields";e={$_.CustomFields}}, @{n="Notas";e={$_.Notes}}, @{n="UUID";e={$_.ExtensionData.Config.InstanceUuid}}, @{n="MoRef";e={$_.Id}}, @{n="HardwareVersion";e={$_.HardwareVersion}}, @{n="DNSName";e={$_.Guest.HostName}}, @{n="SO";e={$_.Guest.OSFullName}}, @{n="UsedSpaceGB";e={$_.UsedSpaceGB}}, @{n="MemoryGB";e={$_.MemoryGB}}, @{n="NumCpu";e={$_.NumCpu}}, @{n="CreateDate";e={$_.CreateDate}} } | select TotalMilliseconds

Resultado: 14 minutos!!
1.4 Análisis de los resultados
Repasando los resultados de los test:

El comportamiento de Get-VM, si no se especifica ninguna propiedad, esta optimizado para retornar lo mínimo. Un objeto VIObject que permita interactuar con otros cmdlets (parámetro de entrada o pipeline), ya sea para traer otra información relacionada (por ejemplo Get-HardDisk) o ejecutar una acción (Stop-VM), de la manera más rápida posible.
Una de las ventajas de Get-VM es que (casi) todas las propiedades vienen «listos para usar», con el nombre del objeto y no su MoRef (clave primaria de los objetos en vCenter). Lo que es muy fácil y cómodo.
Sin embargo todos esos nombres no se guardan en la misma tabla en BBDD (VCDB Database):
- La tabla de maquinas, además de sus propiedades, solo contiene las claves foráneas (MoRef) de otros objetos.
- Cada tipo de objeto (cluster, host, datastore, evento, …) tienen su propia tabla en la BBDD con todas sus propiedades.
- Todas las tablas se relacionan entre si por la clave MoRef: vm-603, host-12, datastore-23
- Para resolver todas las relaciones y obtener los datos de todas las tablas, se requieren muchas subconsultas (join).
Por eso, al solicitar diferentes propiedades de subobjetos, los del tipo 3, tiene que realizar una o varias consulta «pesadas» para la BBDD y que le cuesta ejecutar. Seguramente haya más factores implicados, incluso más influyentes que el descrito de BBDD, como diferentes llamadas internas a la API de vCenter o la gestión de objetos del propio Get-VM.
Y este es el principal inconveniente de Get-VM: Su rendimiento es bastante malo con un número no muy alto de objetos.
2. ForEach
Vamos a probar cómo se comporta Get-VM en un bucle ForEach, muy común en cualquier script.
2.1 Pruebas
2.1.1 Test Foreach varias máquinas
Generamos un array con 4 máquinas y canalizamos por pipeline a un foreach
$vm = @() $maquinas = "bi-lab001", "bi-lab002", "bi-lab003", "bi-lab004" Measure-Command {$maquinas | ForEach-Object{$vm += get-vm $_}} |Select TotalMilliseconds

Resultado: 8 segundos, 2 segundos por cada máquina.
2.1.2 Test Foreach 10 máquinas en fichero texto
Es muy frecuente usar como entrada de un script un listado de máquinas en formato CSV o TXT por su rapidez y comodidad.
En este ejemplo, genero un simple fichero de texto con 10 nombres de maquina:

Y luego cargarlo en una variable y lo recorremos mediante un foreach:
Measure-Command { $vm = @() $maquinas = Get-Content Bi-Lab.txt foreach ($maquina in $maquinas){ $vm += get-VM $maquina | select name, notes } } |Select TotalMilliseconds

O por pipeline, como mas nos guste:
Measure-Command { $vm = Get-Content Bi-Lab.txt |foreach { get-VM $_ | select name, notes } } |Select TotalMilliseconds

Resultado: 22 segundos, 2 segundos por maquina.
2.1.3 Test 10 Máquinas en fichero texto sin foreach
¿Qué ocurre si pasamos este contenido directamente al Get-VM?
Es la misma prueba que el 1.3.2 Test varias maquinas, solo que con 10 vm cargadas de un fichero de texto.
Measure-Command { $maquinas = Get-Content Bi-Lab.txt $vm = get-VM $maquinas | select name, notes } |Select TotalMilliseconds

Resultado: 2 segundos para las 10 máquinas
2.1.4 Test 500 Máquinas en fichero texto sin foreach
Y ya como prueba final, un CSV con 500 máquinas. El codigo es el mismo solo cambia el fichero txt

Resultado: 8 segundos!!
2.2 Análisis de los resultados
Como hemos visto en el Test 2.1.1 y 2.1.2, si cada iteración del bucle lanza un Get-VM, cada uno tarda casi 2 segundos:
Nº maquinas x 2 segundos
Si lo ejecutase para todas las maquinas:
5424 x 2 segundos = 10848 segundos => 180 minutos => 3 horas!!!!
Y solo retorna dos propiedades!!
Resumiendo los resultados de los test:

En conclusión, tal y como demuestran los Test 2.1.3 y 2.1.4, resulta muchísimo más rápido pasarle el array como parámetro Name a Get-VM y obtendremos los resultados en apenas unos segundos, independientemente del numero de máquinas.
3. Filtrado
Como ya detalle al final de la primera parte, Get-VM solo puede filtrar por ciertos parámetros. Si queremos filtrar por cualquier otra propiedad debemos usar Where.
Recordemos que filtrar con Where requiere dos pasos:
- Primero Get-VM obtiene todos los objetos de vCenter
- Luego le pasa por pipeline a Where todos los objetos, que los recorre y retorna los que cumplan la condición.
3.1 Pruebas
3.1.1 Test filtrando propiedad por defecto
En este test pruebo dos filtros de maquinas:
- Solo las encendidas
- Las que comiencen por el nombre «Bi-Lab«.
Measure-Command {$vm = get-vm | where{$_.PowerState -eq "PoweredOn"}} | Select TotalMilliseconds Measure-Command {$vm = get-vm | where{$_.Name -like "Bi-Lab*"}} | Select TotalMilliseconds

Resultado: 7 segundos
En recorrer todos los objetos para filtrar los que cumplen la condición, Where ha tardado 5 segundos extras. Es un tiempo muy razonable para la cantidad de máquinas a procesar.
3.1.2 Test filtrando por propiedad básica
Filtramos por un campo que no está incluido en el objeto por defecto, pero si en las propiedades base de la máquina, como es el caso de Notas:
Measure-Command {$vm = get-vm | where{$_.Notes -match "Windows"}} | Select TotalMilliseconds

Resultado: 11 segundos
Es sensiblemente mas lento, aunque todavía muy razonable.
3.1.3 Test filtrando por subobjeto
Por ultimo, probamos a filtrar buceando por los subobjetos de la máquina, por ejemplo el Estado de las Tools o una IP:
Measure-Command {$vm = get-vm | where{$_.Guest.ExtensionData.ToolsStatus -like "*running"}} | Select TotalMilliseconds

Measure-Command {$vm = get-vm | where{$_.Guest.IPAddress -match "172."}} | Select TotalMilliseconds

Resultado: 6 minutos
Ha pasado de unos segundos a varios minutos de proceso. No es un buen resultado.
3.2 Análisis de los resultados
Resumiendo los resultados de los test:

Como ya comente en el primer artículo, si no se filtran con parámetros de entrada los resultados de Get-VM y se quiere filtrar por alguna propiedad que no sea básica, es un proceso muy lento.
4. Conclusiones
Tras evaluar en este articulo tres casos de uso típicos, hemos podido ver el comportamiento de Get-VM en diferentes escenarios.
Estas conclusiones, como he comentado al principio del artículo, son válidas para cualquier cmdlet de PowerCLI del tipo Get-Loquesea que retornan objetos VIObject.
Ventajas
Las principales ventajas son:
- Facilidad de uso: Todos los datos vienen bien formateados y con su nombre.
- Potencia: al poder utilizarlo en canalizaciones a otros cmdlets de PowerCLI
- Flexible: ya que nos ofrece muchos parámetros de entrada con los que poder filtrar
Inconvenientes
- Lentitud: Precisamente debido a su facilidad de uso y la cantidad de datos que ofrece «listos para usar«, es un cmdlet pesado.
La lentitud puede ser un problema menor para seleccionar datos, conociendo las peculiaridades explicadas en este artículo y los parámetros de entrada, pero para el filtrado no hay solución.
Si queremos procesar una gran cantidad de datos y filtrarlos como queramos, es muchísimo mas recomendable utilizar Get-View, que como veremos en el siguiente artículo de la serie, su principal ventaja es la velocidad.
Cualquier duda o pregunta, responderé encantando en los comentarios.