Загруженность процессора и памяти на Linux

Казалось бы простая опция , к которой пользователи Windows привыкли , но вот в Linux ей похоже не пользуются. Я говорю об процентном соотношении занятости процессора. Занятость отдельно по ядрам есть в каждом системном мониторе , но вот общей для всего процессора я не нашел ни в одной программе. И ладно бы если сделать это было трудно, так ведь нет. В конце концов , если бы не было возможности получить общее значение , можно было бы просто сложить значения загрузки всех ядер и поделить на кол-во ядер. Однако все популярные и юзабельные программы не заложили такой возможности. Именно поэтому я для себя написал утилиту, которая будет показывать загрузку процессора по всем ядрам и общую загрузку. Так же решил в утилиту добавить использование памяти и температуру процессора и видеокарты. Ниже я приведу исходник , в котором будет все , кроме температуры. Температуры нет в исходнике , поскольку врядли это получится сделать универсально для всех, однако я расскажу где и как я брал значения.

И так:

  1. для получения информации о загруженности процессора можно воспользоваться "файлом" /proc/stat
  2. для получения информации о загруженности памяти можно воспользоваться "файлом" /proc/meminfo
  3. для получения информации о температурах (процессора , материнки)  можно воспользоваться "каталогом" /sys/class/hwmon
  4. для получения температуры видеокарты 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