1 /*=========================================================================
2  *
3  *  Copyright Insight Software Consortium
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *         http://www.apache.org/licenses/LICENSE-2.0.txt
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *=========================================================================*/
18 #include "itkMemoryUsageObserver.h"
19 
20 #if defined( WIN32 ) || defined( _WIN32 )
21   #include <windows.h>
22   #if defined( SUPPORT_PSAPI )
23     #include <psapi.h>
24   #endif
25 #endif // defined(WIN32) || defined(_WIN32)
26 
27 #if defined( __SUNPRO_CC ) || defined ( __sun__ )
28   #include <unistd.h>
29   #include <cstdio>
30   #include <string>
31   #include <sstream>
32 #endif // !defined(__SUNPRO_CC) && !defined (__sun__)
33 
34 #if !defined( WIN32 ) && !defined( _WIN32 )
35   #include <sys/resource.h>     // getrusage()
36   #if defined( ITK_HAS_MALLINFO )
37     #include <malloc.h>           // mallinfo()
38   #endif // ITK_HAS_MALLINFO
39 #endif // !defined(WIN32) && !defined(_WIN32)
40 
41 #if defined( __OpenBSD__ )
42 #include <cstdlib>
43 #endif
44 
45 #ifdef __linux__
46 #include <fstream>
47 #include <unistd.h>
48 #endif
49 
50 #ifdef __APPLE__
51 #include <sys/sysctl.h>
52 #include <mach/mach.h>
53 #include <cstdint>
54 #include <unistd.h>
55 #endif
56 
57 namespace itk
58 {
59 MemoryUsageObserverBase::~MemoryUsageObserverBase() = default;
60 
61 #if defined( WIN32 ) || defined( _WIN32 )
62 
63 /**         ----         Windows Memory Usage Observer       ----       */
64 
WindowsMemoryUsageObserver()65 WindowsMemoryUsageObserver::WindowsMemoryUsageObserver()
66 {
67 #if defined( SUPPORT_TOOLHELP32 )
68   m_hNTLib = ::LoadLibraryA("ntdll.dll");
69   if ( m_hNTLib )
70     {
71     // load the support function from the kernel
72     ZwQuerySystemInformation = ( PZwQuerySystemInformation ) ::GetProcAddress(m_hNTLib,
73                                                                               "ZwQuerySystemInformation");
74     }
75 #endif
76 }
77 
~WindowsMemoryUsageObserver()78 WindowsMemoryUsageObserver::~WindowsMemoryUsageObserver()
79 {
80 #if defined ( SUPPORT_TOOLHELP32 )
81   if ( m_hNTLib )
82     {
83     FreeLibrary(m_hNTLib);
84     }
85 #endif
86 }
87 
88 #if defined( SUPPORT_TOOLHELP32 )
89 
90 #define STATUS_INFO_LENGTH_MISMATCH ( (NTSTATUS)0xC0000004L )
91 
92 using KPRIORITY = LONG;
93 #define SystemProcessesAndThreadsInformation    5
94 
95 typedef struct _CLIENT_ID {
96   DWORD UniqueProcess;
97   DWORD UniqueThread;
98 } CLIENT_ID;
99 
100 typedef struct _UNICODE_STRING {
101   USHORT Length;
102   USHORT MaximumLength;
103   PWSTR Buffer;
104 } UNICODE_STRING;
105 
106 typedef struct _VM_COUNTERS {
107 #ifdef _WIN64
108   // the following was inferred by painful reverse engineering
109   SIZE_T PeakVirtualSize;             // not actually
110   SIZE_T PageFaultCount;
111   SIZE_T PeakWorkingSetSize;
112   SIZE_T WorkingSetSize;
113   SIZE_T QuotaPeakPagedPoolUsage;
114   SIZE_T QuotaPagedPoolUsage;
115   SIZE_T QuotaPeakNonPagedPoolUsage;
116   SIZE_T QuotaNonPagedPoolUsage;
117   SIZE_T PagefileUsage;
118   SIZE_T PeakPagefileUsage;
119   SIZE_T VirtualSize;                 // not actually
120 #else
121   SIZE_T PeakVirtualSize;
122   SIZE_T VirtualSize;
123   ULONG PageFaultCount;
124   SIZE_T PeakWorkingSetSize;
125   SIZE_T WorkingSetSize;
126   SIZE_T QuotaPeakPagedPoolUsage;
127   SIZE_T QuotaPagedPoolUsage;
128   SIZE_T QuotaPeakNonPagedPoolUsage;
129   SIZE_T QuotaNonPagedPoolUsage;
130   SIZE_T PagefileUsage;
131   SIZE_T PeakPagefileUsage;
132 #endif
133 } VM_COUNTERS;
134 
135 typedef struct _SYSTEM_THREADS {
136   LARGE_INTEGER KernelTime;
137   LARGE_INTEGER UserTime;
138   LARGE_INTEGER CreateTime;
139   ULONG WaitTime;
140   PVOID StartAddress;
141   CLIENT_ID ClientId;
142   KPRIORITY Priority;
143   KPRIORITY BasePriority;
144   ULONG ContextSwitchCount;
145   LONG State;
146   LONG WaitReason;
147 } SYSTEM_THREADS, *PSYSTEM_THREADS;
148 
149 typedef struct _SYSTEM_PROCESSES { // Information Class 5
150   ULONG NextEntryDelta;
151   ULONG MaximumNumberOfThreads;
152   ULONG Reserved1[6];
153   LARGE_INTEGER CreateTime;
154   LARGE_INTEGER UserTime;
155   LARGE_INTEGER KernelTime;
156   UNICODE_STRING ProcessName;
157   KPRIORITY BasePriority;
158 #ifdef _WIN64
159   ULONG pad1;
160   ULONG ProcessId;
161   ULONG pad2;
162   ULONG InheritedFromProcessId;
163   ULONG pad3;
164   ULONG pad4;
165   ULONG pad5;
166 #else
167   ULONG ProcessId;
168   ULONG InheritedFromProcessId;
169 #endif
170   ULONG HandleCount;
171   ULONG Reserved2[2];
172   VM_COUNTERS VmCounters;
173 #if defined( _WIN64 ) || _WIN32_WINNT >= 0x500
174   IO_COUNTERS IoCounters;
175 #endif
176   SYSTEM_THREADS Threads[1];
177 } SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;
178 #endif
179 
180 MemoryUsageObserverBase::MemoryLoadType
GetMemoryUsage()181 WindowsMemoryUsageObserver::GetMemoryUsage()
182 {
183   MemoryLoadType mem = 0;
184 
185 #if defined( SUPPORT_PSAPI )
186   DWORD                   pid = GetCurrentProcessId();
187   PROCESS_MEMORY_COUNTERS memoryCounters;
188 
189   HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION
190                                 | PROCESS_VM_READ,
191                                 FALSE, pid);
192 
193   if ( nullptr == hProcess )
194     {
195     // Can't determine memory usage.
196     return 0;
197     }
198 
199   GetProcessMemoryInfo( hProcess, &memoryCounters, sizeof( memoryCounters ) );
200 
201   mem = static_cast< MemoryLoadType >(
202     static_cast< double >( memoryCounters.PagefileUsage )
203     / 1024.0 );
204 #elif defined( SUPPORT_TOOLHELP32 )
205 
206   /* Retrieve memory usage using Windows Native API. For more information,
207    * read the book "Windows NT 2000 Native API Reference"
208   */
209 
210   if ( !m_hNTLib )
211     {
212     itkGenericExceptionMacro(<< "Can't find ntdll.dll. "
213                              << "You should probably disable SUPPORT_TOOLHELP32");
214     }
215   // the ntdll.dll library could not have been opened (file not found?)
216   if ( !ZwQuerySystemInformation )
217     {
218     itkGenericExceptionMacro(<< "The file ntdll.dll is not supported. "
219                              << "You should probably disable SUPPORT_TOOLHELP32");
220     }
221 
222   DWORD             pid = GetCurrentProcessId();
223   ULONG             n = 50;
224   PSYSTEM_PROCESSES sp = new SYSTEM_PROCESSES[n];
225   // as we can't know how many processes are running, we loop and test a new size
226   // every time.
227   while ( ZwQuerySystemInformation(SystemProcessesAndThreadsInformation,
228                                    sp, n * sizeof *sp, 0)
229           == STATUS_INFO_LENGTH_MISMATCH )
230     {
231     delete[] sp;
232     n = n * 2;
233     sp = new SYSTEM_PROCESSES[n];
234     }
235   bool done = false;
236   for ( PSYSTEM_PROCESSES spp = sp;
237         !done;
238         spp = PSYSTEM_PROCESSES(PCHAR(spp) + spp->NextEntryDelta) )
239     {
240     // only the current process is interesting here
241     if ( spp->ProcessId == pid )
242       {
243       mem = static_cast< MemoryLoadType >(
244         static_cast< double >( spp->VmCounters.PagefileUsage - sizeof( *sp ) ) / 1024 );
245       break;
246       }
247     done = ( spp->NextEntryDelta == 0 );
248     }
249   delete[] sp;
250 
251 #else
252 
253   /* This solution is not optimal as it returns the system memory usage
254    * instead of the process memory usage.
255   */
256 
257   MEMORYSTATUSEX statex;
258 
259   statex.dwLength = sizeof( statex );
260 
261   GlobalMemoryStatusEx (&statex);
262 
263   mem   = static_cast< MemoryLoadType >(
264     static_cast< double >( statex.ullTotalPhys - statex.ullAvailPhys ) / 1024 );
265 #endif
266   return mem;
267 }
268 
269 #endif // WIN32
270 
271 #if defined(__linux__)
272 
273 /**         ----         Linux Memory Usage Observer       ----       */
274 
~LinuxMemoryUsageObserver()275 LinuxMemoryUsageObserver::~LinuxMemoryUsageObserver()
276 {}
277 
278 /** Get Memory Usage - Linux version.
279  *  Reference for method used:
280  *  http://stackoverflow.com/questions/669438/how-to-get-memory-usage-at-run-time-in-c
281  */
282 MemoryUsageObserverBase::MemoryLoadType
GetMemoryUsage()283 LinuxMemoryUsageObserver::GetMemoryUsage()
284 {
285   std::ifstream procstats("/proc/self/stat",std::ios_base::in);
286   // dummy vars for leading entries in stat that we don't care about
287   //
288   std::string pid, comm, state, ppid, pgrp, session, tty_nr;
289   std::string tpgid, flags, minflt, cminflt, majflt, cmajflt;
290   std::string utime, stime, cutime, cstime, priority, nice;
291   std::string O, itrealvalue, starttime;
292 
293   // the two fields we want
294   //
295   unsigned long vsize;
296   long rss;
297 
298   procstats >> pid >> comm >> state >> ppid >> pgrp >> session >> tty_nr
299               >> tpgid >> flags >> minflt >> cminflt >> majflt >> cmajflt
300               >> utime >> stime >> cutime >> cstime >> priority >> nice
301               >> O >> itrealvalue >> starttime >> vsize >> rss; // don't care about the rest
302 
303   procstats.close();
304 
305   long page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; // in case x86-64 is configured to use 2MB pages
306   //  vm_usage     = vsize / 1024.0;
307   return rss * page_size_kb;
308 }
309 
310 #endif // linux
311 
312 #if defined(__APPLE__)
313 
314 /**         ----         Mac OS X Memory Usage Observer       ----       */
315 
316 MacOSXMemoryUsageObserver::~MacOSXMemoryUsageObserver() = default;
317 
318 MemoryUsageObserverBase::MemoryLoadType
GetMemoryUsage()319 MacOSXMemoryUsageObserver::GetMemoryUsage()
320 {
321   //
322   // this method comes from
323   // http://stackoverflow.com/questions/5839626/how-is-top-able-to-see-memory-usage
324   task_t targetTask = mach_task_self();
325   struct task_basic_info ti;
326   mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT;
327   kern_return_t kr =
328     task_info(targetTask, TASK_BASIC_INFO_64,
329               (task_info_t) &ti, &count);
330   if (kr != KERN_SUCCESS)
331     {
332     return 0;
333     }
334 
335   // On Mac OS X, the resident_size is in bytes, not pages!
336   // (This differs from the GNU Mach kernel)
337   return ti.resident_size / 1024;
338 }
339 
340 #endif // Mac OS X
341 
342 #if defined( __SUNPRO_CC ) || defined ( __sun__ )
343 
344 /**         ----         Sun Solaris Memory Usage Observer       ----       */
345 
~SunSolarisMemoryUsageObserver()346 SunSolarisMemoryUsageObserver::~SunSolarisMemoryUsageObserver()
347 {}
348 
349 /** On Sun Solaris machines, the system call pmap returns information on process.
350  *  Calling "pmap PID", the output shall be like the following:
351  *  102905:    *my_app*
352  *  00010000    192K r-x--  /usr/bin/my_app
353  *  00042000     40K rwx--    [ heap ]
354  *  FF180000    664K r-x--  /usr/lib/libc.so.1
355  *  FF236000     24K rwx--  /usr/lib/libc.so.1
356  *  FF23C000      8K rwx--  /usr/lib/libc.so.1
357  *  FF250000      8K rwx--    [ anon ]
358  *  ...       ...    ...    ...
359  *  FF3F6000      8K rwx--  /usr/lib/ld.so.1
360  *  FFBFC000     16K rw---    [ stack ]
361  *   total     1880K
362  */
363 MemoryUsageObserverBase::MemoryLoadType
GetMemoryUsage()364 SunSolarisMemoryUsageObserver::GetMemoryUsage()
365 {
366   MemoryLoadType mem = 0;
367   int            pid = getpid();
368 
369   FILE *            fp = nullptr;
370   std::stringstream command;
371 
372   command << "pmap " << pid << std::endl;
373 
374   if ( ( fp = popen(command.str().c_str(), "r") ) == nullptr )
375     {
376     itkGenericExceptionMacro(<< "Error using pmap. Can execute pmap command");
377     }
378   char remaining[256];
379   int  pmappid = -1;
380   fscanf(fp, "%d:%s", &pmappid, remaining);
381   //the first word shall be the process ID
382   if ( pmappid != pid )
383     {
384     itkGenericExceptionMacro(<< "Error using pmap. 1st line output shall be PID: name");
385     }
386   bool        heapNotFound = true;
387   char        address[64], perms[32];
388   int         memUsage = 0;
389   std::string mapping;
390   while ( heapNotFound )
391     {
392     if ( fscanf(fp, "%s %dK %s", address, &memUsage, perms) != 3 )
393       {
394       break;
395       }
396     if ( fgets(remaining, 256, fp) != nullptr )
397       {
398       mapping = remaining;
399       if ( mapping.find("[ heap ]", 0) != std::string::npos )
400         {
401         mem = memUsage;
402         heapNotFound = false;
403         break;
404         }
405       // if no [ heap ] token is defined, accumulate all the [ xxx ] tokens
406       else if ( mapping.find("[ ", 0) != std::string::npos
407                 && mapping.find(" ]", 0) != std::string::npos )
408         {
409         mem += memUsage;
410         }
411       }
412     else
413       {
414       if ( ferror (fp) )
415         {
416         itkGenericExceptionMacro(<< "Error using pmap. Corrupted pmap output");
417         }
418       }
419     }
420   if ( pclose(fp) == -1 )
421     {
422     itkGenericExceptionMacro(<< "Error using pmap. Can't close pmap output file.");
423     }
424   return mem;
425 }
426 
427 #endif //defined(__SUNPRO_CC) || defined (__sun__)
428 
429 #if !defined( WIN32 ) && !defined( _WIN32 ) || defined( __OpenBSD__ )
430 
431 /**         ----         SysResource Memory Usage Observer       ----       */
432 
433 SysResourceMemoryUsageObserver::~SysResourceMemoryUsageObserver() = default;
434 
435 MemoryUsageObserverBase::MemoryLoadType
GetMemoryUsage()436 SysResourceMemoryUsageObserver::GetMemoryUsage()
437 {
438   // Maybe use getrusage() ??
439   rusage resourceInfo;
440 
441   const int who = RUSAGE_SELF;
442   if ( getrusage(who, &resourceInfo) == 0 )
443     {
444     return static_cast<MemoryUsageObserverBase::MemoryLoadType> (resourceInfo.ru_ixrss);
445     }
446 
447   return 0;
448 }
449 
450 #if defined( ITK_HAS_MALLINFO )
451 
452 /**         ----         Mallinfo Memory Usage Observer       ----       */
453 
~MallinfoMemoryUsageObserver()454 MallinfoMemoryUsageObserver::~MallinfoMemoryUsageObserver()
455 {}
456 
457 MemoryUsageObserverBase::MemoryLoadType
GetMemoryUsage()458 MallinfoMemoryUsageObserver::GetMemoryUsage()
459 {
460   struct mallinfo minfo = mallinfo();
461 
462   MemoryLoadType mem = static_cast< MemoryLoadType >(
463     static_cast< double >( minfo.uordblks ) / 1024.0 );
464 
465   return mem;
466 }
467 
468 #endif //  ITK_HAS_MALLINFO
469 
470 #endif // Unix and Mac Platforms !defined(WIN32) && !defined(_WIN32)
471 
472 
473 //Destructor for MemoryUsageObserver
474 MemoryUsageObserver::~MemoryUsageObserver()= default;
475 } //end namespace itk
476