W
iększość użytkowników tego systemu nie ma pojęcia, co naprawdę oznacza średnie obciążenie systemu Linux. Bardzo często jest ono kojarzone tylko z wartością obciążenia procesora (CPU), a niestety tak nie jest. W rzeczywistości wartość ta reprezentuje średnią liczbę procesów (wątków), które w danym momencie: 1) wykonują pracę na procesorze 2) czekają w kolejce na dostęp do procesora 3) czekają na operacje wejścia / wyjścia (I/O) i nie tylko. I to właśnie trzeci punkt jest najważniejszy i odróżnia Linuksa od wielu systemów Uniksowych. Dlatego wydając na przykład polecenie uptime, gdy wyświetlą się nam trzy średnie wartości dla 1, 5 i 15 minut:
05:58:34 up 12 days, 7:56, 1 user, load average: 4.18, 3.13, 2.04Nie możemy jednoznacznie powiedzieć, iż dla systemu z dwoma wątkami procesora system jest przeładowany zadaniami obliczeniowymi.
Napewno możemy stwierdzić, iż jeżeli średnie wynoszą 0.00 lub 0.01 to system pozostaje bezczynny. jeżeli średnia minutowa (1’sza wartość) jest wyższa niż 5’cio lub 15-minutowa to obciążenie systemu rośnie. jeżeli średnia minutowa (1’sza wartość) jest niższa niż 5’cio lub 15-minutowa to obciążenie systemu maleje. W celu bliższego zrozumienia load average, musimy spojrzeć na stany procesów w jądrze systemu, które są brane pod uwagę przy sumowaniu tych wartości. Pierwszą grupą rzeczywiście są procesy aktualnie używające procesor lub czekają w kolejce do niego (TASK_RUNNING). Jednak drugą grupą są zablokowane procesy, czekające na coś, co nie jest procesorem (TASK_UNINTERRUPTIBLE – oznaczane w top/htop lub ps literą D). Do tej grupy może zostać zaliczone oczekiwanie na: operacje dyskowe, operacje sieciowe, odpowiedzi ze sterowników, czy zwalnianie blokad muteksów czy semaforów. Dlatego przyjmując założenie, iż jeżeli wartości te są wyższe niż liczba dostępnych wątków / rdzeni procesora możemy mieć problem z wydajnością, ale daje to nam to tylko bardziej holistyczny obraz zapotrzebowania na zasoby systemowe. Wysoki load average nie oznacza automatycznie, ze musimy zdobyć szybszy procesor – oznacza, iż musimy sprawdzić (np. metodą USE), co generuje obciążenie, ponieważ może to być: obliczanie zbyt wielu zadań, nienadążanie z zapisem / odczytem danych, saturacja sieci, błąd sterownika lub blokujące się procesy.
Rys historyczny:
Historia definicji load average zaczyna się długo przed powstaniem systemu Linux. W sierpniu 1973 roku opublikowano RFC 546 dla systemu operacyjnego TENEX, w którym definicja odnosi się wyłącznie do zapotrzebowania na zasoby CPU. Liczono w nim tylko procesy w stanie gotowości do działania (ang. runnable), a sam wskaźnik miał służyć do podejmowania decyzji przez dyspozytora / planistę (ang. scheduler), gdzie umieścić nowe zadania. Nie była to metryka służąca do monitorowania ogólnego „zmęczenia” systemu. W swoich początkach (jądro 0.99) Linux również trzymał się tej klasycznej definicji. Kod źródłowy z tamtego okresu (kernel/sched.c) pokazuje, iż do obliczania load average brano pod uwagę tylko liczbę działających procesów (nr_running). Jednak w październiku 1993 roku programista Matthias Urlichs zaproponował łatkę (patch), która zmieniła definicję na zawsze:
From: Matthias Urlichs <urlichs@smurf.sub.org> Subject: Load average broken ? Date: Fri, 29 Oct 1993 11:37:23 +0200 The kernel only counts "runnable" processes when computing the load average. I don't like that; the problem is that processes which are swapping or waiting on "fast", i.e. noninterruptible, I/O, also consume resources. It seems somewhat nonintuitive that the load average goes down when you replace your fast swap disk with a slow swap disk... Anyway, the following patch seems to make the load average much more consistent WRT the subjective speed of the system. And, most important, the load is still zero when nobody is doing anything. ;-) --- kernel/sched.c.orig Fri Oct 29 10:31:11 1993 +++ kernel/sched.c Fri Oct 29 10:32:51 1993 @@ -414,7 +414,9 @@ unsigned long nr = 0; for(p = &LAST_TASK; p > &FIRST_TASK; --p) - if (*p && (*p)->state == TASK_RUNNING) + if (*p && ((*p)->state == TASK_RUNNING) || + (*p)->state == TASK_UNINTERRUPTIBLE) || + (*p)->state == TASK_SWAPPING)) nr += FIXED_1; return nr; } -- Matthias Urlichs \ XLink-POP N|rnberg | EMail: urlichs@smurf.sub.org Schleiermacherstra_e 12 \ Unix+Linux+Mac | Phone: ...please use email. 90491 N|rnberg (Germany) \ Consulting+Networking+Programming+etc'ing 42Zmiana weszła w listopadzie z wersją 0.99.14 jądra. Wówczas dodano już 13 ścieżek kodu, które bezpośrednio ustawiały wartości dla TASK_UNINTERRUPTIBLE lub TASK_SWAPPING. W tamtych czasach (lata 90.) mała ilość pamięci RAM była rekompensowana partycjami wymiany na wolnych dyskach obrotowych. Zmiana miała głęboki sens, ponieważ jeżeli system był tak zajęty mieleniem danych na dysku (tzw. swapowaniem), iż nie dało się na nim pracować – wskaźnik loadavg powinien być wysoki, aby ostrzec użytkownika. jeżeli wskaźnik pokazywałby wartości bliskie zeru (bo procesor nie wykonywał żadnych poważnych obliczeń), wprowadzałoby to użytkownika w błąd. Mimo iż zmiana z 1993 roku mogła wydawać się drobną poprawką mającą na celu lepsze odzwierciedlenie „odczuć” wydajności systemu, weszła ona na stałe do Linuksa (wersje 2.0 i kolejne). Wraz z rozwojem jądra i sprzętu komputerowego stan TASK_SWAPPING został później usunięty, ale TASK_UNINTERRUPTIBLE pozostał, a w wersji jądra 4.12 istniało już prawie 400 odwołań w kodzie, które ustawiały wartości dla dla tego rodzaju zadań. Na dzień dzisiejszy w serii 6.X jądra liczba ta może prawdopodobnie mieścić się w przedziale od 1000 do 1500 odwołań.
Pułapka skalowania:
Dzisiaj system Linux uruchamiany jest na bardzo szybkich systemach wieloprocesorowych / wielordzeniowych z krótkotrwałymi zadaniami do przetwarzania, co powoduje, iż czasami wskaźnik loadavg może być nieco mylący (zbyt wolno reaguje) choć przez cały czas jest dobrym ogólnym parametrem zdrowia systemu. Inaczej sprawa ma się w środowiskach chmury obliczeniowej, klastrów do konteneryzacji i usług bezserwerowych, gdzie często stosuje się auto scaling – automatyczne dodawanie nowych zasobów obliczeniowych, gdy load average przekroczy pewien próg X. No właśnie. Czy jest to dobry wskaźnik dla skalowania konkretnych zasobów? jeżeli wysoki poziom tej metryki wynika, z tego, iż procesy utknęły w stanie UNINTERRUPTIBLE z powodu zakleszczania się procesów (ang. lock contention) lub powolnego I/O to dodanie większej liczby procesorów (CPU) nic nie zmieni. Przyjdzie zapłacić więcej za zasoby, a wydajność aplikacji nie wzrośnie, ponieważ wąskim gardłem nie była moc obliczeniowa.
W 2018 roku inżynierowie firmy Facebook napotkali dokładnie ten sam problem. W celu zaoszczędzenia pieniędzy upakowali ciasno w centrach danych serwery swojej produkcji, ale potrzebowali precyzyjnego sygnału, który informowałby ich, kiedy stają się one przeciążone (zamiast „zamulonego” loadavg). Dlatego od wersji jądra 4.20 odpowiedzią na ten problem zostały wskaźniki określane PSI (ang. Pressure Stall Information). Load average może odpowiedzieć nam na pytanie: „Ile procesów czeka z różnych powodów w kolejce?”, natomiast PSI: „Jaki procent czasu procesy marnują na czekanie na X, Y, Z?” I tutaj możemy sobie wybrać konkretną metrykę oznaczającą procesor (X), dysk (Y) lub pamięć (Z). W dodatku otrzymujemy jasny wynik w procentach (od 0 do 100%), co jest znacznie łatwiejsze do zrozumienia niż obstrakcyjna liczba (np. 4.50), która pośrednio zależy od liczby rdzeni. W systemie Linux PSI znajdziemy w trzech plikach:
root@darkstar:~# cat /proc/pressure/cpu some avg10=0.00 avg60=0.00 avg300=0.00 total=296126687 full avg10=0.00 avg60=0.00 avg300=0.00 total=0 root@darkstar:~# cat /proc/pressure/memory some avg10=0.00 avg60=0.00 avg300=0.00 total=12 full avg10=0.00 avg60=0.00 avg300=0.00 total=12 root@darkstar:~# cat /proc/pressure/io some avg10=0.00 avg60=0.00 avg300=0.00 total=11387840 full avg10=0.00 avg60=0.00 avg300=0.00 total=10683970Wiersz some wskazuje odsetek czasu (dla 10, 60 i 300 sekund), w którym przynajmniej niektóre zadania są wstrzymane na danym zasobie. Możemy to odczytać na zasadzie „czasami ktoś musi poczekać w kolejce do …”, „system działa, ale z lekką zadyszką na …” lub „owszem pojawiają się opóźnienia przy …, ale system wciąż pracuje”. Z kolei wiersz full wskazuje odsetek czasu (dla 10, 60 i 300 sekund), w którym wszystkie zadania, które nie były bezczynne są wstrzymane na danym zasobie jednocześnie. Możemy to odczytać na zasadzie „wszyscy czekają w kolejce do …” lub „system nie robi żadnych postępów z powodu …”. Przykład – załóżmy, iż wykonujesz polecenie cat /proc/pressure/io i widzisz:
some avg10=40.00 avg60=20.00 avg300=5.00 total= ... full avg10=15.00 avg60=5.00 avg300=1.00 total= ...Oznacza to, że: przez 40% (4 sekundy) czasu przynajmniej jeden proces czekał na dysk; przez 15% czasu wszystkie procesy, które chciały coś zrobić z danymi na dysku czekały (1,5 sekundy) z powodu braku dostępu do I/O. Daje nam to informację, iż dysk aktualnie jest monco zajęty i aplikacje mogą działać wolniej, a czasami choćby mieć sekundowe przestoje (ang. stall). Analogicznie będzie dla pamięci oraz procesora. Dzięki takiej separacji wskaźników ciśnienia na konkretne zasoby możemy wykorzystać je do szybkiej diagnozy problemów z systemem oraz automatyzacji skalowania zasobów.
Więcej informacji: Linux Load Averages: Solving the Mystery, loadavg.c, PSI – Pressure Stall Information, Getting Started with PSI















