Загруженность процессора и памяти на Linux
Казалось бы простая опция , к которой пользователи Windows привыкли , но вот в Linux ей похоже не пользуются. Я говорю об процентном соотношении занятости процессора. Занятость отдельно по ядрам есть в каждом системном мониторе , но вот общей для всего процессора я не нашел ни в одной программе. И ладно бы если сделать это было трудно, так ведь нет. В конце концов , если бы не было возможности получить общее значение , можно было бы просто сложить значения загрузки всех ядер и поделить на кол-во ядер. Однако все популярные и юзабельные программы не заложили такой возможности. Именно поэтому я для себя написал утилиту, которая будет показывать загрузку процессора по всем ядрам и общую загрузку. Так же решил в утилиту добавить использование памяти и температуру процессора и видеокарты. Ниже я приведу исходник , в котором будет все , кроме температуры. Температуры нет в исходнике , поскольку врядли это получится сделать универсально для всех, однако я расскажу где и как я брал значения.
И так:
- для получения информации о загруженности процессора можно воспользоваться "файлом" /proc/stat
- для получения информации о загруженности памяти можно воспользоваться "файлом" /proc/meminfo
- для получения информации о температурах (процессора , материнки) можно воспользоваться "каталогом" /sys/class/hwmon
- для получения температуры видеокарты NVIDIA (при установленной nvidia-settings) можно воспользоваться командой nvidia-settings -q 'GPUCoreTemp'
Вы наверное заметили , что я слова "файл" и "каталог" выделил в кавычки. Я думаю найдутся те , кто скажет: 'мол нафиг постоянно дергать жесткий диск для получения информации' . И будут неправы , поскольку данные "файлы" и "каталоги" находятся не на жестком диске. В реальности до обращения к ним , их вообще не существует для пользователя. При обращении , они создаются на лету в ОЗУ. При чтении файлов в этих каталогах , невозможно определить размер обычными средствами , поэтому конец чтения можно определить только по ошибке чтения.
Теперь более подробно о каждом пункте (кому не интересна теория , переходите сразу вниз к исходнику) :
1) Файл /proc/stat содержит около дюжины характеристик, но я думаю нам интересны только CPU , CPU0 , CPU1 , CPUn; Наверное понятно , что CPU - это общая загруженность процессора , CPU0 , CPU1 ... - это загруженность ядер. Вот как выглядит начало у меня на данный момент:
cpu 72262 187 35014 2393930 28118 0 119 0 0
0
cpu0 19557 41 7033 599091 6836 0 4 0 0 0
cpu1 21290 43 6622 594873 9707
0 31 0 0 0
cpu2 13164 50 12888 600575 5505 0 3 0 0 0
cpu3 18250 51 8470
599390 6068 0 80 0 0 0
....
....
Насколько я понял , данные цифры слева от названий - это значения в тиках относительно первой загрузки системы. Глядя на все это , очень трудно понять как применить данные цифры чтобы вычислить процентную загрузку. Но все окажется не так и сложно. Для начала нужно понять , что из себя представляют данные цифры (а их десять).
Обозначение каждой колонки цифр слева-направо:
User - время, проведенное в
пользовательском режиме
Nice - время, проведенное в
пользовательском режиме с низким приоритетом
System -
время, проведенное в системном режиме
Idle - время,
проведенное в ожидании
Iowait - время ожидания
завершения операций ввода-вывода
Irq - время
обслуживания аппаратных прерываний
Softirq - время
обслуживания прерываний программного обеспечения
Steal - время, проведенное в других операционных
системах при работе в виртуальной среде
Guest -
время, потраченное на работу виртуального процессора для гостевых операционных
систем
Guest_nice - время, потраченное на работу с
низким приоритетом виртуальных ЦПУ для гостевых операционных систем
Из этих данных становится ясно, что 4 и 5 пункты - это время простоя процессора. Все остальные пункты - время активности. Соответственно приняв 100% загрузку при сложении всех чисел , мы получим время простоя сложив пункты 4+5 , а время активности при сложении пунктов 1+2+3+6+7+8+9+10. Но если мы просто сложим данные цифры, то ничего путного не выйдет. Нам ведь нужно определить загруженность на данный момент или точнее за определенный момент времени. Таким образом нам нужно сделать два измерения за определенный участок времени (например за секунду). В обоих измерениях мы сосчитаем активность и простой процессора и вычислим разницу между вычислениями. Чтобы было более понятно , применим формулы:
ПЕРВОЕ ИЗМЕРЕНИЕ АКТИВНОСТИ = 1+2+3+6+7+8+9+10
ВТОРОЕ ИЗМЕРЕНИЕ АКТИВНОСТИ = 1+2+3+6+7+8+9+10
АКТИВНОСТЬ ПРОЦЕССОРА = ВТОРОЕ ИЗМЕРЕНИЕ АКТИВНОСТИ - ПЕРВОЕ ИЗМЕРЕНИЕ АКТИВНОСТИ
ПЕРВОЕ ИЗМЕРЕНИЕ ПРОСТОЯ = 4+5
ВТОРОЕ ИЗМЕРЕНИЕ ПРОСТОЯ = 4+5
ПРОСТОЙ ПРОЦЕССОРА = ВТОРОЕ ИЗМЕРЕНИЕ ПРОСТОЯ - ПЕРВОЕ ИЗМЕРЕНИЕ ПРОСТОЯ
ОБЩЕЕ ВРЕМЯ ПРОЦЕССОРА = АКТИВНОСТЬ ПРОЦЕССОРА + ПРОСТОЙ ПРОЦЕССОРА
ЗАГРУЖЕННОСТЬ В ПРОЦЕНТАХ = 100 * АКТИВНОСТЬ ПРОЦЕССОРА / ОБЩЕЕ ВРЕМЯ ПРОЦЕССОРА
2) Если с загруженностью процессора более или менее для меня понятно , то с загруженностью памяти дело обстоит не так очевидно. Можно было конечно считать данные с утилит "TOP" , "FREE" или еще каких либо... Но мне хотелось брать данные напрямую без посредников. Файл /proc/meminfo у меня содержит следующие поля:
MemTotal - доступный объем оперативной
памяти
MemFree - свободный объем , доступен для немедленного
выделения процессам
MemAvailable - объем памяти, доступной в
распределение без необходимости обмена
Buffers -
данные, ожидающие записи на диск
Cached - объем памяти для
кэша
SwapCached - память, которая когда-то была
выгружена, заменена обратно, но все еще находится в файле
подкачки
Active - память , занятая часто используемыми
страницами памяти
Inactive - память, которая не
использовалась в последнее время и может быть заменена или
восстановлена
Active(anon) - анонимная память, которая
использовалась совсем недавно и обычно не
выгружается
Inactive(anon) - анонимная память, которая
давно не использовалась и может быть заменена или
восстановлена
Active(file) - память кеша страниц, которая
использовалась совсем недавно и обычно не восстанавливается до тех пор, пока не
потребуется
Inactive(file) - память кэша страниц, которую
можно восстановить без значительного снижения
производительности
Unevictable - не подлежащие обмену
страницы , не могут быть выгружены по разным
причинам
Mlocked - страницы заблокированы в памяти с помощью
mlock()системного вызова. Захваченные страницы также
невидимы.
SwapTotal - общее пространство
подкачки
SwapFree - оставшееся доступное пространство
подкачки
Dirty - память ожидающая запись на
диск
Writeback - память, которая в настоящий момент
записывается на диск
AnonPages - анонимные страницы памяти,
не ассоциированные с каким-либо файлом
Mapped - объем памяти
в виртуальном адресном пространстве процессов созданный при помощи библиотек
Shmem - Вся используемая общая (shared) память (совместно
используемая несколькими процессами, включая RAM-диски, SYS-V-IPC и BSD, такие
как SHMEM)
Slab - кеш структур данных в
ядре
SReclaimable - часть памяти Slab, которая может быть
восстановлена ??(например, кэш)
SUnreclaim - часть памяти
Slab, которая не может быть восстановлена
KernelStack -
память, используемая стеком ядра
PageTables - объем памяти,
выделенный для самого низкого уровня таблиц
страниц
NFS_Unstable - страницы NFS отправляемые на сервер,
но еще не переданные в хранилище
Bounce - память
используется для блочного устройства bounce
buffers
WritebackTmp - память, используемая Fuse для
временного буфера обратной записи
CommitLimit - память,
которая может быть выделена системой. Вычисляется на основе vm.overcommit_ratio
и области подкачки
Committed_AS - память, которая нужна всем
процессам в потенциале
VmallocTotal - общий размер памяти
для vmalloc
VmallocUsed - используемый размер памяти
vmalloc
VmallocChunk - наибольший непрерывный блок памяти
vmalloc, который является свободным
HardwareCorrupted -
объем оперативной памяти, который ядро ??определило как поврежденное / не
работающее.
AnonHugePages - огромные страницы без файлов,
сопоставленные с таблицами страниц в пользовательском
пространстве
CmaTotal - общее количество страниц, выделяемых
на непрерывные выделения памяти
CmaFree - свободное
непрерывное распределение памяти страниц
HugePages_Total -
количество страниц, выделяемых ядром (определенными с ВМ.nr_hugepages)
HugePages_Free - количество страниц, не выделяемых
процессом
HugePages_Rsvd - количество страниц, для которых
было выполнено обязательство выделять из пула, но выделение еще не
выполнено.
HugePages_Surp - количество страниц в пуле выше
значения в vm.nr_hugepages. Максимальное количество избыточных страниц
контролируется vm.nr_overcommit_hugepages.
Hugepagesize -
размер hugepage(обычно 2 МБ в системе на базе
Intel)
DirectMap4k - объем памяти, сопоставляемый со
стандартными страницами 4k
DirectMap2M - объем отображаемой
памяти hugepages(обычно 2 МБ)
Даже если забыть про корявость перевода , данная информация заставит задуматься, правда? В итоге я сравнивая результаты с утилитами "TOP" и "FREE" , остановился на такой формуле:
Используемая память = MemTotal - MemFree - Buffers - Cached - Slab
Получаемый результат единтичен с утилитами "TOP" и "FREE" , но отличается от утилиты "HTOP". Если кто-то знает наверняка как вычислить используемую память , то добро пожаловать на форум.
3) температура процессора , материнской платы , скорость вентиляторов и пр. можно отследить в каталоге /sys/class/hwmon . Здесь я думаю универсальности нет , поэтому данную функциональность я не отображал в исходнике. В данном каталоге может быть несколько других каталогов, относящихся к каждому отдельному устройству. И в этих каталогах есть файлы, в которых только одно число, отображающее температуру в 1000-ых долях градусов (например у меня это файлы temp1_input , temp2_input ...).
4) Температуру видеокарты (у меня от NVIDIA) можно отслеживать с помощью
официальной утилиты от NVIDIA. Команда для получения nvidia-settings -q
'GPUCoreTemp'
Других вариантов я не нашел.
Ну а теперь исходник (требуется библиотека window9):
#INCLUDE "window9.bi" Type CPUTYPE As Long iUser 'время, проведенное в пользовательском режиме As Long iNice 'время, проведенное в пользовательском режиме с низким приоритетом As Long iSystem 'время, проведенное в системном режиме As Long iIdle 'время, проведенное в ожидании As Long iIowait 'время ожидания завершения операций ввода-вывода As Long iIrq 'время обслуживания аппаратных прерываний As Long iSoftirq 'время обслуживания прерываний программного обеспечения As Long iSteal 'время, проведенное в других операционных системах при работе в виртуальной среде As Long iGuest 'время, потраченное на работу виртуального процессора для гостевых операционных систем As Long iGuest_nice 'время, потраченное на работу с низким приоритетом виртуальных ЦПУ для гостевых операционных систем End Type Type TIMING As Integer iCurentActive As Integer iCurentIdle As Integer iOldActive As Integer iOldIdle End Type Dim Shared As CPUTYPE tArray(10) Dim Shared As Long iMemTotal , iMemUsed Redim As TIMING Ttiming(0) Dim As Long iCountCore , iFlagCore Dim As Integer iActive , iIdle , iTotal Dim dTimer As Double Sub readMem() Dim As HANDLE handle Dim As Long iMemfree , iMembuffers , iMemcached , iMemslab Dim As String sValue handle=Read_file("/proc/meminfo") If handle <> Cast(Any Ptr, -1) Then Do sValue = Read_String(handle) If Left(Lcase(sValue) , 9) = "memtotal:" Then iMemTotal = Val(Mid(sValue,10)) Elseif Left(Lcase(sValue) , 8) = "memfree:" Then iMemfree = Val(Mid(sValue,9)) Elseif Left(Lcase(sValue) , 8) = "buffers:" Then iMembuffers = Val(Mid(sValue,9)) Elseif Left(Lcase(sValue) , 7) = "cached:" Then iMemcached = Val(Mid(sValue,8)) Elseif Left(Lcase(sValue) , 5) = "slab:" Then iMemslab = Val(Mid(sValue,6)) Endif Loop Until feof(handle) Close_file(handle) iMemUsed = iMemTotal - iMemfree - iMembuffers - iMemcached - iMemslab Endif End Sub Function readStat() As Long Dim As HANDLE handle Dim As String sValue Dim As Long iCountCore handle=Read_file("/proc/stat") If handle <> Cast(Any Ptr, -1) Then Do sValue = Read_String(handle) If Lcase(Left(sValue, 3)) = "cpu" Then Dim As Integer i If sValue[3] = 32 Then sValue = Mid (sValue , 4) Else i = Val(Mid(sValue,4,1)) + 1 sValue = Mid (sValue , 5) iCountCore +=1 Endif sscanf(Strptr(sValue) , "%d %d %d %d %d %d %d %d %d %d", @(tArray(i).iUser) , @(tArray(i).iNice) , @(tArray(i).iSystem) , _ @(tArray(i).iIdle) , @(tArray(i).iIowait) , @(tArray(i).iIrq) , @(tArray(i).iSoftirq) , _ @(tArray(i).iSteal) , @(tArray(i).iGuest) , @(tArray(i).iGuest_nice)) Endif Loop Until feof(handle) Close_file(handle) Endif Return iCountCore End Function Function getIdleTime(i As Long) As Integer Return tArray(i).iIdle + tArray(i).iIowait End Function Function getActiveTime(i As Long) As Integer Return tArray(i).iUser + tArray(i).iNice + tArray(i).iSystem + _ tArray(i).iIrq + tArray(i).iSoftirq + _ tArray(i).iSteal + tArray(i).iGuest + tArray(i).iGuest_nice End Function Dim As Integer event Dim As hwnd hwnd hwnd = OpenWindow("Stat",300,10,200,200) windowcolor(hwnd , &h5cc8ff) TextGadget(1,10,10,45,20,"CPU = ",0) TextGadget(2,10,30,45,20,"core1= ",0) TextGadget(3,10,50,45,20,"core2= ",0) TextGadget(4,10,70,45,20,"core3= ",0) TextGadget(5,10,90,45,20,"core4= ",0) TextGadget(6,10,120,85,20,"Memory Total:",0) TextGadget(7,10,140,85,20,"Memory Used:",0) TextGadget(8,60,10,85,20,"CPU load= ",0) TextGadget(9,60,30,85,20,"core1= ",0) TextGadget(10,60,50,85,20,"core2= ",0) TextGadget(11,60,70,85,20,"core3= ",0) TextGadget(12,60,90,85,20,"core4= ",0) TextGadget(13,100,120,85,20,"Memory Total:",0) TextGadget(14,100,140,85,20,"Memory Used:",0) setgadgetcolor(1, 0, &hFF4533, 2) setgadgetcolor(2, 0, &h009900, 2) setgadgetcolor(3, 0, &h009900, 2) setgadgetcolor(4, 0, &h009900, 2) setgadgetcolor(5, 0, &h009900, 2) setgadgetcolor(6, 0, &hFF5CDF, 2) setgadgetcolor(7, 0, &hFF5CDF, 2) Do iCountCore = readStat() If iFlagCore = 0 Then Redim As TIMING Ttiming(iCountCore) iFlagCore = 1 Endif For i As Integer = 0 To iCountCore Ttiming(i).iCurentActive = getActiveTime(i) iActive = Ttiming(i).iCurentActive - Ttiming(i).iOldActive Ttiming(i).iOldActive = Ttiming(i).iCurentActive Ttiming(i).iCurentIdle = getIdleTime(i) iIdle = Ttiming(i).iCurentIdle - Ttiming(i).iOldIdle Ttiming(i).iOldIdle = Ttiming(i).iCurentIdle iTotal = iActive + iIdle Dim As string*4 sResult = Str( 100 * iActive/iTotal) If i = 0 Then SETGADGETTEXT(8 , sResult & " %") Else SETGADGETTEXT(8+i , sResult & " %") Endif Next readMem() SETGADGETTEXT(13 , Str(iMemTotal\1024) & "MB") SETGADGETTEXT(14 , Str(iMemUsed\1024) & "MB (" & Str(100*iMemUsed \ iMemTotal) & "%)") dTimer = Timer Do event=WindowEvent() Sleep(10) If event=EventClose Then End Loop Until Timer >= dTimer+1 Loop