Konrad Kowalski (rootsher)Principal Platform & Reliability Architect000010100011111001110100010101000000101011101101

PSI: jak Linux mierzy realny CPU pressure

data
kategoria
Observability
także w
Capacity & Performance
czytanie
4 min / 801 słów

W poprzednim artykule doszliśmy do sytuacji:

text
task ma pracę
        |
        v
task jest runnable
        |
        v
CPU wykonuje inne taski
        |
        v
task czeka

To właśnie CPU contention.

Problem z klasycznym CPU usage jest taki, że pokazuje głównie czas, który workload dostał.

Nie pokazuje bezpośrednio czasu, który workload stracił, ponieważ chciał się wykonywać, ale nie miał dostępu do CPU.

Linux ma do tego osobny mechanizm:

text
PSI

czyli:

text
Pressure Stall Information

PSI mierzy czas, przez który taski nie mogły wykonywać pracy z powodu niedostępności zasobu. Linux udostępnia PSI dla CPU, memory i I/O; tutaj interesuje nas wyłącznie CPU.


CPU usage i CPU pressure odpowiadają na inne pytania

Załóżmy workload:

text
CPU usage = 2 CPU

Ta liczba mówi:

text
workload otrzymał około 2 CPU czasu wykonania

Nie mówi jednak, czy chciał:

text
2 CPU

czy:

text
8 CPU

Jeżeli workload chciał 8 CPU, ale node był przeciążony i scheduler dawał mu tylko 2 CPU, różnica materializuje się jako czekanie runnable tasków.

PSI próbuje uchwycić właśnie ten czas.

Możemy więc myśleć:

text
CPU usage:
ile pracy scheduler pozwolił wykonać

CPU pressure:
jak często workload miał pracę,
ale brak CPU opóźniał jej wykonanie

To dwie różne osie obserwacji systemu.


/proc/pressure/cpu

Na poziomie całego systemu Linux udostępnia CPU PSI przez:

text
/proc/pressure/cpu

Przykładowy wynik:

text
some avg10=12.50 avg60=8.20 avg300=3.10 total=91827364
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

Mamy tutaj dwa podstawowe pojęcia:

text
some
full

oraz cztery wartości dla każdego z nich:

text
avg10
avg60
avg300
total

some

Dla CPU some oznacza czas, w którym co najmniej jeden task był opóźniany przez brak dostępnego CPU.

Załóżmy cztery CPU:

text
CPU 0 -> A
CPU 1 -> B
CPU 2 -> C
CPU 3 -> D

E -> runnable, czeka

System nadal wykonuje pracę.

Cztery taski są running.

Ale E chciał również dostać CPU i nie mógł.

Mamy więc CPU pressure:

text
some

To bardzo istotne.

PSI nie pyta:

text
czy CPU robi cokolwiek?

Pyta:

text
czy brak CPU opóźnia jakiś runnable workload?

Jak czytać avg10

Załóżmy:

text
some avg10=25.00

Nie oznacza to:

text
CPU utilization = 25%

Ani:

text
25% tasków czeka

Oznacza mniej więcej:

W ostatnim 10-sekundowym oknie workload doświadczał przynajmniej częściowego CPU stall przez około 25% czasu.

Czyli w uproszczeniu:

text
10 sekund × 25%
=
2.5 sekundy

przez około 2.5 sekundy tego okna co najmniej jeden task czekał na CPU.

To zupełnie inna informacja niż CPU utilization.


avg10, avg60, avg300

PSI raportuje trzy moving averages:

text
avg10
avg60
avg300

czyli trendy z około:

text
10 sekund
60 sekund
300 sekund

Przykład:

text
some avg10=42.00 avg60=12.00 avg300=3.00

To sugeruje gwałtowne, świeże pogorszenie sytuacji.

Ostatnie kilka minut wyglądały stosunkowo spokojnie:

text
avg300 = 3%

ale ostatnie kilkanaście sekund:

text
avg10 = 42%

wygląda już zupełnie inaczej.

Możemy mieć też odwrotną sytuację:

text
avg10=1.00
avg60=12.00
avg300=20.00

czyli pressure był wcześniej duży, ale właśnie spada.

Linux utrzymuje te wartości jako krótkoterminowe, średnioterminowe i dłuższe trendy pressure.


total

Obok średnich mamy:

text
total

Przykład:

text
some ... total=91827364

total jest skumulowanym stall time wyrażonym w mikrosekundach.

Nie jest moving average.

To licznik rosnący w czasie.

Dzięki temu możemy policzyć przyrost pomiędzy dwoma momentami.

Przykład:

text
t1:
total = 1000000

t2:
total = 1300000

Różnica:

text
300000 µs
=
300 ms

Czyli pomiędzy pomiarami doszło około 300 ms dodatkowego CPU stall time.

total jest szczególnie przydatne, jeśli chcemy budować własne rate'y albo obserwować krótkie zdarzenia, które mogą zostać mocno wygładzone przez avg10.


A czym jest full?

PSI posiada również:

text
full

Ogólna definicja full oznacza sytuację, w której wszystkie non-idle taski są jednocześnie stalled i workload jako całość nie wykonuje produktywnej pracy.

Dla memory i I/O jest to bardzo użyteczna informacja.

Dla CPU istnieje jednak ważny szczegół.

Na poziomie całego systemu:

text
/proc/pressure/cpu

CPU full nie ma sensownej interpretacji odpowiadającej memory czy I/O. Kernel dokumentuje, że system-wide CPU full jest raportowane jako zero ze względów kompatybilności.

Dlatego typowy system-level wynik wygląda:

text
some avg10=18.20 avg60=10.40 avg300=5.60 total=...
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

I nie oznacza to:

text
nie ma poważnego CPU pressure

Dla system-wide CPU najważniejsze jest przede wszystkim:

text
some

PSI na poziomie cgroup

PSI nie musi być mierzone tylko globalnie.

W cgroup v2 każda cgroup może posiadać:

text
cpu.pressure

czyli pressure policzone dla tasków należących do konkretnej grupy.

Przykład:

text
/sys/fs/cgroup/.../cpu.pressure

może zwrócić:

text
some avg10=31.20 avg60=15.70 avg300=8.30 total=...
full avg10=7.50 avg60=3.20 avg300=1.10 total=...

To dużo ciekawsze niż wyłącznie globalny pressure node'a.

Node może mieć wiele workloadów, ale problem może dotyczyć tylko konkretnej części hierarchii cgroups.

Dzięki temu możemy pytać:

text
czy cały host ma CPU pressure?

oraz osobno:

text
czy ten konkretny workload doświadcza CPU pressure?

some=0 kontra some=50

Rozważmy dwa przypadki.

Przypadek A

text
CPU usage = 100%
CPU PSI some = 0%

CPU pracuje cały czas.

Ale runnable workload praktycznie nie czeka na dostęp do procesora.

To może być całkowicie zdrowa sytuacja.

Na przykład jeden CPU-bound worker wykorzystuje cały przypisany CPU:

text
RUN RUN RUN RUN RUN

Nie ma kolejki konkurujących tasków.

Przypadek B

text
CPU usage = 100%
CPU PSI some = 50%

CPU również pracuje cały czas.

Ale przez znaczną część czasu przynajmniej jeden dodatkowy task chciał się wykonywać i nie mógł.

Czyli:

text
running:
A B C D

waiting:
E F G

Usage w obu przypadkach może wyglądać podobnie.

Pressure jest zupełnie inne.


Dlaczego PSI jest tak użyteczne

Klasyczne utilization świetnie odpowiada na pytanie:

text
jak mocno wykorzystujemy CPU?

Ale przy capacity problems ciekawsze pytanie brzmi:

text
czy brak CPU już przeszkadza workloadowi w wykonywaniu pracy?

PSI odpowiada właśnie na to drugie.

To pozwala rozróżnić:

text
CPU jest zajęte

od:

text
CPU jest zasobem ograniczającym wykonywanie workloadu

Te dwie rzeczy nie są równoważne.


Przykład

Załóżmy node z:

text
8 CPU

i workload generujący dokładnie:

text
8 runnable CPU-bound threads

W idealnym przypadku:

text
CPU 0 -> T1
CPU 1 -> T2
...
CPU 7 -> T8

CPU utilization:

text
~100%

ale niewiele tasków musi czekać.

Pressure może być niewielkie.

Teraz dodajmy kolejne osiem runnable threadów:

text
T9 .. T16

Capacity pozostaje:

text
8 CPU

Demand rośnie w okolice:

text
16 CPU

CPU utilization nadal wynosi:

text
~100%

Nie może przecież wynosić 200% całego node'a.

Ale teraz część tasków stale czeka.

To właśnie zobaczymy w rosnącym:

text
CPU PSI some

Usage praktycznie przestało być w tym miejscu użyteczne do określania skali contention.

PSI nadal się zmienia.


CPU PSI w Kubernetes

Na współczesnym Kubernetes PSI może być zbierane przez kubelet na poziomie:

text
node
pod
container

Kubernetes udostępnia te dane przez Summary API oraz endpoint metryk kubeleta. W Kubernetes 1.36 obsługa PSI przez kubelet jest już funkcją stabilną.

Sam mechanizm nadal pochodzi jednak z Linuksa.

Kubernetes tylko zbiera i wystawia dane wygenerowane przez PSI w kernelu.


Model mentalny

CPU utilization:

text
ile czasu CPU wykonywało pracę

CPU PSI:

text
jak często brak CPU opóźniał runnable pracę

Najważniejszy sygnał:

text
some

oznacza:

text
przez tę część czasu
co najmniej jeden task chciał CPU,
ale musiał czekać

Natomiast:

text
avg10
avg60
avg300

pokazują trend pressure w różnych oknach czasowych, a:

text
total

jest skumulowanym stall time.

Dlatego PSI nie jest kolejną metryką utilization.

Mierzy coś znacznie ciekawszego:

text
czas stracony z powodu niedostępności CPU