如何从进程内部确定CPU和内存消耗?

时间:2020-03-05 18:53:34  来源:igfitidea点击:

我曾经负责从正在运行的应用程序中确定以下性能参数的任务:

  • 可用虚拟内存总量
  • 当前使用的虚拟内存
  • 我的进程当前使用的虚拟内存
  • 总可用RAM
  • 当前使用的RAM
  • 我的进程当前使用的RAM
  • 当前使用的CPU百分比
  • 我的进程当前使用的CPU百分比

该代码必须在Windows和Linux上运行。尽管这似乎是一项标准任务,但在手册(WIN32 API,GNU文档)以及互联网上找到必要的信息还是花了我几天的时间,因为关于此主题的信息太多,不完整/不正确/过时发现在那里。

为了使其他人免于遭受同样的麻烦,我认为最好将所有分散的信息以及我通过反复试验发现的信息收集到一个地方。

解决方案

回答

视窗

可以从相应的WIN32 API轻松获得上述某些值,此处出于完整性的目的仅在此处列出。但是,其他一些则需要从Performance Data Helper库(PDH)中获得,该库有点"不直观",并且需要大量痛苦的反复试验才能开始工作。 (至少花了我一段时间,也许我只是有点愚蠢...)

注意:为清楚起见,以下代码中省略了所有错误检查。请检查返回码...!

  • 总虚拟内存:
#include "windows.h"

MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(MEMORYSTATUSEX);
GlobalMemoryStatusEx(&memInfo);
DWORDLONG totalVirtualMem = memInfo.ullTotalPageFile;

注意:名称" TotalPageFile"在这里有点误导。实际上,此参数给出了"虚拟内存大小",即交换文件加上已安装的RAM的大小。

  • 当前使用的虚拟内存:与"总虚拟内存"中的代码相同,然后
DWORDLONG virtualMemUsed = memInfo.ullTotalPageFile - memInfo.ullAvailPageFile;
  • 当前进程当前使用的虚拟内存:
#include "windows.h"
#include "psapi.h"

PROCESS_MEMORY_COUNTERS_EX pmc;
GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
SIZE_T virtualMemUsedByMe = pmc.PrivateUsage;
  • 总物理内存(RAM):与"总虚拟内存"中的代码相同,然后
DWORDLONG totalPhysMem = memInfo.ullTotalPhys;
  • 当前使用的物理内存:
Same code as in "Total Virtual Memory" and then

DWORDLONG physMemUsed = memInfo.ullTotalPhys - memInfo.ullAvailPhys;
  • 当前进程当前使用的物理内存:与"当前进程当前使用的虚拟内存"中的代码相同,然后
SIZE_T physMemUsedByMe = pmc.WorkingSetSize;
  • 当前使用的CPU:
#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;
}
  • 当前进程当前使用的CPU:
#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

在Linux上,一开始似乎很明显的选择是使用POSIX API(如getrusage()等)。我花了一些时间试图使之生效,但从未获得有意义的值。当我最终检查内核源代码本身时,我发现显然这些API到Linux内核2.6为止还没有完全实现!

最后,通过读取伪文件系统/ proc和内核调用,我得到了所有值。

  • 总虚拟内存:
#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;
  • 当前使用的虚拟内存:与"总虚拟内存"中的代码相同,然后
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;
  • 当前进程当前使用的虚拟内存:
#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] = '
long long totalPhysMem = memInfo.totalram;
//Multiply in next statement to avoid int overflow on right hand side...
totalPhysMem *= memInfo.mem_unit;
'; 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; }
  • 总物理内存(RAM):与"总虚拟内存"中的代码相同,然后
long long physMemUsed = memInfo.totalram - memInfo.freeram;
//Multiply in next statement to avoid int overflow on right hand side...
physMemUsed *= memInfo.mem_unit;
  • 当前使用的物理内存:与"总虚拟内存"中的代码相同,然后
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;
}
  • 当前进程当前使用的物理内存:如下更改"当前进程当前使用的虚拟内存"中的getValue():
#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;
}
  • 当前使用的CPU:
#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;
}
  • 当前进程当前使用的CPU:
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

待办事项:其他平台

我想,除了读取/ proc伪文件系统的部分之外,某些Linux代码也适用于Unix。也许在Unix上,这些部分可以由getrusage()和类似的函数替换?
如果具有Unix专业知识的人可以编辑此答案并填写详细信息?!

回答

在Linux中,此信息在/ proc文件系统中可用。我不喜欢使用的文本文件格式,因为每个Linux发行版似乎都至少自定义了一个重要文件。快速浏览一下作为" ps"来源的信息,即可发现混乱情况。

但是在这里可以找到我们想要的信息:

/ proc / meminfo包含我们要查找的大多数系统范围的信息。在这里看起来像在我的系统上;我认为我们对MemTotal,MemFree,SwapTotal和SwapFree感兴趣:

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

为了提高CPU利用率,我们需要做一些工作。自系统启动以来,Linux提供了可用的总体CPU利用率。这可能不是我们感兴趣的。如果我们想知道最后一秒或者10秒的CPU使用率,那么我们需要查询信息并自己计算。

该信息可在/ proc / stat中找到,该文件在http://www.linuxhowtos.org/System/procstat.htm上有很好的记录。这是我的4核盒子上的样子:

cpu  2330047 0 2365006 1063853632 9035 9463 96114 0

首先,我们需要确定系统中有多少个CPU(或者处理器或者处理核心)可用。为此,计算" cpuN"条目的数量,其中N从0开始并递增。不要计算" cpu"行,它是cpuN行的组合。在我的示例中,我们可以看到cpu0到cpu3,共有4个处理器。从现在开始,我们可以忽略cpu0..cpu3,而只关注'cpu'行。

接下来,我们需要知道这些行中的第四个数字是空闲时间的量度,因此," cpu"行中的第四个数字是自启动时间以来所有处理器的总空闲时间。此时间是在Linux"跳动"中测量的,每个"跳动"为1/100秒。

但是我们并不在乎总的空闲时间。我们关心的是给定时间段(例如最后一秒)中的空闲时间。计算一下,我们需要两次读取文件(相隔1秒),然后可以对行的第四个值进行比较。例如,如果我们取样并获得:

cpu  2330047 0 2365007 1063854028 9035 9463 96114 0

然后一秒钟后,我们将获得以下示例:

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

将两个数字相减,得出的差异为396,这意味着CPU在最近的1.00秒中空闲了3.96秒。当然,诀窍在于我们需要除以处理器数量。 3.96 / 4 = 0.99,这里有空闲百分比; 99%空闲,而1%忙。

在我的代码中,我有360个条目的环形缓冲区,并且每秒读取一次此文件。这样一来,我可以快速计算出1秒,10秒等的CPU利用率,一直到1小时。

有关特定于进程的信息,我们必须查看/ proc / pid;如果我们不在乎pid,则可以查看/ proc / self。

进程使用的CPU在/ proc / self / stat中可用。这是一个奇怪的文件,由一行组成;例如:

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

这里的重要数据是第13和第14个令牌(此处为0和770)。第13个标记是该进程在用户模式下执行的延迟的数量,第14个标记是该进程在内核模式下执行的延迟的数量。将两者加在一起,就可以得出其总的CPU利用率。

同样,我们将必须定期对该文件进行采样,并计算差异,以便确定一段时间内该进程的CPU使用率。

编辑:请记住,在计算进程的CPU使用率时,必须考虑1)进程中的线程数,以及2)系统中的处理器数。例如,如果单线程进程仅使用25%的CPU,则可能是好是坏。在单处理器系统上是好的,但是在四处理器系统上是不好的;这意味着进程一直在运行,并使用了100%的可用CPU周期。

有关特定于进程的内存信息,我们必须查看/ proc / self / status,如下所示:

Note: Unlike most Unix-based operating systems, Mac OS X does not use a preallocated swap partition for virtual memory. Instead, it uses all of the available space on the machine’s boot partition.

以" Vm"开头的条目很有趣:

  • VmPeak是进程使用的最大虚拟内存空间,以kB(1024字节)为单位。
  • VmSize是该进程使用的当前虚拟内存空间,以kB为单位。在我的示例中,它非常大:651,352 kB,约636兆字节。
  • VmRss是已映射到进程的地址空间或者其驻留集大小的内存量。这要小得多(420,296 kB,或者大约410兆字节)。区别在于:我的程序通过mmap()映射了636 MB,但仅访问了410 MB,因此仅分配了410 MB的页面。

我不确定的唯一项目是我的进程当前使用的交换空间。我不知道这是否可用。

回答

我也希望能为Mac OS X找到类似的信息。由于不在这里,我自己出去挖了出来。这是我发现的一些东西。如果有人有其他建议,我很想听听他们的建议。

虚拟内存总量

在Mac OS X上,这是一个棘手的问题,因为它不使用预设的交换分区或者类似Linux的文件。这是Apple文档中的条目:

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

因此,如果我们想知道仍有多少虚拟内存可用,则需要获取根分区的大小。我们可以这样做:

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

当前使用的虚拟总数

使用" vm.swapusage"键调用systcl可提供有关交换使用情况的有趣信息:

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\",...)" );
}

如果需要更多的交换,则上面显示的交换总使用量不会改变,如上一节所述。因此,总数实际上是当前的交换总数。在C ++中,可以通过以下方式查询此数据:

#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;

请注意,似乎没有记录在sysctl.h中声明的" xsw_usage",我怀疑还有一种更便捷的方式来访问这些值。

我的进程当前使用的虚拟内存

我们可以使用task_info函数获取有关当前进程的统计信息。这包括进程的当前驻留大小和当前虚拟大小。

#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);

总可用RAM

可以使用sysctl系统功能来获得系统中可用的物理RAM量,如下所示:

#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;
}

当前使用的RAM

我们可以从host_statistics系统功能获取常规内存统计信息。

##代码##

这里要注意的一件事是Mac OS X中有五种类型的内存页面。它们如下:

  • 已锁定到位且无法换出的有线页面
  • 活动页面正在加载到物理内存中,并且换出起来相对困难
  • 加载到内存中的非活动页面,但是最近没有使用过,甚至可能根本不需要。这些是可能的交换对象。该内存可能需要刷新。
  • 缓存页面的缓存方式可能​​很容易重用。缓存的内存可能不需要刷新。仍然有可能重新激活缓存的页面
  • 完全免费并可以使用的免费页面。

值得一提的是,仅仅因为Mac OS X有时可能显示很少的实际可用内存,所以它可能并不能很好地指示我们准备在短时间内使用多少内存。

我的进程当前使用的RAM

请参阅上面的"我的进程当前使用的虚拟内存"。相同的代码适用。