vSphere PowerCLI

Get-VM Parte II: Rendimiento

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.

  1. GET-VM PARTE I: LO BASICO
  2. GET-VM PARTE II: RENDIMIENTO
  3. GET-VIEW PARTE I: LO NO TAN BASICO
  4. GET-VIEW PARTE II: CASO PRÁCTICO
  5. GET-VIEW PARTE III: RENDIMIENTO
  6. 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.

info 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:

  1. Propiedades por defecto, aquellas que devuelve por defecto sin seleccionarlas.
  2. Propiedades básicas de la máquina, que obtenemos directamente sin tener que navegar por el objeto. Por ejemplo: Notas
  3. 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
1.1.1: Test una maquina
1.1.1: Test una maquina

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
1.1.2: Test varias máquinas
1.1.2: Test varias máquinas

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
1.1.3: Test todas las maquinas
1.1.3: Test todas las maquinas

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
Test 2.1: Una máquina
1.2.1 Test una máquina

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
1.2.2: Test varias maquinas
1.2.2: Test varias maquinas

Resultado: 2 segundos

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

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
1.3.1: Test una maquina
1.3.1: Test una maquina

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
1.3.2: Test varias maquinas
1.3.2: Test varias maquinas

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
1.3.3: Test todas las maquinas
1.3.3: Test todas las maquinas

Resultado: 14 minutos!!

1.4 Análisis de los resultados

Repasando los resultados de los test:

1.4 Tabla Resultados
1.4 Tabla Resultados

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
2.1.1 Test Foreach varias máquinas
2.1.1 Test Foreach varias máquinas

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:

Archivo texto Bi-Lab.txt
Archivo texto Bi-Lab.txt

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
2.1.2 Test Foreach 10 máquinas en fichero texto
2.1.2 Test Foreach 10 máquinas en fichero texto

O por pipeline, como mas nos guste:

Measure-Command {
$vm = Get-Content Bi-Lab.txt |foreach { get-VM $_ | select name, notes }
} |Select TotalMilliseconds
2.1.2 Test Foreach 10 máquinas en fichero texto
2.1.2 Test Foreach 10 máquinas en fichero texto 2

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
2.1.2 Test 10 Máquinas en fichero texto sin foreach
2.1.2 Test 10 Máquinas en fichero texto sin foreach

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

2.1.4 Test 500 Máquinas en fichero texto sin foreach
2.1.4 Test 500 Máquinas en fichero texto sin foreach

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:

2.2 Tabla Resultados
2.2 Tabla Resultados

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:

  1. Primero Get-VM obtiene todos los objetos de vCenter
  2. 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
3.1.1 Test filtrando propiedad por defecto
3.1.1 Test filtrando propiedad por defecto

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
3.1.2 Test filtrando por propiedad básica
3.1.2 Test filtrando por propiedad básica

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
3.1.3 Test filtrando por subobjeto
3.1.3 Test filtrando por subobjeto
Measure-Command {$vm = get-vm | where{$_.Guest.IPAddress -match "172."}} | Select TotalMilliseconds
3.1.3 Test filtrando por subobjeto 2
3.1.3 Test filtrando por subobjeto 2

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:

3.2 Tabla Resultados
3.2 Tabla Resultados

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.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *