Jak określić zużycie procesora i pamięci od wewnątrz procesu?

593

Kiedyś miałem za zadanie określić następujące parametry wydajności z działającej aplikacji:

  • Całkowita dostępna pamięć wirtualna
  • Aktualnie używana pamięć wirtualna
  • Pamięć wirtualna aktualnie używana przez mój proces
  • Całkowita dostępna pamięć RAM
  • Aktualnie używana pamięć RAM
  • Pamięć RAM aktualnie używana przez mój proces
  • % Aktualnie używanego procesora
  • % Procesora aktualnie używanego przez mój proces

Kod musiał działać w systemie Windows i Linux. Mimo że wydaje się to być standardowym zadaniem, znalezienie niezbędnych informacji w instrukcjach (interfejs API WIN32, dokumenty GNU) oraz w Internecie zajęło mi kilka dni, ponieważ jest tak wiele niekompletnych / niepoprawnych / nieaktualnych informacji na ten temat. znalazłem tam.

Aby uratować innych przed podobnymi problemami, pomyślałem, że dobrym pomysłem będzie zebranie w jednym miejscu wszystkich rozproszonych informacji oraz tego, co znalazłem metodą prób i błędów.

Lanzelot
źródło
13
„Całkowita dostępna pamięć wirtualna” nie ma znaczenia w nowoczesnych systemach operacyjnych.
David Schwartz,
29
Dlaczego to nie ma znaczenia? Czy to unieważnia odpowiedź tutaj? stackoverflow.com/questions/3296211/… ... proszę nie zostawiać cliffhangers podczas komentowania, to nie jest program telewizyjny.
Mindaugas Bernatavičius
3
@ MindaugasBernatavičius: Powiązane pytanie dotyczy „całkowitej pamięci fizycznej”, która jest faktem sprzętowym znanym systemowi operacyjnemu. Otrzymujesz sumę, sumując rozmiary wszystkich modułów pamięci. Z drugiej strony „Całkowita dostępna pamięć wirtualna”, co to w ogóle oznacza? Czy jest to połączona wirtualna przestrzeń adresowa wszystkich procesów, które teoretycznie można by stworzyć? Liczba ta wynosiłaby około 2 ^ 80 bajtów, więc na pewno bez znaczenia.
MSalters
2
@MSalters - dziękuję za zaangażowanie. Uważam, że pytanie o to, co miał na myśli OP, jest o wiele milsze i zdrowsze niż stwierdzenie, że coś jest bez znaczenia (bez wyjaśnienia). Jeśli zauważysz, odpowiedzi również zajmują określone stanowisko w tym zakresie: Pamięć wirtualna = RAM + SWAP (lub PAGEFILE) - co jest rozsądnym założeniem. Z tego wiemy, że nie jest bez znaczenia, ponieważ istnieje szczególna interpretacja tego terminu (który może nie być najbardziej technicznie poprawny, kolokwalizm), który ma znaczenie.
Mindaugas Bernatavičius
2
@ MindaugasBernatavičius: Ignoruje pliki mapowane w pamięci i kod, który nie jest stronicowany. Linux nie ma przydzielonego przydziału pamięci (nie jest wspierany przez RAM ani swap), a Windows ma niewykorzystane stosy.
MSalters

Odpowiedzi:

643

Windows

Niektóre z powyższych wartości są łatwo dostępne z odpowiedniego interfejsu API WIN32, po prostu podaję je tutaj dla kompletności. Inne należy jednak uzyskać z biblioteki Performance Data Helper (PDH), która jest nieco „nieintuicyjna” i wymaga wielu bolesnych prób i błędów, aby zabrać się do pracy. (Przynajmniej zajęło mi to trochę czasu, być może byłem tylko trochę głupi ...)

Uwaga: dla jasności wszystkie sprawdzanie błędów zostało pominięte w poniższym kodzie. Sprawdź kody powrotne ...!


  • Całkowita pamięć wirtualna:

    #include "windows.h"
    
    MEMORYSTATUSEX memInfo;
    memInfo.dwLength = sizeof(MEMORYSTATUSEX);
    GlobalMemoryStatusEx(&memInfo);
    DWORDLONG totalVirtualMem = memInfo.ullTotalPageFile;

    Uwaga: nazwa „TotalPageFile” jest tutaj nieco myląca. W rzeczywistości parametr ten podaje „Rozmiar pamięci wirtualnej”, który jest rozmiarem pliku wymiany plus zainstalowanej pamięci RAM.

  • Aktualnie używana pamięć wirtualna:

    Taki sam kod jak w „Total Virtual Memory”, a następnie

    DWORDLONG virtualMemUsed = memInfo.ullTotalPageFile - memInfo.ullAvailPageFile;
  • Pamięć wirtualna aktualnie używana przez bieżący proces:

    #include "windows.h"
    #include "psapi.h"
    
    PROCESS_MEMORY_COUNTERS_EX pmc;
    GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc));
    SIZE_T virtualMemUsedByMe = pmc.PrivateUsage;



  • Całkowita pamięć fizyczna (RAM):

    Taki sam kod jak w „Total Virtual Memory”, a następnie

    DWORDLONG totalPhysMem = memInfo.ullTotalPhys;
  • Aktualnie używana pamięć fizyczna:

    Same code as in "Total Virtual Memory" and then
    
    DWORDLONG physMemUsed = memInfo.ullTotalPhys - memInfo.ullAvailPhys;
  • Pamięć fizyczna aktualnie używana przez bieżący proces:

    Taki sam kod jak w „Pamięci wirtualnej obecnie używanej przez bieżący proces”, a następnie

    SIZE_T physMemUsedByMe = pmc.WorkingSetSize;



  • Aktualnie używany procesor:

    #include "TCHAR.h"
    #include "pdh.h"
    
    static PDH_HQUERY cpuQuery;
    static PDH_HCOUNTER cpuTotal;
    
    void init(){
        PdhOpenQuery(NULL, NULL, &cpuQuery);
        // You can also use L"\\Processor(*)\\% Processor Time" and get individual CPU values with PdhGetFormattedCounterArray()
        PdhAddEnglishCounter(cpuQuery, L"\\Processor(_Total)\\% Processor Time", NULL, &cpuTotal);
        PdhCollectQueryData(cpuQuery);
    }
    
    double getCurrentValue(){
        PDH_FMT_COUNTERVALUE counterVal;
    
        PdhCollectQueryData(cpuQuery);
        PdhGetFormattedCounterValue(cpuTotal, PDH_FMT_DOUBLE, NULL, &counterVal);
        return counterVal.doubleValue;
    }
  • Procesor aktualnie używany przez bieżący proces:

    #include "windows.h"
    
    static ULARGE_INTEGER lastCPU, lastSysCPU, lastUserCPU;
    static int numProcessors;
    static HANDLE self;
    
    void init(){
        SYSTEM_INFO sysInfo;
        FILETIME ftime, fsys, fuser;
    
        GetSystemInfo(&sysInfo);
        numProcessors = sysInfo.dwNumberOfProcessors;
    
        GetSystemTimeAsFileTime(&ftime);
        memcpy(&lastCPU, &ftime, sizeof(FILETIME));
    
        self = GetCurrentProcess();
        GetProcessTimes(self, &ftime, &ftime, &fsys, &fuser);
        memcpy(&lastSysCPU, &fsys, sizeof(FILETIME));
        memcpy(&lastUserCPU, &fuser, sizeof(FILETIME));
    }
    
    double getCurrentValue(){
        FILETIME ftime, fsys, fuser;
        ULARGE_INTEGER now, sys, user;
        double percent;
    
        GetSystemTimeAsFileTime(&ftime);
        memcpy(&now, &ftime, sizeof(FILETIME));
    
        GetProcessTimes(self, &ftime, &ftime, &fsys, &fuser);
        memcpy(&sys, &fsys, sizeof(FILETIME));
        memcpy(&user, &fuser, sizeof(FILETIME));
        percent = (sys.QuadPart - lastSysCPU.QuadPart) +
            (user.QuadPart - lastUserCPU.QuadPart);
        percent /= (now.QuadPart - lastCPU.QuadPart);
        percent /= numProcessors;
        lastCPU = now;
        lastUserCPU = user;
        lastSysCPU = sys;
    
        return percent * 100;
    }

Linux

W systemie Linux wybór, który na pierwszy rzut oka wydawał się oczywisty, polegał na użyciu interfejsów API POSIX, takich jak getrusage() itp. Spędziłem trochę czasu starając się, aby to zadziałało, ale nigdy nie uzyskałem znaczących wartości. Kiedy w końcu sprawdziłem same źródła jądra, dowiedziałem się, że najwyraźniej te interfejsy API nie są jeszcze w pełni zaimplementowane w jądrze Linuksa 2.6 !?

Ostatecznie wszystkie wartości uzyskałem przez połączenie odczytu pseudo-systemu plików /proci wywołań jądra.

  • Całkowita pamięć wirtualna:

    #include "sys/types.h"
    #include "sys/sysinfo.h"
    
    struct sysinfo memInfo;
    
    sysinfo (&memInfo);
    long long totalVirtualMem = memInfo.totalram;
    //Add other values in next statement to avoid int overflow on right hand side...
    totalVirtualMem += memInfo.totalswap;
    totalVirtualMem *= memInfo.mem_unit;
  • Aktualnie używana pamięć wirtualna:

    Taki sam kod jak w „Total Virtual Memory”, a następnie

    long long virtualMemUsed = memInfo.totalram - memInfo.freeram;
    //Add other values in next statement to avoid int overflow on right hand side...
    virtualMemUsed += memInfo.totalswap - memInfo.freeswap;
    virtualMemUsed *= memInfo.mem_unit;
  • Pamięć wirtualna aktualnie używana przez bieżący proces:

    #include "stdlib.h"
    #include "stdio.h"
    #include "string.h"
    
    int parseLine(char* line){
        // This assumes that a digit will be found and the line ends in " Kb".
        int i = strlen(line);
        const char* p = line;
        while (*p <'0' || *p > '9') p++;
        line[i-3] = '\0';
        i = atoi(p);
        return i;
    }
    
    int getValue(){ //Note: this value is in KB!
        FILE* file = fopen("/proc/self/status", "r");
        int result = -1;
        char line[128];
    
        while (fgets(line, 128, file) != NULL){
            if (strncmp(line, "VmSize:", 7) == 0){
                result = parseLine(line);
                break;
            }
        }
        fclose(file);
        return result;
    }



  • Całkowita pamięć fizyczna (RAM):

    Taki sam kod jak w „Total Virtual Memory”, a następnie

    long long totalPhysMem = memInfo.totalram;
    //Multiply in next statement to avoid int overflow on right hand side...
    totalPhysMem *= memInfo.mem_unit;
  • Aktualnie używana pamięć fizyczna:

    Taki sam kod jak w „Total Virtual Memory”, a następnie

    long long physMemUsed = memInfo.totalram - memInfo.freeram;
    //Multiply in next statement to avoid int overflow on right hand side...
    physMemUsed *= memInfo.mem_unit;
  • Pamięć fizyczna aktualnie używana przez bieżący proces:

    Zmień getValue () w „Pamięci wirtualnej aktualnie używanej przez bieżący proces” w następujący sposób:

    int getValue(){ //Note: this value is in KB!
        FILE* file = fopen("/proc/self/status", "r");
        int result = -1;
        char line[128];
    
        while (fgets(line, 128, file) != NULL){
            if (strncmp(line, "VmRSS:", 6) == 0){
                result = parseLine(line);
                break;
            }
        }
        fclose(file);
        return result;
    }



  • Aktualnie używany procesor:

    #include "stdlib.h"
    #include "stdio.h"
    #include "string.h"
    
    static unsigned long long lastTotalUser, lastTotalUserLow, lastTotalSys, lastTotalIdle;
    
    void init(){
        FILE* file = fopen("/proc/stat", "r");
        fscanf(file, "cpu %llu %llu %llu %llu", &lastTotalUser, &lastTotalUserLow,
            &lastTotalSys, &lastTotalIdle);
        fclose(file);
    }
    
    double getCurrentValue(){
        double percent;
        FILE* file;
        unsigned long long totalUser, totalUserLow, totalSys, totalIdle, total;
    
        file = fopen("/proc/stat", "r");
        fscanf(file, "cpu %llu %llu %llu %llu", &totalUser, &totalUserLow,
            &totalSys, &totalIdle);
        fclose(file);
    
        if (totalUser < lastTotalUser || totalUserLow < lastTotalUserLow ||
            totalSys < lastTotalSys || totalIdle < lastTotalIdle){
            //Overflow detection. Just skip this value.
            percent = -1.0;
        }
        else{
            total = (totalUser - lastTotalUser) + (totalUserLow - lastTotalUserLow) +
                (totalSys - lastTotalSys);
            percent = total;
            total += (totalIdle - lastTotalIdle);
            percent /= total;
            percent *= 100;
        }
    
        lastTotalUser = totalUser;
        lastTotalUserLow = totalUserLow;
        lastTotalSys = totalSys;
        lastTotalIdle = totalIdle;
    
        return percent;
    }
  • Procesor aktualnie używany przez bieżący proces:

    #include "stdlib.h"
    #include "stdio.h"
    #include "string.h"
    #include "sys/times.h"
    #include "sys/vtimes.h"
    
    static clock_t lastCPU, lastSysCPU, lastUserCPU;
    static int numProcessors;
    
    void init(){
        FILE* file;
        struct tms timeSample;
        char line[128];
    
        lastCPU = times(&timeSample);
        lastSysCPU = timeSample.tms_stime;
        lastUserCPU = timeSample.tms_utime;
    
        file = fopen("/proc/cpuinfo", "r");
        numProcessors = 0;
        while(fgets(line, 128, file) != NULL){
            if (strncmp(line, "processor", 9) == 0) numProcessors++;
        }
        fclose(file);
    }
    
    double getCurrentValue(){
        struct tms timeSample;
        clock_t now;
        double percent;
    
        now = times(&timeSample);
        if (now <= lastCPU || timeSample.tms_stime < lastSysCPU ||
            timeSample.tms_utime < lastUserCPU){
            //Overflow detection. Just skip this value.
            percent = -1.0;
        }
        else{
            percent = (timeSample.tms_stime - lastSysCPU) +
                (timeSample.tms_utime - lastUserCPU);
            percent /= (now - lastCPU);
            percent /= numProcessors;
            percent *= 100;
        }
        lastCPU = now;
        lastSysCPU = timeSample.tms_stime;
        lastUserCPU = timeSample.tms_utime;
    
        return percent;
    }

DO ZROBIENIA: Inne platformy

Zakładam, że część kodu Linuksa działa również dla Uniksa, z wyjątkiem części, które czytają pseudo-system plików / proc. Być może w Unixie te części można zastąpić getrusage()podobnymi funkcjami? Jeśli ktoś z wiedzą na temat Uniksa mógłby edytować tę odpowiedź i wypełnić szczegóły ?!

Lanzelot
źródło
10
Ostrzeżenie: w PdhAddCounter zapytanie musi być zlokalizowane, w przeciwnym razie będzie działać tylko w systemie rodzimym. W systemie Vista / 2008 i nowszym wolisz używać PdhAddEnglishCounter, aby uniknąć problemów z lokalizacją.
moala
2
@NunoAniceto Podczas korzystania PROCESS_MEMORY_COUNTERS, w jaki sposób pobierasz „Pamięć wirtualną aktualnie używaną przez bieżący proces”? PrivateUsagenie jest członkiem PROCESS_MEMORY_COUNTERSjest błąd kompilatora, który otrzymuję!
CinCout,
2
Dlaczego używasz "quotes like these"do dołączania nagłówków systemowych?
Wyścigi lekkości na orbicie
8
@CinCout potrzebujesz castingu. Zamień GetProcessMemoryInfo (GetCurrentProcess (), & pmc, sizeof (pmc)); z GetProcessMemoryInfo (GetCurrentProcess (), (PROCESS_MEMORY_COUNTERS *) & pmc, sizeof (pmc));
Sumia,
3
@Lanzelot Ponieważ jesteśmy znacznie powyżej jądra Linux 3.0. Czy możesz zaktualizować swoją odpowiedź Linuksa na API POSIX? Jeśli to możliwe, popraw także odpowiedź Windows, zastępując GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc));
Dr. Xperience
140

Mac OS X

Miałem też nadzieję znaleźć podobne informacje dla Mac OS X. Ponieważ go tu nie było, wyszedłem i sam go wykopałem. Oto niektóre z rzeczy, które znalazłem. Jeśli ktoś ma jakieś inne sugestie, chciałbym je usłyszeć.

Całkowita pamięć wirtualna

Ten jest trudny w systemie Mac OS X, ponieważ nie korzysta ze wstępnie ustawionej partycji wymiany ani pliku takiego jak Linux. Oto wpis z dokumentacji Apple:

Uwaga: W przeciwieństwie do większości systemów operacyjnych opartych na Uniksie, Mac OS X nie używa wstępnie przydzielonej partycji wymiany pamięci wirtualnej. Zamiast tego wykorzystuje całą dostępną przestrzeń na partycji rozruchowej komputera.

Jeśli więc chcesz wiedzieć, ile pamięci wirtualnej jest nadal dostępne, musisz uzyskać rozmiar partycji głównej. Możesz to zrobić w następujący sposób:

struct statfs stats;
if (0 == statfs("/", &stats))
{
    myFreeSwap = (uint64_t)stats.f_bsize * stats.f_bfree;
}

Łącznie aktualnie używane wirtualne

Wywołanie systcl za pomocą klawisza „vm.swapusage” dostarcza interesujących informacji o użyciu wymiany:

sysctl -n vm.swapusage
vm.swapusage: total = 3072.00M  used = 2511.78M  free = 560.22M  (encrypted)

Nie oznacza to, że całkowite użycie wymiany może się zmienić, jeśli potrzeba więcej wymiany, jak wyjaśniono w powyższej sekcji. Tak więc suma jest w rzeczywistości bieżącą sumą wymiany. W języku C ++ dane można przeszukiwać w następujący sposób:

xsw_usage vmusage = {0};
size_t size = sizeof(vmusage);
if( sysctlbyname("vm.swapusage", &vmusage, &size, NULL, 0)!=0 )
{
   perror( "unable to get swap usage by calling sysctlbyname(\"vm.swapusage\",...)" );
}

Zauważ, że „xsw_usage”, zadeklarowany w sysctl.h, wydaje się nieudokumentowany i podejrzewam, że istnieje bardziej przenośny sposób dostępu do tych wartości.

Pamięć wirtualna aktualnie używana przez mój proces

Za pomocą tej task_infofunkcji można uzyskać statystyki dotyczące bieżącego procesu . Obejmuje to bieżący rozmiar rezydenta procesu i bieżący rozmiar wirtualny.

#include<mach/mach.h>

struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;

if (KERN_SUCCESS != task_info(mach_task_self(),
                              TASK_BASIC_INFO, (task_info_t)&t_info, 
                              &t_info_count))
{
    return -1;
}
// resident size is in t_info.resident_size;
// virtual size is in t_info.virtual_size;

Całkowita dostępna pamięć RAM

Ilość fizycznej pamięci RAM dostępnej w twoim systemie jest dostępna za pomocą sysctlfunkcji systemowej w następujący sposób:

#include <sys/types.h>
#include <sys/sysctl.h>
...
int mib[2];
int64_t physical_memory;
mib[0] = CTL_HW;
mib[1] = HW_MEMSIZE;
length = sizeof(int64_t);
sysctl(mib, 2, &physical_memory, &length, NULL, 0);

Aktualnie używana pamięć RAM

Ogólne statystyki pamięci można uzyskać z host_statisticsfunkcji systemu.

#include <mach/vm_statistics.h>
#include <mach/mach_types.h>
#include <mach/mach_init.h>
#include <mach/mach_host.h>

int main(int argc, const char * argv[]) {
    vm_size_t page_size;
    mach_port_t mach_port;
    mach_msg_type_number_t count;
    vm_statistics64_data_t vm_stats;

    mach_port = mach_host_self();
    count = sizeof(vm_stats) / sizeof(natural_t);
    if (KERN_SUCCESS == host_page_size(mach_port, &page_size) &&
        KERN_SUCCESS == host_statistics64(mach_port, HOST_VM_INFO,
                                        (host_info64_t)&vm_stats, &count))
    {
        long long free_memory = (int64_t)vm_stats.free_count * (int64_t)page_size;

        long long used_memory = ((int64_t)vm_stats.active_count +
                                 (int64_t)vm_stats.inactive_count +
                                 (int64_t)vm_stats.wire_count) *  (int64_t)page_size;
        printf("free memory: %lld\nused memory: %lld\n", free_memory, used_memory);
    }

    return 0;
}

Należy tutaj zauważyć, że w systemie Mac OS X istnieje pięć rodzajów stron pamięci. Są to:

  1. Przewodowe strony, które są zablokowane i nie można ich zamienić
  2. Aktywny strony, które ładują się do pamięci fizycznej i byłyby stosunkowo trudne do wymiany
  3. Nieaktywne strony, które są ładowane do pamięci, ale nie były ostatnio używane i mogą nawet nie być potrzebne. Są to potencjalni kandydaci do zamiany. Pamięć ta prawdopodobnie musiałaby zostać wyczyszczona.
  4. Strony z pamięci podręcznej , które w pewnym stopniu przypominały pamięć podręczną, mogą być łatwo ponownie wykorzystane. Pamięć podręczna prawdopodobnie nie wymagałaby opróżniania. Nadal możliwe jest reaktywowanie stron z pamięci podręcznej
  5. Darmowe strony, które są całkowicie darmowe i gotowe do użycia.

Warto zauważyć, że tylko dlatego, że Mac OS X może czasami wykazywać bardzo mało rzeczywistej wolnej pamięci, może nie być dobrym wskaźnikiem tego, ile jest gotowe do użycia w krótkim czasie.

Pamięć RAM aktualnie używana przez mój proces

Zobacz „Pamięć wirtualna aktualnie używana przez mój proces” powyżej. Obowiązuje ten sam kod.

Michael Taylor
źródło
Opuściłeś #include <mach / mach.h>, aby zdefiniować task_info () w sekcji „Pamięć wirtualna aktualnie używana przez mój proces”. Ten nagłówek musi zostać dołączony, aby zdefiniować tę funkcję.
Dan Nissenbaum,
4
Masz pomysł na uzyskanie użycia procesora?
Mihir Mehta
@Michael Taylor, to świetnie, dziękuję, ale pytanie o pamięć RAM aktualnie używaną dla OS X, wygląda na to, że otrzymujesz statystyki VM, a nie pamięć fizyczną. Czy tak właśnie jest w tym przypadku?
Edgar Aroutiounian
1
Jak obliczyć pamięć APP i pamięć podręczną, np. Monitor aktywności? Użyłem vm_stats.inactive_page_count * rozmiar_strony t, aby obliczyć pamięć podręczną plików, ale nie jest ona zsynchronizowana z monitorem aktywności. Z góry dziękuję
Amit Khandelwal
1
oryginalny autor wydaje się mylić z „pamięcią wirtualną” - w niektórych kontekstach nie odnosi się do pamięci zamienionej na dysk, ale raczej wirtualną przestrzeń adresową, która może nie być całkowicie stronicowana. Znajdź system, który nie używa żadnej wymiany, a zobaczysz, że większość procesów ma większy rozmiar „virt” niż rozmiar „rss”. W sekcji „Pamięć wirtualna aktualnie używana przez mój proces” to właśnie odnosi się do „pamięci wirtualnej” - przestrzeni adresowej, a nie pamięci zamienionej na dysk.
Pierce
63

Linux

W systemie Linux informacje te są dostępne w systemie plików / proc. Nie jestem wielkim fanem używanego formatu pliku tekstowego, ponieważ każda dystrybucja Linuksa wydaje się dostosowywać co najmniej jeden ważny plik. Szybkie spojrzenie na źródło „ps” ujawnia bałagan.

Ale tutaj znajdziesz informacje, których szukasz:

/ proc / meminfo zawiera większość informacji ogólnosystemowego szukacie. Tutaj wygląda jak w moim systemie; Myślę, że interesują Cię MemTotal , MemFree , SwapTotal i SwapFree :

Anderson cxc # more /proc/meminfo
MemTotal:      4083948 kB
MemFree:       2198520 kB
Buffers:         82080 kB
Cached:        1141460 kB
SwapCached:          0 kB
Active:        1137960 kB
Inactive:       608588 kB
HighTotal:     3276672 kB
HighFree:      1607744 kB
LowTotal:       807276 kB
LowFree:        590776 kB
SwapTotal:     2096440 kB
SwapFree:      2096440 kB
Dirty:              32 kB
Writeback:           0 kB
AnonPages:      523252 kB
Mapped:          93560 kB
Slab:            52880 kB
SReclaimable:    24652 kB
SUnreclaim:      28228 kB
PageTables:       2284 kB
NFS_Unstable:        0 kB
Bounce:              0 kB
CommitLimit:   4138412 kB
Committed_AS:  1845072 kB
VmallocTotal:   118776 kB
VmallocUsed:      3964 kB
VmallocChunk:   112860 kB
HugePages_Total:     0
HugePages_Free:      0
HugePages_Rsvd:      0
Hugepagesize:     2048 kB

Aby wykorzystać procesor, musisz trochę popracować. Linux udostępnia ogólne wykorzystanie procesora od momentu uruchomienia systemu; prawdopodobnie nie jest to tym, czym jesteś zainteresowany. Jeśli chcesz wiedzieć, jakie było wykorzystanie procesora przez ostatnią sekundę lub 10 sekund, musisz zapytać o informacje i sam je obliczyć.

Informacje są dostępne w / proc / stat , który jest dość dobrze udokumentowany na stronie http://www.linuxhowtos.org/System/procstat.htm ; oto jak to wygląda na moim 4-rdzeniowym pudełku:

Anderson cxc #  more /proc/stat
cpu  2329889 0 2364567 1063530460 9034 9463 96111 0
cpu0 572526 0 636532 265864398 2928 1621 6899 0
cpu1 590441 0 531079 265949732 4763 351 8522 0
cpu2 562983 0 645163 265796890 682 7490 71650 0
cpu3 603938 0 551790 265919440 660 0 9040 0
intr 37124247
ctxt 50795173133
btime 1218807985
processes 116889
procs_running 1
procs_blocked 0

Najpierw musisz określić, ile procesorów (lub procesorów lub rdzeni przetwarzających) jest dostępnych w systemie. Aby to zrobić, policz liczbę wpisów „cpuN”, gdzie N zaczyna się od 0 i zwiększa. Nie licz linii „cpu”, która jest kombinacją linii cpuN. W moim przykładzie możesz zobaczyć cpu0 do cpu3, ​​w sumie 4 procesory. Od teraz możesz ignorować cpu0..cpu3 i skupiać się tylko na linii „cpu”.

Następnie musisz wiedzieć, że czwarta liczba w tych wierszach jest miarą czasu bezczynności, a zatem czwarta liczba w linii „procesora” to całkowity czas bezczynności dla wszystkich procesorów od czasu rozruchu. Czas ten mierzony jest w „jiffies” Linuksa, które wynoszą 1/100 sekundy każda.

Ale nie obchodzi cię całkowity czas bezczynności; zależy Ci na czasie bezczynności w danym okresie, np. w ostatniej sekundzie. Oblicz to, musisz przeczytać ten plik dwa razy, w odstępie 1 sekundy. Następnie możesz wykonać różnicę czwartej wartości linii. Na przykład, jeśli weźmiesz próbkę i otrzymasz:

cpu  2330047 0 2365006 1063853632 9035 9463 96114 0

Następnie sekundę później otrzymujesz tę próbkę:

cpu  2330047 0 2365007 1063854028 9035 9463 96114 0

Odejmij dwie liczby, a otrzymasz różnicę 396, co oznacza, że ​​twój procesor był bezczynny przez 3,96 sekundy z ostatniego 1,00 sekundy. Sztuczka polega oczywiście na tym, że musisz podzielić liczbę procesorów. 3,96 / 4 = 0,99, a jest twój bezczynny procent; 99% bezczynności i 1% zajęty.

W moim kodzie mam bufor pierścieniowy zawierający 360 wpisów i czytam ten plik co sekundę. To pozwala mi szybko obliczyć wykorzystanie procesora przez 1 sekundę, 10 sekund itp., Aż do 1 godziny.

Aby uzyskać informacje specyficzne dla procesu, musisz poszukać w / proc / pid ; jeśli nie obchodzi cię twój pid, możesz zajrzeć do / proc / self.

Procesor używany przez proces jest dostępny w / proc / self / stat . Jest to dziwnie wyglądający plik składający się z jednej linii; na przykład:

19340 (whatever) S 19115 19115 3084 34816 19115 4202752 118200 607 0 0 770 384 2
 7 20 0 77 0 266764385 692477952 105074 4294967295 134512640 146462952 321468364
8 3214683328 4294960144 0 2147221247 268439552 1276 4294967295 0 0 17 0 0 0 0

Ważnymi danymi są tutaj 13 i 14 żeton (tutaj 0 i 770). 13. token to liczba jiffies, które proces wykonał w trybie użytkownika, a 14. to liczba jiffies, które proces wykonał w trybie jądra. Dodaj oba razem, a uzyskasz całkowite wykorzystanie procesora.

Ponownie będziesz musiał okresowo próbkować ten plik i obliczać różnicę, aby określić zużycie procesora przez proces w czasie.

Edytować: pamiętaj, że obliczając wykorzystanie procesora przez proces, należy wziąć pod uwagę 1) liczbę wątków w procesie i 2) liczbę procesorów w systemie. Na przykład, jeśli proces jednowątkowy zużywa tylko 25% procesora, może to być dobre lub złe. Dobry w systemie jednoprocesorowym, ale zły w systemie 4-procesorowym; oznacza to, że proces działa nieprzerwanie i wykorzystuje 100% dostępnych cykli procesora.

Aby uzyskać informacje o pamięci specyficzne dla procesu, należy spojrzeć na / proc / self / status, który wygląda następująco:

Name:   whatever
State:  S (sleeping)
Tgid:   19340
Pid:    19340
PPid:   19115
TracerPid:      0
Uid:    0       0       0       0
Gid:    0       0       0       0
FDSize: 256
Groups: 0 1 2 3 4 6 10 11 20 26 27
VmPeak:   676252 kB
VmSize:   651352 kB
VmLck:         0 kB
VmHWM:    420300 kB
VmRSS:    420296 kB
VmData:   581028 kB
VmStk:       112 kB
VmExe:     11672 kB
VmLib:     76608 kB
VmPTE:      1244 kB
Threads:        77
SigQ:   0/36864
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: fffffffe7ffbfeff
SigIgn: 0000000010001000
SigCgt: 20000001800004fc
CapInh: 0000000000000000
CapPrm: 00000000ffffffff
CapEff: 00000000fffffeff
Cpus_allowed:   0f
Mems_allowed:   1
voluntary_ctxt_switches:        6518
nonvoluntary_ctxt_switches:     6598

Wpisy zaczynające się od „Vm” to interesujące:

  • VmPeak to maksymalna przestrzeń pamięci wirtualnej wykorzystywana przez proces, w kB (1024 bajty).
  • VmSize to bieżąca pamięć wirtualna używana przez proces, w kB. W moim przykładzie jest dość duży: 651,352 kB, czyli około 636 megabajtów.
  • VmRss to ilość pamięci, która została odwzorowana na przestrzeń adresową procesu lub rozmiar zestawu rezydentnego. Jest to znacznie mniej (420 296 kB lub około 410 megabajtów). Różnica: mój program zmapował 636 MB za pomocą mmap (), ale uzyskał do niej dostęp tylko 410 MB, a zatem przypisano mu tylko 410 MB stron.

Jedynym elementem, którego nie jestem pewien, jest Swapspace, który jest obecnie używany przez mój proces . Nie wiem czy to jest dostępne.

Martin Del Vecchio
źródło
1
Dzięki Martinowi, gdybym zebrał wszystkie te informacje w jednym miejscu, zaoszczędziłoby mi to kilka nieprzespanych nocy ... Jednak dla procesora bieżącego procesu myślę, że funkcja times () jest łatwiejszym i bardziej niezawodnym podejściem. BTW: Swapspace bieżącego procesu = VmSize - VmRSS, prawda?
Lanzelot
14

w systemie Windows można uzyskać użycie procesora przez poniższy kod:

#include <windows.h>
#include <stdio.h>

    //------------------------------------------------------------------------------------------------------------------
    // Prototype(s)...
    //------------------------------------------------------------------------------------------------------------------
    CHAR cpuusage(void);

    //-----------------------------------------------------
    typedef BOOL ( __stdcall * pfnGetSystemTimes)( LPFILETIME lpIdleTime, LPFILETIME lpKernelTime, LPFILETIME lpUserTime );
    static pfnGetSystemTimes s_pfnGetSystemTimes = NULL;

    static HMODULE s_hKernel = NULL;
    //-----------------------------------------------------
    void GetSystemTimesAddress()
    {
        if( s_hKernel == NULL )
        {   
            s_hKernel = LoadLibrary( L"Kernel32.dll" );
            if( s_hKernel != NULL )
            {
                s_pfnGetSystemTimes = (pfnGetSystemTimes)GetProcAddress( s_hKernel, "GetSystemTimes" );
                if( s_pfnGetSystemTimes == NULL )
                {
                    FreeLibrary( s_hKernel ); s_hKernel = NULL;
                }
            }
        }
    }
    //----------------------------------------------------------------------------------------------------------------

    //----------------------------------------------------------------------------------------------------------------
    // cpuusage(void)
    // ==============
    // Return a CHAR value in the range 0 - 100 representing actual CPU usage in percent.
    //----------------------------------------------------------------------------------------------------------------
    CHAR cpuusage()
    {
        FILETIME               ft_sys_idle;
        FILETIME               ft_sys_kernel;
        FILETIME               ft_sys_user;

        ULARGE_INTEGER         ul_sys_idle;
        ULARGE_INTEGER         ul_sys_kernel;
        ULARGE_INTEGER         ul_sys_user;

        static ULARGE_INTEGER    ul_sys_idle_old;
        static ULARGE_INTEGER  ul_sys_kernel_old;
        static ULARGE_INTEGER  ul_sys_user_old;

        CHAR  usage = 0;

        // we cannot directly use GetSystemTimes on C language
        /* add this line :: pfnGetSystemTimes */
        s_pfnGetSystemTimes(&ft_sys_idle,    /* System idle time */
            &ft_sys_kernel,  /* system kernel time */
            &ft_sys_user);   /* System user time */

        CopyMemory(&ul_sys_idle  , &ft_sys_idle  , sizeof(FILETIME)); // Could been optimized away...
        CopyMemory(&ul_sys_kernel, &ft_sys_kernel, sizeof(FILETIME)); // Could been optimized away...
        CopyMemory(&ul_sys_user  , &ft_sys_user  , sizeof(FILETIME)); // Could been optimized away...

        usage  =
            (
            (
            (
            (
            (ul_sys_kernel.QuadPart - ul_sys_kernel_old.QuadPart)+
            (ul_sys_user.QuadPart   - ul_sys_user_old.QuadPart)
            )
            -
            (ul_sys_idle.QuadPart-ul_sys_idle_old.QuadPart)
            )
            *
            (100)
            )
            /
            (
            (ul_sys_kernel.QuadPart - ul_sys_kernel_old.QuadPart)+
            (ul_sys_user.QuadPart   - ul_sys_user_old.QuadPart)
            )
            );

        ul_sys_idle_old.QuadPart   = ul_sys_idle.QuadPart;
        ul_sys_user_old.QuadPart   = ul_sys_user.QuadPart;
        ul_sys_kernel_old.QuadPart = ul_sys_kernel.QuadPart;

        return usage;
    }
    //------------------------------------------------------------------------------------------------------------------
    // Entry point
    //------------------------------------------------------------------------------------------------------------------
    int main(void)
    {
        int n;
        GetSystemTimesAddress();
        for(n=0;n<20;n++)
        {
            printf("CPU Usage: %3d%%\r",cpuusage());
            Sleep(2000);
        }
        printf("\n");
        return 0;
    }
sayyed mohsen zahraee
źródło
czy można to zmienić dla zewnętrznej funkcji DLL, którą później mogę wywołać w c #?
Nico,
11
Formatowanie usage =jest najbardziej kreatywną rzeczą, jaką widziałem od jakiegoś czasu, w ogóle nieczytelną, ale kreatywną
ViRuSTriNiTy
Ostrzeżenie: wyrażenie w powyższym kodzie, które oblicza „użycie”, jest dalekie. Gdyby system był bezczynny, dzieliłby się przez zero. Gdyby czas bezczynności był równy czasowi użytkownika + czas jądra, wygenerowałby 0, a nie 50%, jak można by się spodziewać.
Andrei Belogortseff,
Należy również pamiętać, że zgodnie z bieżącym MSDN czas jądra obejmuje również czas bezczynności!
Andrei Belogortseff
@sayyedmohsenzahraee: Nie przyjrzałem się logice, tylko komentarz do samego kodu. 1) Użyj zwykłych zmiennych 64-bitowych zamiast unii, tzn. ULONGLONGDla VS zamiast ULARGE_INTEGER. 2) Dzwonisz, nadmiernie komplikujesz rzeczy CopyMemory(), po prostu zrób to ULONGLONG ul_sys_idle = *(ULONGLONG*)&ft_sys_idle;zamiast tego. Zostanie przetłumaczony na pojedynczą instrukcję CPU mov(lub lea).
ahmd0
12

Linux

Przenośnym sposobem odczytu pamięci i numerów ładowania jest sysinfopołączenie

Stosowanie

   #include <sys/sysinfo.h>

   int sysinfo(struct sysinfo *info);

OPIS

   Until Linux 2.3.16, sysinfo() used to return information in the
   following structure:

       struct sysinfo {
           long uptime;             /* Seconds since boot */
           unsigned long loads[3];  /* 1, 5, and 15 minute load averages */
           unsigned long totalram;  /* Total usable main memory size */
           unsigned long freeram;   /* Available memory size */
           unsigned long sharedram; /* Amount of shared memory */
           unsigned long bufferram; /* Memory used by buffers */
           unsigned long totalswap; /* Total swap space size */
           unsigned long freeswap;  /* swap space still available */
           unsigned short procs;    /* Number of current processes */
           char _f[22];             /* Pads structure to 64 bytes */
       };

   and the sizes were given in bytes.

   Since Linux 2.3.23 (i386), 2.3.48 (all architectures) the structure
   is:

       struct sysinfo {
           long uptime;             /* Seconds since boot */
           unsigned long loads[3];  /* 1, 5, and 15 minute load averages */
           unsigned long totalram;  /* Total usable main memory size */
           unsigned long freeram;   /* Available memory size */
           unsigned long sharedram; /* Amount of shared memory */
           unsigned long bufferram; /* Memory used by buffers */
           unsigned long totalswap; /* Total swap space size */
           unsigned long freeswap;  /* swap space still available */
           unsigned short procs;    /* Number of current processes */
           unsigned long totalhigh; /* Total high memory size */
           unsigned long freehigh;  /* Available high memory size */
           unsigned int mem_unit;   /* Memory unit size in bytes */
           char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding to 64 bytes */
       };

   and the sizes are given as multiples of mem_unit bytes.
Mark Lakata
źródło
3

QNX

Ponieważ jest to jak „wikipage kodu”, chcę dodać kod z bazy wiedzy QNX (uwaga: to nie moja praca, ale sprawdziłem ją i działa dobrze w moim systemie):

Jak uzyskać użycie procesora w%: http://www.qnx.com/support/knowledgebase.html?id=50130000000P9b5

#include <atomic.h>
#include <libc.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/iofunc.h>
#include <sys/neutrino.h>
#include <sys/resmgr.h>
#include <sys/syspage.h>
#include <unistd.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/debug.h>
#include <sys/procfs.h>
#include <sys/syspage.h>
#include <sys/neutrino.h>
#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#include <devctl.h>
#include <errno.h>

#define MAX_CPUS 32

static float Loads[MAX_CPUS];
static _uint64 LastSutime[MAX_CPUS];
static _uint64 LastNsec[MAX_CPUS];
static int ProcFd = -1;
static int NumCpus = 0;


int find_ncpus(void) {
    return NumCpus;
}

int get_cpu(int cpu) {
    int ret;
    ret = (int)Loads[ cpu % MAX_CPUS ];
    ret = max(0,ret);
    ret = min(100,ret);
    return( ret );
}

static _uint64 nanoseconds( void ) {
    _uint64 sec, usec;
    struct timeval tval;
    gettimeofday( &tval, NULL );
    sec = tval.tv_sec;
    usec = tval.tv_usec;
    return( ( ( sec * 1000000 ) + usec ) * 1000 );
}

int sample_cpus( void ) {
    int i;
    debug_thread_t debug_data;
    _uint64 current_nsec, sutime_delta, time_delta;
    memset( &debug_data, 0, sizeof( debug_data ) );

    for( i=0; i<NumCpus; i++ ) {
        /* Get the sutime of the idle thread #i+1 */
        debug_data.tid = i + 1;
        devctl( ProcFd, DCMD_PROC_TIDSTATUS,
        &debug_data, sizeof( debug_data ), NULL );
        /* Get the current time */
        current_nsec = nanoseconds();
        /* Get the deltas between now and the last samples */
        sutime_delta = debug_data.sutime - LastSutime[i];
        time_delta = current_nsec - LastNsec[i];
        /* Figure out the load */
        Loads[i] = 100.0 - ( (float)( sutime_delta * 100 ) / (float)time_delta );
        /* Flat out strange rounding issues. */
        if( Loads[i] < 0 ) {
            Loads[i] = 0;
        }
        /* Keep these for reference in the next cycle */
        LastNsec[i] = current_nsec;
        LastSutime[i] = debug_data.sutime;
    }
    return EOK;
}

int init_cpu( void ) {
    int i;
    debug_thread_t debug_data;
    memset( &debug_data, 0, sizeof( debug_data ) );
/* Open a connection to proc to talk over.*/
    ProcFd = open( "/proc/1/as", O_RDONLY );
    if( ProcFd == -1 ) {
        fprintf( stderr, "pload: Unable to access procnto: %s\n",strerror( errno ) );
        fflush( stderr );
        return -1;
    }
    i = fcntl(ProcFd,F_GETFD);
    if(i != -1){
        i |= FD_CLOEXEC;
        if(fcntl(ProcFd,F_SETFD,i) != -1){
            /* Grab this value */
            NumCpus = _syspage_ptr->num_cpu;
            /* Get a starting point for the comparisons */
            for( i=0; i<NumCpus; i++ ) {
                /*
                * the sutime of idle thread is how much
                * time that thread has been using, we can compare this
                * against how much time has passed to get an idea of the
                * load on the system.
                */
                debug_data.tid = i + 1;
                devctl( ProcFd, DCMD_PROC_TIDSTATUS, &debug_data, sizeof( debug_data ), NULL );
                LastSutime[i] = debug_data.sutime;
                LastNsec[i] = nanoseconds();
            }
            return(EOK);
        }
    }
    close(ProcFd);
    return(-1);
}

void close_cpu(void){
    if(ProcFd != -1){
        close(ProcFd);
        ProcFd = -1;
    }
}

int main(int argc, char* argv[]){
    int i,j;
    init_cpu();
    printf("System has: %d CPUs\n", NumCpus);
    for(i=0; i<20; i++) {
        sample_cpus();
        for(j=0; j<NumCpus;j++)
        printf("CPU #%d: %f\n", j, Loads[j]);
        sleep(1);
    }
    close_cpu();
}

Jak uzyskać bezpłatną (!) Pamięć: http://www.qnx.com/support/knowledgebase.html?id=50130000000mlbbx

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <err.h>
#include <sys/stat.h>
#include <sys/types.h>

int main( int argc, char *argv[] ){
    struct stat statbuf;
    paddr_t freemem;
    stat( "/proc", &statbuf );
    freemem = (paddr_t)statbuf.st_size;
    printf( "Free memory: %d bytes\n", freemem );
    printf( "Free memory: %d KB\n", freemem / 1024 );
    printf( "Free memory: %d MB\n", freemem / ( 1024 * 1024 ) );
    return 0;
} 
Boernii
źródło
1

Mac OS X - procesor

Ogólne użycie procesora:

Od Pobierz informacje o systemie na MacOS X? :

#include <mach/mach_init.h>
#include <mach/mach_error.h>
#include <mach/mach_host.h>
#include <mach/vm_map.h>

static unsigned long long _previousTotalTicks = 0;
static unsigned long long _previousIdleTicks = 0;

// Returns 1.0f for "CPU fully pinned", 0.0f for "CPU idle", or somewhere in between
// You'll need to call this at regular intervals, since it measures the load between
// the previous call and the current one.
float GetCPULoad()
{
   host_cpu_load_info_data_t cpuinfo;
   mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT;
   if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&cpuinfo, &count) == KERN_SUCCESS)
   {
      unsigned long long totalTicks = 0;
      for(int i=0; i<CPU_STATE_MAX; i++) totalTicks += cpuinfo.cpu_ticks[i];
      return CalculateCPULoad(cpuinfo.cpu_ticks[CPU_STATE_IDLE], totalTicks);
   }
   else return -1.0f;
}

float CalculateCPULoad(unsigned long long idleTicks, unsigned long long totalTicks)
{
  unsigned long long totalTicksSinceLastTime = totalTicks-_previousTotalTicks;
  unsigned long long idleTicksSinceLastTime  = idleTicks-_previousIdleTicks;
  float ret = 1.0f-((totalTicksSinceLastTime > 0) ? ((float)idleTicksSinceLastTime)/totalTicksSinceLastTime : 0);
  _previousTotalTicks = totalTicks;
  _previousIdleTicks  = idleTicks;
  return ret;
}
souch
źródło
0

W systemie Linux można również użyć / proc / self / statm, aby uzyskać pojedynczy wiersz liczb zawierający kluczowe informacje o pamięci procesu, co jest szybsze niż przetworzenie długiej listy zgłaszanych informacji uzyskanych z proc / self / status

Zobacz http://man7.org/linux/man-pages/man5/proc.5.html

   /proc/[pid]/statm
          Provides information about memory usage, measured in pages.
          The columns are:

              size       (1) total program size
                         (same as VmSize in /proc/[pid]/status)
              resident   (2) resident set size
                         (same as VmRSS in /proc/[pid]/status)
              shared     (3) number of resident shared pages (i.e., backed by a file)
                         (same as RssFile+RssShmem in /proc/[pid]/status)
              text       (4) text (code)
              lib        (5) library (unused since Linux 2.6; always 0)
              data       (6) data + stack
              dt         (7) dirty pages (unused since Linux 2.6; always 0)
Steven Warner
źródło
1
Czy ktoś wie, jak wiarygodny jest całkowity rozmiar podany na stronach? Czy to rzeczywisty ślad w mem na stronach?
niken
-1

Użyłem tego kodu w moim projekcie C ++ i działało dobrze:

static HANDLE self;
static int numProcessors;
SYSTEM_INFO sysInfo;

double percent;

numProcessors = sysInfo.dwNumberOfProcessors;

//Getting system times information
FILETIME SysidleTime;
FILETIME SyskernelTime; 
FILETIME SysuserTime; 
ULARGE_INTEGER SyskernelTimeInt, SysuserTimeInt;
GetSystemTimes(&SysidleTime, &SyskernelTime, &SysuserTime);
memcpy(&SyskernelTimeInt, &SyskernelTime, sizeof(FILETIME));
memcpy(&SysuserTimeInt, &SysuserTime, sizeof(FILETIME));
__int64 denomenator = SysuserTimeInt.QuadPart + SyskernelTimeInt.QuadPart;  

//Getting process times information
FILETIME ProccreationTime, ProcexitTime, ProcKernelTime, ProcUserTime;
ULARGE_INTEGER ProccreationTimeInt, ProcexitTimeInt, ProcKernelTimeInt, ProcUserTimeInt;
GetProcessTimes(self, &ProccreationTime, &ProcexitTime, &ProcKernelTime, &ProcUserTime);
memcpy(&ProcKernelTimeInt, &ProcKernelTime, sizeof(FILETIME));
memcpy(&ProcUserTimeInt, &ProcUserTime, sizeof(FILETIME));
__int64 numerator = ProcUserTimeInt.QuadPart + ProcKernelTimeInt.QuadPart;
//QuadPart represents a 64-bit signed integer (ULARGE_INTEGER)

percent = 100*(numerator/denomenator);
Salman Ghaffar
źródło
Nie masz wartości takich jak 0,00% i wartości przekraczających 100% dzięki temu mechanizmowi?
Buddhika Chaturanga
Czy to dla Mac OS?
RuLoViC
@RuLoViC It's for Windows.
nieprostokątny