C/C++ tip: How to get the process resident set size (physical memory use)

Topics: C/C++

The "Resident set size" ("Working set size" on Windows) is the amount of physical memory (RAM) used by a process's code and data. Monitoring size changes is an important way to find memory leaks and improve performance, but methods to get this data differ between Windows, Linux, OSX, BSD, Solaris, and others. This article provides cross-platform functions to get the peak (maximum) and current resident set size of a process, and explains what works on what OS.

How to get the resident set size (RSS)

A process's resident set grows as heap space is allocated, shrinks (sometimes) as heap space is freed, and changes as shared libraries are loaded and process pages are moved in and out of memory. Memory leaks cause the resident set to grow without bound, and large resident sets exceeding available physical memory cause a process to thrash as the OS desperately tries to manage memory to give the process what it needs.

Most OSes track the current and peak (maximum so far) resident set sizes for each process. Tools like POSIX ps, OSX Activity Monitor, and Windows Task Manager display resident set sizes, but it's often useful to track these values from within the process itself. While most OSes provide APIs or other methods to access this data, each OS has its quirks.

Code

The following getPeakRSS( ) and getCurrentRSS( ) functions work for most OSes (copy and paste, or download getRSS.c). On Windows, link with Microsoft's psapi.lib. On other OSes, the default libraries are sufficient.

See the sections that follow for discussion, caveats, and why this code requires so many #ifdef's.

/*
 * Author:  David Robert Nadeau
 * Site:    http://NadeauSoftware.com/
 * License: Creative Commons Attribution 3.0 Unported License
 *          http://creativecommons.org/licenses/by/3.0/deed.en_US
 */

#if defined(_WIN32)
#include <windows.h>
#include <psapi.h>

#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
#include <unistd.h>
#include <sys/resource.h>

#if defined(__APPLE__) && defined(__MACH__)
#include <mach/mach.h>

#elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
#include <fcntl.h>
#include <procfs.h>

#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
#include <stdio.h>

#endif

#else
#error "Cannot define getPeakRSS( ) or getCurrentRSS( ) for an unknown OS."
#endif





/**
 * Returns the peak (maximum so far) resident set size (physical
 * memory use) measured in bytes, or zero if the value cannot be
 * determined on this OS.
 */
size_t getPeakRSS( )
{
#if defined(_WIN32)
	/* Windows -------------------------------------------------- */
	PROCESS_MEMORY_COUNTERS info;
	GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) );
	return (size_t)info.PeakWorkingSetSize;

#elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
	/* AIX and Solaris ------------------------------------------ */
	struct psinfo psinfo;
	int fd = -1;
	if ( (fd = open( "/proc/self/psinfo", O_RDONLY )) == -1 )
		return (size_t)0L;		/* Can't open? */
	if ( read( fd, &psinfo, sizeof(psinfo) ) != sizeof(psinfo) )
	{
		close( fd );
		return (size_t)0L;		/* Can't read? */
	}
	close( fd );
	return (size_t)(psinfo.pr_rssize * 1024L);

#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
	/* BSD, Linux, and OSX -------------------------------------- */
	struct rusage rusage;
	getrusage( RUSAGE_SELF, &rusage );
#if defined(__APPLE__) && defined(__MACH__)
	return (size_t)rusage.ru_maxrss;
#else
	return (size_t)(rusage.ru_maxrss * 1024L);
#endif

#else
	/* Unknown OS ----------------------------------------------- */
	return (size_t)0L;			/* Unsupported. */
#endif
}





/**
 * Returns the current resident set size (physical memory use) measured
 * in bytes, or zero if the value cannot be determined on this OS.
 */
size_t getCurrentRSS( )
{
#if defined(_WIN32)
	/* Windows -------------------------------------------------- */
	PROCESS_MEMORY_COUNTERS info;
	GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) );
	return (size_t)info.WorkingSetSize;

#elif defined(__APPLE__) && defined(__MACH__)
	/* OSX ------------------------------------------------------ */
	struct mach_task_basic_info info;
	mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
	if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO,
		(task_info_t)&info, &infoCount ) != KERN_SUCCESS )
		return (size_t)0L;		/* Can't access? */
	return (size_t)info.resident_size;

#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
	/* Linux ---------------------------------------------------- */
	long rss = 0L;
	FILE* fp = NULL;
	if ( (fp = fopen( "/proc/self/statm", "r" )) == NULL )
		return (size_t)0L;		/* Can't open? */
	if ( fscanf( fp, "%*s%ld", &rss ) != 1 )
	{
		fclose( fp );
		return (size_t)0L;		/* Can't read? */
	}
	fclose( fp );
	return (size_t)rss * (size_t)sysconf( _SC_PAGESIZE);

#else
	/* AIX, BSD, Solaris, and Unknown OS ------------------------ */
	return (size_t)0L;			/* Unsupported. */
#endif
}

Usage

Simply call either function to get the current and peak resident set size, in bytes, of the current process:

size_t currentSize = getCurrentRSS( );
size_t peakSize    = getPeakRSS( );

Discussion

Each OS has its own way of providing resident set sizes for a process:

OS Peak RSS Current RSS
AIX getrusage( ) or /proc/self/psinfo N/A
BSD getrusage( ) N/A
Linux getrusage( ), /proc/self/stat, /proc/self/statm, or /proc/self/status /proc/self/stat, /proc/self/statm, or /proc/self/status
OSX getrusage( ) or task_info( ) task_info( )
Solaris /proc/self/psinfo N/A
Windows GetCurrentProcess( ) GetCurrentProcess( )

Each of these is discussed below.

Note: AIX and Solaris, and some distributions of BSD (but deprecated by FreeBSD), include a Linux-like /proc pseudo-file system that contains a directory for each running process. For AIX and Solaris, /proc/[pid]/psinfo contains information about the process with id [pid], including the peak resident set size (see below). But there are no pseudo-files containing the current resident set size, as there are on Linux. There are no appropriate API functions either. Only the peak resident set size is available on AIX, BSD, and Solaris.

GetProcessMemoryInfo( ) for peak and current resident set size

On Windows, GetProcessMemoryInfo( ) fills a PROCESS_MEMORY_COUNTERS struct describing a process (link with psapi.lib). The PROCESS_MEMORY_COUNTERS struct contains the following fields:

typedef struct _PROCESS_MEMORY_COUNTERS {
  DWORD  cb;
  DWORD  PageFaultCount;
  SIZE_T PeakWorkingSetSize;
  SIZE_T WorkingSetSize;
  SIZE_T QuotaPeakPagedPoolUsage;
  SIZE_T QuotaPagedPoolUsage;
  SIZE_T QuotaPeakNonPagedPoolUsage;
  SIZE_T QuotaNonPagedPoolUsage;
  SIZE_T PagefileUsage;
  SIZE_T PeakPagefileUsage;
} PROCESS_MEMORY_COUNTERS, *PPROCESS_MEMORY_COUNTERS; 

The PeakWorkingSetSize and WorkingSetSize fields contain the process's peak and current resident set sizes, in bytes.

Availability: Windows XP or later.

Get peak RSS:

#include <windows.h>
#include <psapi.h>
...
	PROCESS_MEMORY_COUNTERS info;
	GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) );
	return (size_t)info.PeakWorkingSetSize;

Get current RSS:

#include <windows.h>
#include <psapi.h>
...
	PROCESS_MEMORY_COUNTERS info;
	GetProcessMemoryInfo( GetCurrentProcess( ), &anp;info, sizeof(info) );
	return (size_t)info.WorkingSetSize;

getrusage( ) for peak resident set size

For most UNIX-style OSes, the POSIX getrusage( ) function (see man pages for AIX, BSD, Linux, OSX, and Solaris versions) returns the resource usage of the current process, its child processes, or (for some OSes) the current thread. The function fills an rusage struct with the following fields:

struct rusage {
struct timeval ru_utime; /* user time used */
struct timeval ru_stime; /* system time used */
long ru_maxrss; /* maximum resident set size */
long ru_ixrss; /* integral shared memory size */
long ru_idrss; /* integral unshared data size */
long ru_isrss; /* integral unshared stack size */
long ru_minflt; /* page reclaims */
long ru_majflt; /* page faults */
long ru_nswap; /* swaps */
long ru_inblock; /* block input operations */
long ru_oublock; /* block output operations */
long ru_msgsnd; /* messages sent */
long ru_msgrcv; /* messages received */
long ru_nsignals; /* signals received */
long ru_nvcsw; /* voluntary context switches */
long ru_nivcsw; /* involuntary context switches */
};

Unfortunately, the POSIX standard only defines the ru_utime and ru_stime fields. The other fields are inherited from BSD and aren't supported by all OSes (such as Solaris). When supported, the ru_maxrss field provides the peak resident set size, measured in bytes (OSX) or kilobytes (AIX, BSD, and Linux). There isn't a field for the current resident set size.

Availability: AIX, BSD, Linux (2.6.32 kernel and later), and OSX, but not Solaris.

Get peak RSS:

#include <sys/resource.h>
...
	struct rusage rusage;
	getrusage( RUSAGE_SELF, &rusage );
#if defined(__APPLE__) && defined(__MACH__)
	return (size_t)rusage.ru_maxrss;
#else
	return (size_t)(rusage.ru_maxrss * 1024L);
#endif

task_info( ) for current resident set size

For OSX (based on the open-source MACH kernel), the task_info( ) function returns information about a selected task (process). The function fills a mach_task_basic_info struct with the following fields:

struct mach_task_basic_info {
        mach_vm_size_t  virtual_size;       /* virtual memory size (bytes) */
        mach_vm_size_t  resident_size;      /* resident memory size (bytes) */
        mach_vm_size_t  resident_size_max;  /* maximum resident memory size (bytes) */
        time_value_t    user_time;          /* total user run time for terminated threads */
        time_value_t    system_time;        /* total system run time for terminated threads */
        policy_t        policy;             /* default policy for new threads */
        integer_t       suspend_count;      /* suspend count for task */
};

The resident_size and resident_size_max fields contain the current and peak resident set sizes. Both values are in bytes (though open source MACH uses kilobytes). The resident_size_max field reports the same value returned by getrusage( ) above.

Availability: OSX.

Get current RSS:

#include <mach/mach.h>
...
	struct mach_task_basic_info info;
	mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
	if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO,
		(task_info_t)&info, &infoCount ) != KERN_SUCCESS )
		return (size_t)0L;		/* Can't access? */
	return (size_t)info.resident_size;

/proc/self/psinfo for peak resident set size

On AIX and Solaris, the /proc pseudo-file system (see man pages for AIX and Solaris versions) contains a directory for each running process. The /proc/[pid]/psinfo pseudo-file for the process with id [pid] contains a psinfo struct with the following fields describing the process:

typedef struct psinfo {
   int pr_flag;             /* process flags (DEPRECATED) */
   int pr_nlwp;             /* number of active lwps in the process */
   int pr_nzomb;            /* number of zombie lwps in the process */
   pid_t pr_pid;            /* process id */
   pid_t pr_ppid;           /* process id of parent */
   pid_t pr_pgid;           /* process id of process group leader */
   pid_t pr_sid;            /* session id */
   uid_t pr_uid;            /* real user id */
   uid_t pr_euid;           /* effective user id */
   gid_t pr_gid;            /* real group id */
   gid_t pr_egid;           /* effective group id */
   uintptr_t pr_addr;       /* address of process */
   size_t pr_size;          /* size of process image in Kbytes */
   size_t pr_rssize;        /* resident set size in Kbytes */
   dev_t pr_ttydev;         /* controlling tty device (or PRNODEV) */
   ushort_t pr_pctcpu;      /* % of recent cpu time used by all lwps */
   ushort_t pr_pctmem;      /* % of system memory used by process */
   timestruc_t pr_start;    /* process start time, from the epoch */
   timestruc_t pr_time;     /* cpu time for this process */
   timestruc_t pr_ctime;    /* cpu time for reaped children */
   char pr_fname[PRFNSZ];   /* name of exec’ed file */
   char pr_psargs[PRARGSZ]; /* initial characters of arg list */
   int pr_wstat;            /* if zombie, the wait() status */
   int pr_argc;             /* initial argument count */
   uintptr_t pr_argv;       /* address of initial argument vector */
   uintptr_t pr_envp;       /* address of initial environment vector */
   char pr_dmodel;          /* data model of the process */
   lwpsinfo_t pr_lwp;       /* information for representative lwp */
   taskid_t pr_taskid;      /* task id */
   projid_t pr_projid;      /* project id */
   poolid_t pr_poolid;      /* pool id */
   zoneid_t pr_zoneid;      /* zone id */
   ctid_t pr_contract;      /* process contract id */
} psinfo_t;

The pr_rssize field contains the process's peak resident set size, in kilobytes. To get the field for the current process, open /proc/self/psinfo and read a psinfo struct.

Availability: AIX and Solaris.

Get peak RSS:

#include <unistd.h>
#include <fcntl.h>
#include <procfs.h>
...
	struct psinfo psinfo;
	int fd = -1;
	if ( (fd = open( "/proc/self/psinfo", O_RDONLY )) == -1 )
		return (size_t)0L;		/* Can't open? */
	if ( read( fd, &psinfo, sizeof(psinfo) ) != sizeof(psinfo) )
	{
		close( fd );
		return (size_t)0L;		/* Can't read? */
	}
	close( fd );
	return (size_t)(psinfo.pr_rssize * 1024L);

/proc/self/statm for current resident set size

On Linux, the /proc pseudo-file system contains a directory for each running or zombie process. The /proc/[pid]/stat, /proc/[pid]/statm, and /proc/[pid]/status pseudo-files for the process with id [pid] all contain a process's current resident set size, among other things. But the /proc/[pid]/statm pseudo-file is the easiest to read since it contains a single line of text with white-space delimited values:

  1. total program size
  2. resident set size
  3. shared pages
  4. text (code) size
  5. library size
  6. data size (heap + stack)
  7. dirty pages

The second value provides the process's current resident set size in pages. To get the field for the current process, open /proc/self/statm and parse the second integer value. Multiply the field by the page size from sysconf( ).

Availability: Linux.

Get current RSS:

#include <unistd.h>
#include <stdio.h>
...
	long rss = 0L;
	FILE* fp = NULL;
	if ( (fp = fopen( "/proc/self/statm", "r" )) == NULL )
		return (size_t)0L;		// Can't open? */
	if ( fscanf( fp, "%*s%ld", &rss ) != 1 )
	{
		fclose( fp );
		return (size_t)0L;		// Can't read? */
	}
	fclose( fp );
	return (size_t)rss * (size_t)sysconf( _SC_PAGESIZE);

Downloads

Further reading

Related articles at NadeauSoftware.com

Web articles

Comments

I appreciate your site

After digging around a lot about stuff like memory usage, I just found your wonderful site, it seems filled with great and portable code and use cases, explanation and discussion, really thank you.

OSX version

In OSX 10.6 I had to remove the MACH_ prefix from the MACH_TASK_BASIC_INFO* defines.

And use "struct task_basic_info" instead of "struct mach_task_basic_info".

Compilation error on OSX 10.4

Hello,
On OSX 10.4, I fail to compile getCurrentRSS with
libcuser.c: In function 'getCurrentRSS':
libcuser.c:132: error: storage size of 'info' isn't known // struct mach_task_basic_info info;
libcuser.c:134: error: 'MACH_TASK_BASIC_INFO_COUNT' undeclared (first use in this function) // mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
libcuser.c:134: error: (Each undeclared identifier is reported only once
libcuser.c:134: error: for each function it appears in.)
libcuser.c:136: error: 'MACH_TASK_BASIC_INFO' undeclared (first use in this function) // if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO,

Any idea ?

Re: Compilation error on OSX 10.4

I'm sorry, but it is not practical to test this code on ancient versions of OS X, or any other OS. This article was written in mid-2012 on OS X 10.7 (Lion). I'm not surprised there's a compilation error for OS X 10.4 (Tiger), which was released way back in 2005. Since newer OS's have new features, better performance, and better security I recommend that you upgrade, if possible.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

Nadeau software consulting
Nadeau software consulting