Przejdź do treści
AdministracjaW redakcji··~18 min czytania

Jak mierzyć zużycie energii hosta KVM

Metoda porównywalnego pomiaru hosta KVM: co mierzy watomierz, bateria i RAPL, jak przygotować eksperyment oraz jak nie wyciągnąć z jednego odczytu zbyt dalekich wniosków.

Autor: Zespół LinuxLab

W redakcji. Artykuł jest wersją roboczą, nie jest jeszcze wersją finalną i może ulec zmianie.

Pomiary energii mają sens wtedy, gdy odpowiadają na konkretne pytanie: ile pobiera cały host, czy dana VM powoduje wybudzenia procesora albo czy zmiana konfiguracji rzeczywiście coś poprawiła. Opisujemy metodę z labu, nie uniwersalną receptę dla każdego serwera.

Czy pomiar energii ma sens dla danego hosta KVM?

Pomiar jest użyteczny, gdy prowadzi do decyzji. Może odpowiedzieć na przykład, czy host laboratoryjny pracujący całą dobę rzeczywiście zużywa mniej po zmianie definicji VM, czy warto przenieść zadanie z laptopa na serwer albo która maszyna powoduje nieoczekiwane wybudzenia procesora. Nie zastępuje monitoringu dostępności, testu wydajności ani planowania pojemności.

Najwięcej daje na hostach z wieloma bezczynnymi VM, małym obciążeniem tła i długim czasem pracy. Przy serwerze, którego obciążenie stale zmienia się wraz z ruchem użytkowników, pojedynczy wynik jest tylko obserwacją. Wtedy potrzebny jest dłuższy szereg danych, zebrany w porównywalnych przedziałach i zestawiony z metrykami aplikacji.

Co dokładnie mierzymy: moc, energię, host czy pojedynczą VM?

Moc wyrażamy w watach (W). Jeden wat oznacza tempo zużywania energii równe jednemu dżulowi na sekundę. Odczyt 5 W mówi więc, ile energii urządzenie pobiera w danej chwili albo w krótkim oknie uśredniania. Energia jest sumą mocy w czasie: watogodzina (Wh) to praca urządzenia o mocy 1 W przez godzinę, a 1 kWh to 1000 Wh. Host pobierający stale 3,3 W przez rok zużyje w przybliżeniu 28,9 kWh: 3,3 × 24 × 365 / 1000.

Proces QEMU i VM nie są osobnym licznikiem energii. VM zużywa czas CPU, pamięć i I/O na wspólnym hoście, a do poboru całej maszyny dochodzą dyski, sieć, wentylatory, kontrolery oraz straty zasilacza. Dlatego odczyt CPU gościa odpowiada na pytanie „co robi aplikacja w VM”, a watomierz przy gniazdku odpowiada na pytanie „ile energii pobiera cały zestaw”. Oba wyniki są przydatne, lecz nie należy ich ze sobą utożsamiać.

Kiedy użyć watomierza, BMC/IPMI, baterii lub RAPL?

Źródło danychNa jakie pytanie odpowiadaNajważniejsze ograniczenie
watomierz przy gniazdkuile energii pobiera kompletny host razem z zasilaczemtrzeba utrzymać ten sam zasilacz, ekran i peryferia; rozdzielczość tanich modeli może ukryć małą różnicę
BMC/IPMIile pobiera serwer, jeśli kontroler udostępnia czujnik mocyzakres, częstotliwość próbkowania i dokładność zależą od producenta oraz modelu serwera
power_now baterii laptopajaką moc pobiera laptop z baterii w danym stanienie jest to pobór z gniazdka; podczas ładowania odczyt może być zerowy lub nieprzydatny
RAPL (Running Average Power Limit)jak zmienia się energia pakietu CPU, a na części platform także domeny psysto licznik dostępny tylko na części procesorów; nie obejmuje automatycznie całego hosta ani strat zasilacza

Watomierz jest punktem odniesienia dla kosztu zasilania. RAPL i turbostat najlepiej pokazują, czy zmiana wpływa na procesor i stany bezczynności. Bateria może być użytecznym przybliżeniem całej płyty w laptopie, ale nie wymusza się jej rozładowania na hoście produkcyjnym tylko po to, aby uzyskać pomiar. Ten tryb może zakończyć pracę hosta po wyczerpaniu akumulatora.

Jak przygotować porównywalny i bezpieczny pomiar?

Najpierw zapisujemy warunki, a dopiero potem zmieniamy jedną rzecz. Host powinien pracować z tym samym źródłem zasilania, profilem energetycznym, stanem ekranu, siecią i zestawem urządzeń USB. Na laptopie znaczenie mają również poziom baterii, ładowarka, temperatura, zamknięcie klapy i podświetlenie. Na każdym hoście wynik zmienią C-states procesora, turbo, praca wentylatorów, zadania okresowe, cache dysków i ruch sieciowy.

  1. Wybieramy okno laboratoryjne albo VM, której bezpiecznie dotyczy eksperyment. Nie generujemy sztucznego obciążenia na produkcji bez ustalonego limitu i planu wycofania.
  2. Notujemy wersję jądra, aktywne VM, źródło zasilania i temperaturę otoczenia. Te dane są potrzebne, aby później wyjaśnić różnicę.
  3. Odczekujemy, aż host osiągnie stan ustalony, a następnie wykonujemy kilka równie długich pomiarów kontrolnych i kilka pomiarów po zmianie. Porównujemy medianę lub przedział, nie najniższy pojedynczy odczyt.
  4. Między serią kontrolną i serią po zmianie nie aktualizujemy systemu, nie podłączamy monitora ani nie uruchamiamy innych testów. Jeżeli to niemożliwe, wynik opisujemy jako niewiarygodny.

Na hoście KVM administrator root, albo użytkownik mający dostęp do libvirt i sudo, może zapisać stan wyjściowy poniższymi poleceniami. Wszystkie są odczytowe: nie zmieniają konfiguracji ani stanu VM. Sukces oznacza pełny identyfikator hosta, wersję jądra i listę domen, które później da się zestawić z wynikami pomiaru.

hostnamectl
uname -r
virsh list --all
systemctl list-timers --all

Na jakim środowisku wykonaliśmy przykład i czego on nie obejmuje?

Wszystkie liczby niżej pochodzą z pomiarów laboratoryjnych wykonanych 17 września 2026 roku. Lab to zestaw maszyn wirtualnych do testowania zmian przed wdrożeniem, opisany w artykule Lab do testów na Terraform i OpenTofu. Nazwę modelu urządzenia, adresy i nazwy domen celowo zastąpiliśmy danymi ogólnymi.

ElementStan
Hostlaptop laboratoryjny, procesor Intel 4 rdzenie / 8 wątków, 38 GiB pamięci, dysk NVMe, sieć wyłącznie po Wi-Fi
System hostaDebian 13.7, jądro 6.12
WirtualizacjaKVM (od angielskiego Kernel-based Virtual Machine, czyli wirtualizacja wbudowana w jądro), QEMU 10.0.13 (emulator, który uruchamia maszyny i udaje ich urządzenia), libvirt 11.3.0
Definicje maszynTerraform 1.16.3, provider dmacvicar/libvirt 0.8.3
Obciążenie10 działających VM: 6 bezczynnych systemów testowych, VM z pulpitem, serwer kopii zapasowych, k3s i host Dockera; jedenasta VM była wyłączona
Metodalicznik energii części platformy w procesorze (RAPL psys), moc całej płyty z akumulatora jako kontrola oraz zużycie CPU z procesów QEMU

Czego nie mierzyliśmy: poboru z gniazdka (nie mieliśmy licznika energii, więc straty zasilacza są poza pomiarem i rzeczywisty rachunek jest wyższy), wpływu zmian na czas ciężkich zadań oraz zachowania na innych laptopach. Tam, gdzie wniosek wynika z rozumowania, a nie z pomiaru, piszemy to wprost.

Kontrola laboratoryjna: przy dziesięciu uruchomionych VM narzędzie power-probe.py odczytało w oknie 10 s RAPL psys = 3,33 W. Bateria była wtedy podłączona do zasilania i nie rozładowywała się, więc nie stanowiła niezależnego pomiaru całej płyty. Ten odczyt potwierdza zakres historycznego eksperymentu, ale nie jest pomiarem poboru z gniazdka.

Jaki wynik dał eksperyment z hostem KVM przed zmianami i po nich?

Poniższy przykład pokazuje moc całej płyty laptopa odczytaną z power_now baterii w ustalonych oknach pomiarowych. Nie jest to pobór z gniazdka, więc nie obejmuje strat zasilacza. Stan „po zmianach” obejmuje wyłączenie HPET w bezgłowych VM, usunięcie nieużywanej grafiki, preferencję energetyczną procesora i wygaszanie konsoli. Porównujemy te same dziesięć VM, a nie różne zestawy maszyn.

StanMoc płyty
Host bez maszyn, ekran aktywny (klapa zamknięta)1,49-1,59 W
Host bez maszyn, konsola wygaszona1,07-1,10 W
10 maszyn, konfiguracja wyjściowa5,29-5,90 W
10 maszyn, po zmianach (także po restarcie hosta)3,25-3,39 W

Przy dziesięciu VM to spadek o około 2,2 W, czyli o około 40 procent. W skali roku oznacza to około 49 kWh przed zmianami i około 29 kWh po nich, licząc samą płytę, bez strat zasilacza. Wynik nie mówi, że każdy host KVM oszczędzi dokładnie 2,2 W. Pokazuje, że w tym zestawie największy koszt powodowała konfiguracja bezczynnych VM. Na zmianę złożyły się:

  1. wyłączenie emulowanego zegara HPET (od angielskiego High Precision Event Timer) w maszynach i usunięcie z nich grafiki, której nikt nie ogląda - największy efekt i zarazem największe zaskoczenie,
  2. preferencja energetyczna procesora ustawiona na balance_power,
  3. wygaszanie konsoli po 60 sekundach.

Ciekawsze od listy jest to, czego na niej nie ma. Wyłączanie Bluetooth, zarządzanie energią urządzeń PCI czy wyłączenie watchdoga dają ułamki wata albo nic - sprawdziliśmy to pomiarem i opisujemy niżej.

Jak odczytać RAPL na hoście bez zmiany konfiguracji?

Procesory Intela mogą udostępniać liczniki energii RAPL (od angielskiego Running Average Power Limit). Domena psys bywa użytecznym wskaźnikiem części platformy, lecz nie występuje na każdym procesorze i nie jest pomiarem z gniazdka. Na hoście KVM administrator root wykonuje poniższy blok tylko po to, aby znaleźć licznik. Polecenie jest odczytowe; powodzenie oznacza wypisanie ścieżki do energy_uj. Nie zakładamy numeru katalogu, ponieważ zależy on od platformy.

P=
for d in /sys/class/powercap/intel-rapl*:*; do
  [ "$(cat "$d/name" 2>/dev/null)" = psys ] && P="$d/energy_uj"
done
[ -n "$P" ] || { echo "brak domeny psys"; exit 1; }
echo "$P"

Licznik podaje energię w mikrodżulach, więc moc to różnica dwóch odczytów podzielona przez czas. Plik energy_uj może czytać tylko root (na naszym hoście ma uprawnienia 0400). Dostęp ograniczył commit jądra 949dd0104c49.

Na hoście KVM uruchamiamy następny blok jako root, na przykład po sudo -i. Jest odczytowy: czeka 60 sekund i wylicza średnią moc z tego okresu, nie zmieniając VM ani ustawień hosta. Dodatni wynik w watach oznacza sukces. W dłuższej serii trzeba obsłużyć przejście licznika przez zero, dlatego blok uwzględnia max_energy_range_uj.

P=
for d in /sys/class/powercap/intel-rapl*:*; do
  [ "$(cat "$d/name" 2>/dev/null)" = psys ] && P="$d/energy_uj"
done
[ -n "$P" ] || { echo "brak domeny psys"; exit 1; }

m=$(cat "${P%/energy_uj}/max_energy_range_uj")
a=$(cat "$P"); t0=$(date +%s.%N)
sleep 60
b=$(cat "$P"); t1=$(date +%s.%N)
[ "$b" -lt "$a" ] && b=$((b + m))    # licznik przeszedł przez zero

awk -v a="$a" -v b="$b" -v t0="$t0" -v t1="$t1" \
  'BEGIN { printf "%.2f\n", (b - a) / (t1 - t0) / 1000000 }'  # waty

Tak wyglądał host z dziesięcioma działającymi VM, mierzony oknami po 60 sekund. Najpierw i na końcu rejestrowaliśmy bieg jałowy. Pomiędzy nimi uruchomiliśmy obciążenie CPU wyłącznie w odizolowanej VM laboratoryjnej; nie uruchamialiśmy go na produkcji.

Stanpsys (część platformy)pakiet procesora
bieg jałowy2,93 W1,08 W
osiem wątków obciążenia20,31 W13,61 W
powrót do biegu jałowego2,92 W1,07 W

Jeżeli do kontroli czujnika potrzebne jest znane obciążenie, w testowej VM można uruchomić stress-ng już zainstalowany w tym gościu. Polecenie obciąża osiem wątków przez dwie minuty i samo kończy pracę; sukces oznacza zakończenie z kodem 0. Nie należy kopiować go na VM obsługującą ruch użytkowników bez wcześniejszego limitu i zgody na test.

sudo stress-ng --cpu 8 --timeout 120s --metrics-brief

Licznik reaguje na obciążenie i wraca do wcześniejszego zakresu, więc nadaje się do porównań „przed i po”. Jedno zastrzeżenie: psys to nie jest pełny rachunek. Obejmuje część platformy związaną z procesorem, pamięcią i przetwornicami, ale nie wszystko, co siedzi na płycie. Na tym laptopie czytał 0,7-1 W mniej niż moc faktycznie pobierana z akumulatora w tym samym oknie czasowym (dwa pomiary: 4,07 wobec 5,06 W oraz 2,59 wobec 3,28 W). Poza licznikiem zostają między innymi karta Wi-Fi, dysk, kontroler wbudowany i straty przetwornic. Pobór z gniazdka jest jeszcze wyższy o straty zasilacza.

Uwaga na btop. Monitor btop w wersji 1.3.2 pokazuje przy baterii wartość z jednostką „W”, która watami nie jest. W kodzie tej wersji liczy ją jako energy_now / 1000000, czyli podaje zapas energii akumulatora w watogodzinach. Na naszym hoście to 43,75 - i dokładnie tyle wynosi energy_full naładowanej baterii. W gałęzi głównej projektu poprawiono to na power_now. Jeżeli Twój „pobór mocy” nie zmienia się pod obciążeniem i podejrzanie przypomina pojemność baterii, to nie jest pomiar mocy.

Kiedy pomiar z baterii obejmuje całą płytę laptopa?

Żeby sprawdzić, czego psys nie widzi, potrzebny jest pomiar obejmujący więcej niż CPU. Akumulator może go dać, gdy laptop rzeczywiście pracuje z baterii i sterownik udostępnia power_now. Najpierw na hoście laboratoryjnym sprawdzamy wyłącznie dostępne dane. Ten blok jest odczytowy; sukces oznacza nazwę baterii, stan Discharging oraz liczbę w mikrowatach, którą można przeliczyć na waty.

ls /sys/class/power_supply/
B=/sys/class/power_supply/BAT0
cat "$B/status"
cat "$B/power_now"

Nie każdy laptop poda użyteczną wartość. Podczas ładowania odczyt może wynosić zero albo opisywać przepływ do baterii, a model kontrolera decyduje o częstotliwości odświeżania i dokładności. W naszym labie laptop mógł na krótko wymusić rozładowanie mimo podłączonego zasilacza przez charge_behaviour. Zgodnie z opisem interfejsu jądra wartość force-discharge oznacza właśnie taki tryb.

To nie jest polecenie dla hosta produkcyjnego ani hosta dostępnego wyłącznie przez SSH. Użyliśmy go tylko przy fizycznie dostępnym laptopie laboratoryjnym, z naładowaną baterią, podłączonym zasilaczem i timerem przywracającym normalny tryb. Skutek poleceń w kolejnym bloku jest zapisywalny: bateria zaczyna się rozładowywać. Warunkiem bezpiecznego zakończenia jest powrót auto i stan inny niż Discharging.

# Tylko fizycznie nadzorowany host laboratoryjny.
B=/sys/class/power_supply/BAT0

# Bezpiecznik: za 60 minut wróć do ładowania, nawet jeśli zerwie się SSH.
sudo systemd-run --unit=power-guard --on-active=60min \
  /bin/sh -c "echo auto > $B/charge_behaviour"

echo force-discharge | sudo tee $B/charge_behaviour
cat $B/status               # Discharging
cat $B/power_now            # mikrowaty: 5060000 to 5,06 W

echo auto | sudo tee $B/charge_behaviour
sudo systemctl stop power-guard.timer

Bezpiecznik nie jest przesadą: zapomniany tryb rozładowania przy pracy na odległość może wyłączyć host. Wynik warto potwierdzić drugą drogą, przez ubytek energy_now w czasie. W naszym pięciominutowym oknie odczyt power_now 5,06 W i wyliczenie z ubytku energii 5,03 W były zbliżone, a psys w tym samym czasie pokazał 4,07 W. To wyjaśnia, dlaczego licznik platformy nie może zastąpić pomiaru całej płyty.

Liczby w tabelach dotyczących płyty pochodzą z tego laboratoryjnego pomiaru baterii. Odczyt jest uśredniany przez układ pomiarowy i ma szum: dwa pomiary tej samej konfiguracji dały 5,29 W i 5,90 W. Dlatego przy małych różnicach korzystaliśmy także ze stabilniejszych liczników procesora, a serie układaliśmy naprzemiennie: kontrola, zmiana, kontrola, zmiana.

Moc pakietu procesora i udział stanów uśpienia odczytuje turbostat z pakietu linux-cpupower. Na hoście KVM uruchamiamy go jako root; jest odczytowy i przez dwie minuty zbiera próbkę. Sukces oznacza wiersz z wartościami mocy oraz udziałem stanów C, a brak kolumny oznacza, że platforma jej nie udostępnia.

sudo turbostat --quiet --show Busy%,PkgWatt,RAMWatt,Pkg%pc8,Pkg%pc10 \
  --interval 120 --num_iterations 1

Kolumny Pkg%pc8 i Pkg%pc10 to udział czasu, w którym cały procesor był w dwóch najgłębszych stanach uśpienia. To one tłumaczą większość wyników poniżej. Każdy pomiar trwał 90-180 sekund i w tym czasie na hoście nie uruchamialiśmy nic innego; jeden pomiar, podczas którego równolegle działał menedżer pakietów, odrzuciliśmy.

Dlaczego pusty host nie wchodził w najgłębszy stan uśpienia procesora?

Pierwszy pomiar zrobiliśmy przed instalacją czegokolwiek: 1,49-1,59 W. Procesor spędzał 92 procent czasu w stanie PC8, ale do najgłębszego PC10 nie schodził ani razu. Przyczynę znaleźliśmy w katalogu /sys/class/drm: wewnętrzny panel eDP (od angielskiego Embedded DisplayPort) miał stan DPMS (od angielskiego Display Power Management Signaling) On, a podświetlenie w sysfs stało na maksimum, mimo zamkniętej klapy. Konsola tekstowa nigdy się nie wygaszała.

Tak ma być z definicji. Dokumentacja parametrów jądra mówi o consoleblank wprost: wartość 0 wyłącza wygaszanie i jest domyślna. Na komputerze, który ma pracować bez monitora, to ustawienie po prostu kosztuje. Zmierzyliśmy to naprzemiennie, po 120 sekund:

StanMoc płytyProcesorPC8PC10
konsola wygaszona1,10 W0,20 W28%67%
panel aktywny1,49 W0,31 W92%0%
konsola wygaszona1,07 W0,19 W29%67%

Różnica wynosiła około 0,4 W, czyli ponad jedną czwartą mocy pustego hosta. Czy panel faktycznie świecił pod zamkniętą klapą, nie sprawdzaliśmy; istotne jest to, że aktywny potok wyświetlania nie pozwalał procesorowi zejść do PC10. Na bezgłowym hoście laboratoryjnym trwałą zmianę zapisaliśmy w osobnym pliku GRUB. Jest to zmiana zapisywalna, która zacznie działać po restarcie.

# /etc/default/grub.d/power.cfg
GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT consoleblank=60"
sudo update-grub
# doraźnie, bez restartu:
sudo sh -c 'TERM=linux setterm --blank force </dev/tty1 >/dev/tty1'

Administrator na hoście uruchamia potem sudo update-grub; sukces sprawdzamy po restarcie przez obecność consoleblank=60 w /proc/cmdline. Aby wycofać zmianę, usuwamy plik /etc/default/grub.d/power.cfg, uruchamiamy ponownie update-grub i planujemy restart. Wciśnięcie klawisza na lokalnej konsoli nadal ją wybudza. Przy działających VM procesor i tak rzadko dochodził do PC10 (0,5-1,1 procent czasu), więc ta zmiana ma znaczenie głównie wtedy, gdy VM są wyłączone.

Jak znaleźć bezczynną VM lub emulowane urządzenie, które zużywa CPU hosta?

Po uruchomieniu dziesięciu maszyn host pobierał 5,29-5,90 W, procesor 2,80-2,85 W, a do stanu PC8 nie schodził już wcale. Rozkład zużycia CPU na procesy QEMU wyglądał tak (100 procent to jeden wątek hosta): k3s około 50, pulpit KDE około 28 (jak się później okazało, jego serwer graficzny padał wtedy w pętli), a każdy z bezczynnych systemów testowych 5-6 procent. Te ostatnie nie robiły nic. Wewnątrz gościa było około 50 przerwań na sekundę i 100 procent bezczynności.

Proces QEMU ma osobne wątki: po jednym na każdy procesor wirtualny (vCPU, od angielskiego virtual CPU) oraz wątek główny, który obsługuje emulowane urządzenia. Na hoście KVM administrator może odczytać czas wątków jednej laboratoryjnej VM z /proc. Blok jest odczytowy; przed uruchomieniem zastępujemy deb13 unikalnym fragmentem nazwy własnej domeny. Sukces oznacza listę wątków i rosnące liczniki czasu CPU w dwóch próbkach.

p=$(pgrep -f 'guest=deb13,')
for t in /proc/$p/task/*; do
  echo "$(cat $t/comm): $(sed 's/.*) //' $t/stat | awk '{print $12+$13}')"
done
# dwa odczyty w odstępie, różnica / (CLK_TCK * sekundy) = udział CPU

Nazwę wątku obcinamy przez sed 's/.*) //', bo może zawierać spację: wątki procesorów wirtualnych QEMU nazywają się CPU 0/KVM, CPU 1/KVM i tak dalej. Po obcięciu czas użytkownika (utime) i czas w jądrze (stime) są w polach 12 i 13. Pierwsza wersja tego artykułu liczyła awk '{print $14+$15}' na całej linii i dla takich wątków sumowała niewłaściwe pola, pomijając stime. Na labie 29 września 2026 roku linia wątku vCPU zaczynała się tak: 3463 (CPU 0/KVM) S 1 3451 3451 0 -1 138412224 32930 0 0 0 118638 22844. awk traktuje (CPU i 0/KVM) jako dwa pola, więc zamiast utime i stime (118638 i 22844 tików) dodawał pole cmajflt (tu 0) i utime.

Wynik dla bezczynnego Debiana 13: oba vCPU po 0,3 procent, wątek grafiki 0,3 procent, a wątek główny 4,65 procent. Wartości dla vCPU pochodzą z pierwszej wersji skryptu, więc obejmują tylko czas użytkownika i mogą być zaniżone. Nazwy wątku grafiki z tamtego pomiaru nie zapisaliśmy, więc nie wiemy, czy dotyczyło go to samo. Wątek główny nazywa się qemu-system-x86, bez spacji, więc jego wynik jest poprawny. Maszyna prawie nie pracowała, pracował emulator. Co dokładnie robił, pokazał strace:

Do krótkiej diagnozy laboratorium użyliśmy także strace. Dołączenie śledzenia nie zmienia konfiguracji VM, ale samo obserwowanie procesu może nieznacznie wpłynąć na jego czas pracy; dlatego traktujemy je jako wskazówkę, a nie pomiar mocy. Nie dołączamy go do procesu produkcyjnego w czasie incydentu bez oceny wpływu.

$ sudo timeout 10 strace -c -p $p
% time     seconds  usecs/call     calls    errors syscall
 76,99    0,062275          12      5005           ppoll
 22,94    0,018559           3      5000           ioctl

$ sudo timeout 2 strace -tt -p $p      # lista deskryptorów skrócona
13:20:36.170975 ioctl(15, KVM_IRQ_LINE_STATUS, 0x7fff134cf4a0) = 0
13:20:36.171126 ioctl(15, KVM_IRQ_LINE_STATUS, 0x7fff134cf4a0) = 0
13:20:36.171170 ppoll([...], 79, {tv_sec=0, tv_nsec=0}, NULL, 8) = 0 (Timeout)
13:20:36.171242 ppoll([...], 79, {tv_sec=0, tv_nsec=3649479}, NULL, 8) = 0 (Timeout)
13:20:36.175092 ioctl(15, KVM_IRQ_LINE_STATUS, 0x7fff134cf4a0) = 0
13:20:36.175189 ioctl(15, KVM_IRQ_LINE_STATUS, 0x7fff134cf4a0) = 0

Co 4 milisekundy, czyli 250 razy na sekundę, wątek główny budzi się i dwa razy zmienia stan linii przerwania. Tak wygląda zegar sprzętowy emulowany w przestrzeni użytkownika. Po stronie gościa odpowiedź była w sysfs: jako zapasowe źródło zdarzeń zegarowych (/sys/devices/system/clockevents/broadcast/current_device) jądro wybrało hpet.

Jak emulowany zegar HPET wybudzał QEMU?

HPET to zegar sprzętowy, który QEMU emuluje programowo. Nasze domeny miały w definicji tylko <clock offset='utc'/>, bez żadnych ustawień zegarów, więc HPET był obecny. Tymczasem virt-install 4.1.0 dla gościa Debian 12 generuje taki blok (sprawdzone przez --print-xml, które niczego nie tworzy):

<clock offset="utc">
  <timer name="rtc" tickpolicy="catchup"/>
  <timer name="pit" tickpolicy="delay"/>
  <timer name="hpet" present="no"/>
</clock>

RTC (od angielskiego Real-Time Clock) i PIT (od angielskiego Programmable Interval Timer) to dwa pozostałe zegary, a ich opcje opisuje rozdział o zegarach w dokumentacji formatu domeny libvirt. Ponieważ ten blok zmienia trzy rzeczy naraz, rozdzieliliśmy je osobnym testem na dwóch VM Ubuntu, obok trzeciej z pełną poprawką:

WariantCPU hostaWywołania systemowe QEMU na 10 s
HPET obecny, zmienione tylko tickpolicy5,78%10 005
wyłączony tylko HPET0,20%68
pełny blok (kontrola)0,98%-

Odpowiada za to sam HPET. Z 10 tysięcy wywołań systemowych w 10 sekund zostało 68. Jeśli VM powstają własnym XML, skryptem albo narzędziem do infrastruktury jako kodu, najpierw sprawdzamy faktycznie wdrożoną definicję. Na hoście KVM poniższe polecenie jest odczytowe; zastępujemy deb13 nazwą domeny. Sukces oznacza pokazanie bloku <clock>, który należy porównać z definicją źródłową.

virsh dumpxml deb13 | grep -A4 '<clock'

Czy bezgłowa VM ma grafikę, której nikt nie używa?

W tym samym wyniku był jeszcze wątek grafiki. Nasze maszyny testowe nie mają pulpitu i kod nie deklaruje im żadnej grafiki. A jednak każda miała protokół SPICE (od angielskiego Simple Protocol for Independent Computing Environments, czyli system zdalnego ekranu dla maszyn wirtualnych) i kartę cirrus. Źródło widać w kodzie providera: domyślna definicja domeny w pliku domain_def.go wersji 0.8.3 zawiera już grafikę SPICE, a dokumentacja zasobu domeny nazywa cirrus domyślną kartą libvirt. Brak bloku graphics w kodzie nie oznacza więc braku grafiki.

Trzy bezczynne Debiany zmierzyliśmy w tych samych 120 sekundach:

MaszynaHPETSPICE i karta graficznaCPU hosta
deb11taktak5,09%
deb12nietak1,76%
deb13nienie0,06%

Po wyłączeniu HPET grafika wciąż kosztowała około 1,7 punktu procentowego rdzenia na maszynę. Bez obu dodatków bezczynna maszyna przestaje być widoczna w zużyciu hosta.

Jak bezpiecznie przetestować zmianę definicji VM w Terraform?

Provider 0.8.3 nie ma argumentu do ustawiania zegarów ani do usunięcia domyślnej grafiki. Ma za to blok xml z polem xslt: arkusz XSLT (od angielskiego XSL Transformations, czyli język przekształcania dokumentów XML), który przekształca wygenerowaną definicję, zanim trafi do libvirt. Mieliśmy już taki arkusz do poprawiania formatu dysków danych, więc zamieniliśmy go w szablon z jedną zmienną. Jego aktualną wersję można obejrzeć w repozytorium, w pliku terraform/modules/vm-group/domain.xsl.tftpl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" omit-xml-declaration="yes"/>

  <!-- kopiuj wszystko bez zmian -->
  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
  </xsl:template>

  <!-- dyski *.qcow2: provider wpisuje każdy plik jako raw -->
  <xsl:template match="/domain/devices/disk[@device='disk'][source[contains(@file, '.qcow2')]]/driver/@type">
    <xsl:attribute name="type">qcow2</xsl:attribute>
  </xsl:template>

  <!-- zegary bez HPET, niezależnie od tego, co wygenerował provider -->
  <xsl:template match="/domain">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()[not(self::clock)]"/>
      <clock offset="utc">
        <timer name="rtc" tickpolicy="catchup"/>
        <timer name="pit" tickpolicy="delay"/>
        <timer name="hpet" present="no"/>
      </clock>
    </xsl:copy>
  </xsl:template>
%{ if headless ~}

  <!-- maszyny bez pulpitu: bez grafiki -->
  <xsl:template match="/domain/devices/graphics"/>
  <xsl:template match="/domain/devices/video"/>
  <xsl:template match="/domain/devices/audio"/>
%{ endif ~}
</xsl:stylesheet>

W zasobie domeny arkusz dostaje każda maszyna, a zmienna mówi, czy ma pulpit:

resource "libvirt_domain" "vm" {
  # ...
  xml {
    xslt = templatefile("${path.module}/domain.xsl.tftpl", {
      headless = each.value.profile != "desktop"
    })
  }
}

Maszyna z pulpitem zachowuje SPICE i swoją kartę graficzną, ale też traci HPET. Konsola szeregowa działa u wszystkich jak wcześniej. Przed wdrożeniem sprawdziliśmy wynik szablonu poleceniem xsltproc na prawdziwych definicjach trzech maszyn, a potem virt-xml-validate wobec schematu libvirt.

Cztery rzeczy, które warto wiedzieć przed apply:

  • Zmiana xslt odtwarza domenę. Plan pokazał must be replaced dla wszystkich jedenastu maszyn. Dyski są osobnymi zasobami i zostały nietknięte, ale sama maszyna jest usuwana i tworzona od nowa. Wyłączyliśmy więc maszyny łagodnie przed apply, a nie po.
  • Dotyczy to każdej zmiany treści szablonu, także komentarza. Do xslt trafia wyrenderowany tekst, a nie znaczenie arkusza.
  • Potrzebny jest xsltproc na maszynie, na której działa Terraform. Bez niego apply kończy się błędem exec: "xsltproc": executable file not found in $PATH.
  • Konfiguracja startowa nie uruchomiła się ponownie. Dysk z danymi cloud-init był ten sam, więc każdy gość ma jeden katalog instancji, a moduły jednorazowe zgłaszają w logu already ran (sprawdzone na dziewięciu maszynach, z pominięciem wyłączonej i tej bez agenta).

Tak wyglądał efekt przy dziesięciu maszynach, jeszcze przy domyślnej preferencji energetycznej procesora:

Moc płytyProcesor (RAPL)Zajętość CPU hostaCPU procesów QEMUPC8
przed5,29-5,90 W2,80-2,85 W23%148-150%0%
po4,17 W1,71 W7,3%47%23%

Jedno zastrzeżenie, żeby liczby nie mówiły więcej, niż wiemy. W pomiarach „przed” dwie maszyny Ubuntu były świeżo po pierwszym starcie i zużywały po 17-18 procent CPU, a później 0-1 procent. Część spadku w kolumnie QEMU (około 35 z około 100 punktów) wynika więc z tego, a nie z poprawki. Czysty efekt samej poprawki pokazują tabele z dwóch poprzednich sekcji: około 5 punktów procentowych rdzenia na każdą bezczynną maszynę.

Kiedy preferencja balance_power ma sens dla procesora hosta?

Procesor pracuje pod sterownikiem intel_pstate w trybie aktywnym, z HWP (od angielskiego hardware-managed P-states, czyli sprzętowym sterowaniem częstotliwością). W tym trybie jądro nie wybiera częstotliwości samo, tylko podpowiada procesorowi preferencję - EPP (od angielskiego Energy-Performance Preference). Opisuje to dokumentacja intel_pstate. Domyślnie na naszym hoście było balance_performance. Zmierzyliśmy balance_power przed poprawką XSLT i po niej:

10 maszynEPPMoc płytyProcesorŚrednia częstotliwość w pracy
przed poprawkąbalance_performance5,29-5,90 W2,80-2,85 W1084 MHz
przed poprawkąbalance_power4,98 W1,89 W919 MHz
po poprawcebalance_performance4,17 W1,71 W1312 MHz
po poprawcebalance_power3,25-3,39 W1,12-1,20 W1014-1049 MHz

Na samym procesorze pobór spadł o około 0,6 W. Kosztem jest nieco dłuższa praca przy niższej częstotliwości: zajętość CPU hosta wzrosła z 7,3 do 9,0-9,1 procent. Tryb turbo pozostaje dostępny. Czasu ciężkich zadań, na przykład aktualizacji wszystkich VM naraz, nie mierzyliśmy. Dlatego nie ustawiamy tej preferencji bez wcześniejszego benchmarku ważnego dla danego hosta.

Najpierw na hoście KVM odczytujemy bieżące wartości. Ten blok jest odczytowy; sukces oznacza jednolitą wartość EPP oraz governor dla wszystkich CPU albo pokazuje, że host wymaga osobnej analizy.

cat /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference | sort | uniq -c
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor | sort | uniq -c

Na odizolowanym hoście laboratoryjnym można następnie wykonać tymczasowy test. Poniższy blok jest zapisywalny i zmienia preferencję wszystkich CPU; przed wykonaniem zapisujemy pierwotną wartość z poprzedniego odczytu. Powodzenie oznacza, że drugi odczyt zwraca balance_power. Aby wycofać test, zamiast balance_power zapisujemy wcześniej odnotowaną wartość, na przykład balance_performance.

echo balance_power | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference
# wycofanie przykładu:
echo balance_performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference

Na stałe ustawia ją jedna linia dla systemd-tmpfiles, ale dopiero po pomiarze i benchmarku. Reguła w zapisuje wartość do pliku przy starcie i przyjmuje wzorzec ścieżki, więc obejmuje wszystkie wątki naraz. Na badanym hoście nie było TLP ani demona profili energetycznych, które mogłyby ją nadpisać.

Preferencję da się tak zmieniać przy governorze powersave, który na naszym hoście był ustawiony na wszystkich ośmiu wątkach. Przy governorze performance sterownik intel_pstate z HWP wpisuje do EPP wartość 0 i, według tej samej dokumentacji, odrzuca zapis innej wartości przez sysfs, więc reguła poniżej nic by nie zmieniła. Governor sprawdzamy razem z wynikiem reguły.

# /etc/tmpfiles.d/power.conf
w /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference - - - - balance_power
sudo systemd-tmpfiles --create /etc/tmpfiles.d/power.conf
cat /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference | sort | uniq -c
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor | sort | uniq -c    # oczekiwane: powersave

Jak oszacować wpływ jednej VM bez mylenia go z poborem całego hosta?

Po obu zmianach sprawdziliśmy, gdzie zostały waty. Kolejne grupy VM wstrzymywaliśmy poleceniem virsh suspend: VM zachowuje pamięć i stan, ale jej procesory przestają pracować. To jest zmiana stanu usługi, dlatego wykonaliśmy ją wyłącznie na VM laboratoryjnych, po sprawdzeniu że nikt z nich nie korzysta. Na produkcji bezpieczniejszy jest najpierw odczyt CPU QEMU, a takie wstrzymanie wymaga uzgodnionego okna.

# Host KVM, tylko VM laboratoryjna:
virsh suspend nazwa-vm
virsh domstate nazwa-vm      # oczekiwane: paused
virsh resume nazwa-vm        # wycofanie; oczekiwane: running
StanMoc płytyProcesorRóżnica
wszystkie 10 maszyn działa3,39 W1,12 W
wstrzymane 6 systemów testowych3,20 W1,08 Wokoło 0,2 W za sześć
+ wstrzymana maszyna z k3s2,36 W0,72 Wokoło 0,85 W
+ wstrzymany pulpit KDE1,52 W0,31 Wokoło 0,85 W
+ wstrzymane Proxmox Backup Server i Docker1,23 W0,21 Wokoło 0,3 W
wszystkie wznowione (kontrola)3,25 W1,12 W

W tym labie sześć bezczynnych systemów testowych kosztuje razem tyle, ile mieści się w szumie pomiaru. Nie dowodzi to, że każda bezczynna VM jest bezpłatna energetycznie. Resztę poboru powodowały głównie dwie VM. Pierwsza to k3s: 32-36 procent wątku hosta, a wewnątrz gościa najwięcej zużywał proces k3s-server.

Druga, pulpit KDE, okazała się awarią, a nie kosztem pulpitu. Gdy sprawdziliśmy procesy w środku, serwer X padał tam w pętli, około 11 razy na minutę (szczegóły w artykule Lab na osobnym hoście). Po naprawie zmierzyliśmy tę maszynę jeszcze raz, w trzech stanach po 120 sekund:

Stan (10 maszyn, pulpit po naprawie)Moc płytyProcesorCPU hosta dla pulpitu
pulpit z ekranem logowania3,21 W1,02 W39%
pulpit wstrzymany3,00 W0,84 W1%
pulpit działa, menedżer logowania zatrzymany2,82 W0,85 W3%

Działający ekran logowania zajmuje więcej CPU niż pętla awarii (39 procent wątku wobec 11-22), a mimo to kosztuje mniej energii: około 0,2-0,4 W wobec około 0,85 W. Wyjaśnienie, którego nie weryfikowaliśmy osobno: równomierna praca pozwala procesorowi utrzymać niską częstotliwość, a ciągłe uruchamianie serwera od nowa - nie. Dla maszyny z pulpitem, która przez większość czasu czeka, najtańszy jest stan bez menedżera logowania, uruchamianego dopiero wtedy, gdy pulpit jest potrzebny.

Które zmiany nie obniżyły poboru energii w tym labie?

Dwie zmiany wyglądały obiecująco i obie odrzuciliśmy po pomiarze:

ZmianaWynik
kvm.halt_poll_ns=0 - host nie czeka aktywnie na wybudzenie bezczynnego procesora wirtualnego (domyślnie na tym jądrze 200 000 ns)procesor 2,80 W wobec 2,80-2,85 W, zużycie CPU procesów QEMU bez zmian
zarządzanie energią urządzeń PCI w czasie pracy (power/control=auto, sugestia powertop) plus kernel.nmi_watchdog=0, czyli wyłączony watchdog NMI (od angielskiego Non-Maskable Interrupt)dwie pary kontrola/zmiana: 3,30/3,47 W i 3,30/3,29 W, procesor bez zmian

Pierwsza nie zadziałała, bo problemem nie było czekanie procesorów wirtualnych, tylko wątek główny emulatora - co widać było już w podziale na wątki. Druga może dawać coś poniżej progu naszego pomiaru, ale nie potrafimy tego wykazać, więc nie wdrażamy. Kartę Wi-Fi celowo wyłączyliśmy z testu zarządzania energią, bo to jedyne łącze hosta.

Czego świadomie nie zmienialiśmy, mimo że narzędzia to sugerowały?

Świadomie nie wdrażaliśmy:

  • wyłączania Bluetooth, kamery, czytnika linii papilarnych, czytnika kart i usypiania USB - to ułamki wata za cenę dodatkowej konfiguracji,
  • wydłużenia interwału zapisu buforów na dysk (vm.dirty_writeback_centisecs=1500, kolejna sugestia powertop) - dłuższe okno utraty danych przy awarii, bez mierzalnego zysku przy tym obciążeniu,
  • wyłączenia trybu turbo - dotyczy tylko szczytów, a wydłużyłoby ciężkie zadania,
  • KSM (od angielskiego Kernel Samepage Merging, czyli scalania identycznych stron pamięci) - samo skanowanie pamięci zużywa CPU, a pamięci mamy dość,
  • przejścia z Wi-Fi na kabel - nie mierzyliśmy, wymaga okablowania.

Bez żadnych zmian dobrze działały już:

  • dysk NVMe - APST (od angielskiego Autonomous Power State Transition) był włączony, a po 100 ms bezczynności dysk przechodzi do stanu o poborze 4 mW (nvme get-feature z pakietu nvme-cli),
  • oszczędzanie energii karty Wi-Fi (iw dev ... get power_save zwracało on),
  • ASPM (od angielskiego Active State Power Management) w stanie L1 na łączu dysku i większości portów PCI Express,
  • usypianie kodeka dźwięku po 10 sekundach,
  • pamięć - liczniki RAPL pokazywały 0,4-0,5 W bez względu na stan, więc przy 38 GiB nie było tu czego odzyskiwać.

Jak sprawdzić, czy ustawienia przetrwały restart hosta?

Po wdrożeniu zrestartowaliśmy host. Po starcie /proc/cmdline zawierał consoleblank=60, wszystkie osiem wątków miało preferencję balance_power, panel przeszedł w DPMS Off po minucie, a systemctl --failed był pusty. Poprawka XSLT znajduje się w definicjach domen, więc nie zależy od restartu hosta. Pomiar po ponownym uruchomieniu VM dał 3,39 W, a procesor 1,15 W, czyli zakres zbliżony do stanu przed restartem.

Co wynika z wyniku, a czego pojedynczy odczyt nie dowodzi?

Wynik 5,29-5,90 W → 3,25-3,39 W pokazuje, że w tym samym laboratorium konfiguracja bezczynnych VM miała większe znaczenie niż drobne ustawienia hosta. Nie dowodzi, że każda zmiana HPET da 40 procent oszczędności, że identycznie zachowa się inny procesor ani że tyle samo spadnie rachunek za energię. Pomiary baterii nie obejmują strat zasilacza, a RAPL nie obejmuje automatycznie wszystkich urządzeń hosta.

Różnicę mniejszą od rozrzutu serii traktujemy jako brak potwierdzonego efektu. Jeżeli moc rośnie po godzinie pracy, przyczyną może być temperatura, turbo, wentylator, cache, zadanie okresowe lub bateria, a nie ostatnia zmiana w XML. Dlatego wiarygodny wniosek wymaga powrotu do stanu kontrolnego albo powtórzenia pomiaru w innym dniu. Dopiero zgodny wynik z kilku serii uzasadnia trwałą zmianę konfiguracji.

Jak przeprowadzić kolejny pomiar hosta KVM krok po kroku?

  1. Wybierz źródło danych zgodne z pytaniem: watomierz dla całego poboru, BMC/IPMI dla serwera, bateria tylko w kontrolowanym labie laptopa, a RAPL dla trendu CPU/platformy.
  2. Sprawdź, czy host z maszynami schodzi w ogóle do głębokich stanów uśpienia. Zero procent w PC8 to sygnał, że coś budzi procesor bez przerwy.
  3. Rozbij zużycie CPU procesów QEMU na wątki. Wysoki wątek główny przy bezczynnych procesorach wirtualnych to emulowane urządzenie, a nie praca gościa.
  4. Sprawdź blok <clock> w definicjach maszyn. Brak <timer name='hpet' present='no'/> kosztował nas około 5 procent rdzenia na każdą bezczynną maszynę.
  5. Sprawdź, czy maszyny bez pulpitu nie mają grafiki, której nie zamawiałeś. Brak bloku w kodzie nie znaczy brak urządzenia.
  6. Pamiętaj, że zmiana arkusza XSLT w providerze libvirt odtwarza domeny. Wyłącz maszyny przed apply.
  7. Na hoście bez monitora ustaw consoleblank, bo jądro domyślnie nie wygasza konsoli.
  8. Preferencję balance_power ustaw trwale i wiedz, jak ją podnieść na czas ciężkiej pracy.
  9. Zanim zaczniesz wyłączać drobne urządzenia, sprawdź, które maszyny kosztują najwięcej. U nas dwie z dziesięciu brały połowę.
  10. Każdą zmianę, która nie dała mierzalnego efektu, cofnij, zamiast zostawiać „bo nie szkodzi”.

Najbardziej pouczające w tym ćwiczeniu jest to, gdzie była oszczędność. Laptop sam w sobie był już dobrze ustawiony: dysk, Wi-Fi i łącza PCI Express zasypiały bez naszej pomocy. Waty uciekały w definicjach maszyn, które nikt nie oglądał, bo były generowane przez narzędzie. Mierzenie na poziomie wątków emulatora zajęło kwadrans i dało więcej niż wszystkie ustawienia hosta razem.

Chcesz wiedzieć, na co naprawdę idzie moc Twoich serwerów?

Mierzymy, zanim zmienimy: od liczników procesora po wątki emulatora. Pomagamy utrzymać infrastrukturę wirtualną, która nie płaci za urządzenia, których nikt nie używa.