Игорь Голиков

Ведущий разработчик

Всем привет! Я Игорь Голиков, ведущий разработчик ГК “Юзтех”.  В данной статье хочу рассказать о метриках памяти в VMware vCenter, в том числе как получить скрытые метрики. 

Статья может быть полезна SRE/DevOps и администраторам VMware vCenter, заинтересованным в получении «гостевых метрик» виртуальных машин, тем, кто хочет обосновать снижение выделенной виртуальным машинам памяти и сократить расходы без риска для производительности.

На одном из наших проектов возникла необходимость отслеживать использование памяти в гостевой ОС на виртуальных машинах под управлением VMware vCetner и формировать рекомендации по увеличению/уменьшению памяти выделенной виртуальной машине (rightsizing). Стандартные метрики памяти, доступные через vSphere Web Services API, не позволяют оценить объём памяти, используемой гостевой ОС.

Метрика (производительности) — это количественный показатель, который отражает состояние или поведение системы во времени (CPU, память, диск, сеть и т.д.).

Задача: найти метрику, показывающую объем памяти, потребляемой гостевой ОС и процессами в Linux системах с установленными Guest Tools.

Требования к метрике:

  • Точность - позволяет оценить объём памяти используемой гостевой ОС

  • Отзывчивость к изменениям - оперативно отображать изменение используемой гостевой ОС памяти

  • Доступность – не требует установки дополнительных агентов, доступна через vSphere Web Services API

Стенд и сценарий 

В рамках данной статьи для анализа я буду использовать виртуальную машину с ОС Alpine Linux с установленными Guest Tools c 8 GB RAM под управлением VMware vCenter 8.0.

Проверять я буду простой сценарий - на ВМ после перезагрузки (момент времени t0) запускается(t1) процесс резервирующий 5ГБ, через некоторое время (~30минут) процесс прерывается(t2).

A blue line graph on a white background  Description automatically generated
Рис. 1. Поведение, которого я хочу добиться
Рис 2. ВМ для анализа
Рис 2. ВМ для анализа

Запускаю сценарий и изучаю доступные метрики

Метрики VMware vCenter

VMware vCenter предоставляет нам две основные метрики памяти посредством публичного API (специфичные метрики, такие как Ballooning, Compressed и т.д., меня не интересуют):

Consumed (Consumed Host Memory) — это объём памяти, выделенной хостом под ВМ (с учётом TPS и overhead).

Этот объём не равен «used внутри гостя», часто можно наблюдать такую картину, что при росте объема используемой памяти в гостевой ОС растет и Consumed, но после того, как мы освобождаем всю память, используемую процессами гостевой ОС, Consumed остается прежним и не уменьшается, в последствии эта память может быть изъята с помощью механизмов ESXi, например, Ballooning.

TPS (Transparent Page Sharing) — это механизм ESXi, который на лету находит идентичные 4-KB страницы RAM у разных ВМ и хранит их в одном экземпляре.

Ballooning – способ,  с помощью которого гипервизор забирает неиспользуемую память у ВМ, когда у самого гипервизора не хватает памяти.  Делает это драйвер vmmemctl (в составе VMware Tools) внутри ВМ: он закрепляет страницы памяти в гостевой ОС, как будто «надувает шарик», и, тем самым вынуждает гостевую ОС вытеснить малоиспользуемые данные (в кэш или swap). Закреплённые страницы затем возвращаются гипервизору(хосту).

Overhead (memory overhead) — это дополнительная память хоста, которая нужна гипервизору, чтобы запустить и обслуживать ВМ. Она не видна внутри гостя, но учитывается на хосте и попадает в Consumed.

Active (Active Guest Memory) — память(страницы), к которой гостевая ОС реально обращалась за некий интервал времени.

Четкой формулировки, что именно показывает эта метрика, я не нашел,  это некая оценка, которая показывает «сколько памяти ВМ задействует по-настоящему сейчас», а не сколько ей выделено.

A screenshot of a graph  Description automatically generated
DescriРис. 3. Поведение метрик Consumed и Active во время экспериментаption automatically generated

Метрика Active начинает падать сразу после резервирования, несмотря на то, что память все еще удерживается процессом, так как никаких обращений к зарезервированным страницам не происходит.

Примечание: такое поведение процесса - синтетическое, в реальной жизни тяжеловесные процессы, резервирующие большие объемы памяти, активно используют какую-то часть этой памяти.

Метрика Consumed остается неизменной после высвобождения зарезервированных страниц процессом, так как гипервизор ничего не знает о процессах внутри ВМ.

Название метрики

Значение метрик, минимальное

До(t0-t1)

В течение (t1-t2)

После(t2-)

Active(KB)

83 884

83 884

83 884

Consumed (KB)

720 896

5 957 632

5 957 632

В таблице представлены минимальные значения каждой метрики в периоде

  • до запуска процесса, резервирующего память;

  • за промежуток времени, в течение которого процесс был запущен;

  • после завершения процесса.

Вердикт: ни одна из метрик не подходит для моих целей, так как не отражает выделение памяти внутри гостевой ОС. 

Метрики VMware Aria Operations (vRealize Operations)

Если отбросить вышеперечисленные метрики, то в VMware Aria мы найдем следующие метрики (описание из официальной документации):

Memory Guest Usage (KB) - Guest memory entitlement (This metric shows the amount of memory the VM uses)

Memory Usage (%) - Memory currently in use as a percentage of total available memory 

Memory Workload (%) - Demand over usable capacity. where it is applicable, demand includes limit and contention 

Guest Used Memory (KB) - Memory value reported by 'in use' counter in Windows and used counter in Linux. This contains pages that was used in the past but still considered used at present. It includes compressed pages in windows.

Guest Needed Memory(KB)  - Amount of memory needed for the Guest OS to perform optimally. This memory is considered as a cache for the disk and is a little more than the actual used memory.

Memory Usage (%) – показывает соотношение Guest Needed Memory и общего объема памяти и рассчитывается как:

\frac{\text{Guest Needed Memory (KB)}}{\text{Memory Total Capacity (KB)}} \times 100\%

A screenshot of a computer  Description automatically generated
Рис. 4.1. Memory Guest Usage и Memory Usage
A screenshot of a computer  Description automatically generated
Рис. 4.2. Memory Workload и Guest Memory Needed
A graph on a screen  Description automatically generated
Рис. 4.3 Guest Used Memory

Название метрики

Значение метрик

До(t0-t1)

В течение(t1-t2)

После(t2-)

Memory Guest Usage (KB)

720 896

5 956 949

5 956 949

Memory Usage(%)

13.16

75.82

13.01

Memory Workload (%)

13.16

75.82

13.01

Guest Needed Memory (KB)

1 103 759

6 360 070

1 091 150

Guest Used Memory (KB)

204 225

5 460 513

191 428

В наблюдениях Memory Usage совпадает с Memory Workload и является производным от Guest Needed Memory, поэтому их можно исключить.

Memory Guest Usage не отреагировал на высвобождение памяти процессом, исключаем его тоже.

«Guest Needed Memory (KB)»  - отражает потребность гостевой ОC в памяти, собирается через VMware Tools и показывает, сколько ОЗУ гостевая ОС «хотела бы» иметь для оптимальной работы (обычно включает page cache, поэтому часто выше, чем «фактически используется»).

Согласно документации VMware ‘Guest Needed Memory’ рассчитывается как:

\text{Guest Needed Memory}= \text{physUsable}- \max\!\left(0,\ \text{MemAvailable} - 0.05 \cdot \text{physUsable}\right)

Где physUsable берется из /proc/zoneinfo, как  сумма страниц со статусом “present”, умноженная на размер страницы. Это близко к MemTotal из /proc/meminfo, но может включать в себя зарезервированные и неуправляемые аллокатором страницы. Если physUsable больше MemTotal, то его необходимо уменьшить на 5%

А  MemAvailable  значение из /proc/meminfo.

https://knowledge.broadcom.com/external/article/410736

Попробуем вручную рассчитать Guest Needed Memory:

user:~$ getconf PAGE_SIZE
4096

user:~$ grep MemTotal /proc/meminfo
MemTotal:        8146520 kB
user:~$ free -k 
              total        used        free      shared  buff/cache   available
Mem:        8146520      230232     5819544         512     2096744     7644540
Swap:       8388604       12024     8376580

echo $(awk '/present/ {sum+=$2} END{printf "%.0f", sum*4096/1024}' /proc/zoneinfo)
8388088
echo $(awk '/MemAvailable:/ {print $2}' /proc/meminfo)
7635284

Получаем:

physUsable  = 8388088 kB

MemAvailable   = 7635284 kB

8388088 - \max\!\left(0,\ 7635284 - 0.05\cdot 8388088\right) = 1\,172\,208.4

Guest Used Memory (KB) — значение памяти, сообщаемое счетчиком “In use” в Windows и счетчиком “used” в Linux (прямой перевод определения из официальной документации). Совпадает с колонкой Used в выводе команды free в Linux, а следовательно, рассчитывается из /proc/meminfo.

Вывод: Guest Needed Memory и Guest Used Memory позволяют оценить объем памяти используемой гостевой ОС и оперативно отражают его изменение.

Примечание 1: данные метрики доступны только при условии, что на ВМ установлены Guest Tools, иначе происходит откат к метрикам гипервизора (Consumed/Active)

https://vmwarelab.org/2022/02/18/vrealize-operations-vrops-memory-reporting/ 

Примечание 2: иногда Guest Used Memorу перестает собираться Aria, в тоже время Guest Needed Memory продолжается собираться. Никаких объяснений этому мне обнаружить не удалось.

A screenshot of a computer  Description automatically generated
Рис. 4.4. Отсутствующие значения для Guest Used Memory

Метрики Zabbix

Метрика Linux: memory utilization рассчитывается из /proc/meminfo как:

100 \cdot \left(1 - \frac{\text{MemAvailable}}{\text{MemTotal}}\right)\%

A screenshot of a graph  Description automatically generated
Рис. 5. Linux: memory utilization

Название метрики

Значение метрик

До(t0-t1)

В течение(t1-t2)

После(t2-)

Linux: memory utilization(%)

5.46%

69.9%

5.46%

Linux: memory utilization(KB)

8388608 KB x0.0545=

457 179

5 863 636

457 179

Вывод: метрика Linux: memory utilization (%) также позволяют оценить объем памяти используемой гостевой ОС и оперативно отражают его изменение.

Итоги: выбор метрики

В шорт лист попали следующие метрики:

Aria  - Guest Needed Memory – интересная метрика оценивающая «сколько памяти необходимо гостевой ОС»

Aria  - Guest Used Memory – в отличие от предыдущей метрики, оценивающей сколько гостевая ОС хочет, эта метрика показывает сколько реально занято, предположительно Used посчитанная из /proc/meminfo

Zabbix - Linux: memory utilization  - базовая MemAvailable из /proc/meminfo

Валидность результатов

Хотя эксперимент проводился на конкретной сборке Linux, я считаю, что выводы масштабируются на дистрибутивы и версии Linux в целом: подсистема управления памятью ядра (учёт страниц, page cache, MemAvailable/MemTotal, поведение reclaim) работает по единым принципам, а интерфейсы /proc/meminfo и счётчики ядра сохраняют совместимость. Для Unix-подобных систем и для Windows - модель памяти и экспонируемые метрики различаются, поэтому данные системы требуют отдельной проверки.

Получение гостевых метрик без VMware Aria Operations

Предположение: Aria не получает никаких дополнительных метрик с виртуальных машин, а рассчитывает “Guest Needed Memory” только по метрикам, полученным с vCenter, значит vCenter раскрывает не все метрики в своем публичном API.

Метод QueryPerfCounter не возвращает ни одной метрики похожей на “Guest Needed Memory”.

С помощью пакета mitmproxy запускаю reverse-proxy, который будет проксировать и сохранять на диск все HTTP запросы на реальный vCenter. Добавляю в Aria новый Data Source – vCenter, используя IP адрес моего  reverse-proxy, жду сбора данных и получаю дамп всех запрос от Aria к vCenter и ответов на них.

#!/usr/bin/env bash
docker run -it --rm --name mitmproxy \
  -p 0.0.0.0:443:443 -p 0.0.0.0:8081:8081 \
  -v "$PWD/mitmproxy/.mitmproxy:/home/mitmproxy/.mitmproxy" \
  -v "$PWD/mitmproxy/addons:/addons" \
  -v "$PWD/mitmproxy/out:/out" \
  mitmproxy/mitmproxy \
  mitmweb --mode reverse:https://VCENTER_IP:443 \
          --listen-host 0.0.0.0 --listen-port 443 \
          --set block_global=false \
          --set connection_strategy=lazy \
          --set ssl_insecure=true \
          -s /addons/dump_soap.py

Скрипт dump_soap.py для сохранения запросов и ответов

from mitmproxy import http, ctx
from datetime import datetime
from pathlib import Path
from xml.dom import minidom
import re

OUT_DIR = Path("/out")
OUT_DIR.mkdir(parents=True, exist_ok=True)

SOAP_SIG = re.compile(rb"<\s*(soap:)?Envelope", re.I)

def _pretty_xml(data: bytes) -> bytes:
    try:
        return minidom.parseString(data).toprettyxml(indent="  ", encoding="utf-8")
    except Exception:
        return data

def _is_xml_or_soap(ct: str) -> bool:
    ct = (ct or "").lower()
    return ("xml" in ct) or ("soap" in ct) or ("application/soap+xml" in ct) or ("text/xml" in ct)

def _looks_like_soap(body: bytes) -> bool:
    return bool(body) and bool(SOAP_SIG.search(body))

def _fname(prefix: str, flow: http.HTTPFlow) -> Path:
    ts = datetime.utcnow().strftime("%Y%m%dT%H%M%S.%fZ")
    hostpath = (flow.request.host + flow.request.path).replace("/", "_")
    if len(hostpath) > 160:
        hostpath = hostpath[:160]
    return OUT_DIR / f"{ts}_{prefix}_{hostpath or 'root'}.xml"

def request(flow: http.HTTPFlow):
    ct = flow.request.headers.get("Content-Type", "")
    body = flow.request.content or b""
    if (_is_xml_or_soap(ct) or _looks_like_soap(body)) and body:
        ctx.log.info(f"SOAP request → {flow.request.method} {flow.request.url}")
        with _fname("req", flow).open("wb") as f:
            f.write(_pretty_xml(body))

def response(flow: http.HTTPFlow):
    if not flow.response:
        return
    ct = flow.response.headers.get("Content-Type", "")
    body = flow.response.content or b""
    if (_is_xml_or_soap(ct) or _looks_like_soap(body)) and body:
        ctx.log.info(f"SOAP response ← {flow.response.status_code} {flow.request.url}")
        with _fname("res", flow).open("wb") as f:
            f.write(_pretty_xml(body))

Подсмотрев  примерное название метрики делаю поиск по дампу.

Рис 6. Имя метрики в запросе
Рис 6. Имя метрики в запросе

И нахожу, что строка «guest.mem.needed» есть в ответе на некий вызов QueryPerfCounterInt.  В wsdl от vSphere Web Services API подобный вызов отсутствует, но есть QueryPerfCounter, возможно суффикс Int образован от Internal. 

Метод возвращает список гостевых метрик(Perfomance counter  в терминологии VMware, и необходимый  мне counter Id для вызова QueryPerf).

Id  Name
–-|----------------------------
541 guest.hugePage.size
542 guest.mem.physUsable
543 guest.mem.free
544 guest.mem.activeFileCache
545 guest.swap.spaceRemaining
546 guest.hugePage.total
547 guest.page.inRate
548 guest.page.outRate
549 guest.contextSwapRate
550 guest.page.size
551 guest.mem.needed
552 guest.mem.neededReservation
553 guest.mem.available
554 guest.mem.slabReclaim
555 guest.mem.buffers
556 guest.mem.cached
557 guest.mem.total
558 guest.cpu.runQueue
559 guest.disk.requestQueue
560 guest.disk.requestQueueAvg
801 guest.processor.queue
802 guest.mem.availableToMm
803 guest.mem.standby.normal
804 guest.mem.standby.reserve
805 guest.mem.standby.core
806 guest.mem.modifiedPages
807 guest.disk.queue
808 guest.disk.queueAvg

Добавим в wsdl новый метод по аналогии с QueryPerfCounter

<operation name="QueryPerfCounterInt">
  <input message="vim25:QueryPerfCounterIntRequestMsg" />
  <output message="vim25:QueryPerfCounterIntResponseMsg" />
  <fault name="RuntimeFault" message="vim25:RuntimeFaultFaultMsg"/>
</operation>
 
<operation name="QueryPerfCounter">
  <input message="vim25:QueryPerfCounterRequestMsg" />
  <output message="vim25:QueryPerfCounterResponseMsg" />
  <fault name="RuntimeFault" message="vim25:RuntimeFaultFaultMsg"/>
</operation>

<message name="QueryPerfCounterIntRequestMsg">
  <part name="parameters" element="vim25:QueryPerfCounterInt" />
</message>

<message name="QueryPerfCounterIntResponseMsg">
    <part name="parameters" element="vim25:QueryPerfCounterIntResponse" />
</message>

<operation name="QueryPerfCounterInt">
   <soap:operation soapAction="urn:vim25/6.7" style="document" />
   <input>
       <soap:body use="literal" />
   </input>
   <output>
       <soap:body use="literal" />
   </output>
   <fault name="RuntimeFault">
       <soap:fault name="RuntimeFault" use="literal" />
   </fault>
</operation>

<element name="QueryPerfCounterInt"  type="vim25:QueryPerfCounterIntRequestType" />
<element name="QueryPerfCounterIntResponse">
    <complexType>
        <sequence>
            <element name="returnval" type="vim25:PerfCounterInfoInt" minOccurs="0" maxOccurs="unbounded" />
        </sequence>
    </complexType>
</element>

Я не нашел, как в библиотеке pyVmomi вызвать нестандартный метод поэтому сделаем это с помощью Zeep и извлечем session cookie для последующего использования в pyVmomi

from requests import Session
from zeep import Client, Settings
from zeep.transports import Transport
from pathlib import Path


def enable_guest(addr: str, user: str, password: str):
   session = Session()
   session.verify = False
   transport = Transport(session=session)
   settings = Settings(strict=False, xml_huge_tree=True)


   # load WSDL from disk
   wsdl_path = Path(__file__).parent / "wsdl/vimService.wsdl"
   client = Client(wsdl=wsdl_path.as_uri(),
                   transport=transport,
                   settings=settings)


   service = client.bind("VimService", "VimPort")
   service._binding_options["address"] = f"https://{addr}/sdk"


   # log in
   mir = client.get_type("ns0:ManagedObjectReference")
   sm_ref = mir("SessionManager", type="SessionManager")
   service.Login(_this=sm_ref,
                 userName=user,
                 password=password)


   si_ref = mir("ServiceInstance", type="ServiceInstance")
   sc = service.RetrieveServiceContent(_this=si_ref)
   perf_mgr_ref = sc.perfManager
   print("Type:", perf_mgr_ref.type)
   print("Value:", perf_mgr_ref._value_1)


   # call QueryPerfCounterInt
   counters = service.QueryPerfCounterInt(_this=perf_mgr_ref)
   mappings = []
   result = []
   clm = client.get_type("ns0:PerformanceManagerCounterLevelMapping")
   for c in counters:
       print(c.key, f'{c.groupInfo.key}.{c.nameInfo.key}', c.rollupType)
       mappings.append(clm(counterId=c.key, aggregateLevel=1))
       result.append((c.key, f'{c.groupInfo.key}.{c.nameInfo.key}', c.rollupType, c.unitInfo.label))


    # Call updateCounterLevelMapping
    service.UpdateCounterLevelMapping(_this=perf_mgr_ref, counterLevelMap=mappings)




   session_cookie_value = client.transport.session.cookies.get('vmware_soap_session')
   return session_cookie_value, result

Для новых метрик может понадобиться вызвать UpdateCounterLevelMapping (судя по дампу, Aria делает это).

Запрашиваем значения для guest.mem.needed и получаем те же значения, что и в Aria Operations:

A graph with blue lines  Description automatically generated
Рис. 5. Значения метрики guest.mem.needed

Полный код скрипта https://github.com/igolikov/vmware-perf-counters/tree/main

В списке полученных метрик кроме guest.mem.needed есть еще  2 интересные метрики: guest.mem.free и guest.mem.available, построим для них нашу таблицу и пересчитаем утилизацию памяти (как обратную величину). Полная емкость равна 8146520 KB (метрика guest.mem.total).

Название метрики

Значение метрик

До(t0-t1)

В течение(t1-t2)

После(t2-)

guest.mem.free (KB)

7646536 (6.1%)

2390064 (70.6%)

7641128 (6.2%)

guest.mem.available

7693720 (5.5%)

2440092 (70%)

7692460 (5.5%)

guest.mem.needed

1099304(13.4%)

6364228(78%)

1100248(13.5%)

Значения guest.mem.free и guest.mem.available совпадают со значениями столбцов free  и available вывода команды free.

Метрики, соответствующей Guest Used Memorу в этом списке, обнаружить не удалось, думаю, она рассчитывается так же как и в результатах команды ”free”:

\text{used} = \text{total} - \bigl(\text{free} + \text{``buff/cache''}\bigr)

В терминах VMware vCenter:

\text{used} = \text{total} - \bigl(\text{free} + \text{``buff/cache''}\bigr)

Название метрики

Значение метрик

До(t0-t1)

В течение(t1-t2)

После(t2-)

guest.mem.free (KB)

7646536

2390064 

7641128 

guest.mem.buffers (KB)

177155

177365

177521

guest.mem.cached (KB)

118204

118278

135843

Расчетное guest.used (KB)

204 625

5 460 813

192 028

Aria Guest Used Memory (KB)

204 225

5 460 513

191 428

Выводы

  1. Стандартные метрики памяти VMware vCenter (Memory Consumed и Memory Active) не отражают реальное потребление памяти в гостевой ОС, поскольку они учитывают лишь данные гипервизора, а не внутренние ресурсы виртуальной машины;

  2. Гостевые метрики VMware vCenter (метрики с префиксом guest.mem), дают более точную картину, показывают фактическое потребление памяти внутри виртуальной машины, а не то, как гипервизор выделяет или использует память;

  3. VMware Tools или Guest Tools должны быть установлены на виртуальной машине для сбора гостевых метрик;

  4. Aria Operations не является обязательной для получения гостевых метрик. Даже если Aria не используется в инфраструктуре, метрики памяти можно получить через внутренние вызовы API vCenter (QueryPerfCounterInt) .

Полезные ссылки

https://knowledge.broadcom.com/external/article/410736 

https://vmwarelab.org/2022/02/18/vrealize-operations-vrops-memory-reporting/ 

https://www.vmware.com/docs/perf-vsphere-memory_management 

https://garyflynn.com/post/vrops-memory-metric-collection-changes-in-6-7-7-0/ 

Комментарии (0)