1 /**
2  * The amount of virtual and physycal memory used by system or process.
3  * Authors:
4  *  $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov).
5  * License:
6  *  $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
7  * Copyright:
8  *  Roman Chistokhodov 2015
9  *
10  * Note: Every function may throw on fail ($(B ErrnoException) on Linux, $(B WindowsException) on Windows).
11  */
12 
13 module resusage.memory;
14 import resusage.common;
15 
16 import std.exception;
17 
18 ///Get system memory information.
19 @trusted SystemMemInfo systemMemInfo()
20 {
21     SystemMemInfo memInfo;
22     memInfo.update();
23     return memInfo;
24 }
25 
26 ///Get memory info for current process.
27 @trusted ProcessMemInfo processMemInfo() {
28     ProcessMemInfo memInfo;
29     memInfo.initialize();
30     memInfo.update();
31     return memInfo;
32 }
33 
34 /**
35  * Get memory info for specific process.
36  * Params:
37  *  pid = Process ID of specific process. On Windows also can be process handle.
38  */
39 @trusted ProcessMemInfo processMemInfo(int pid) {
40     ProcessMemInfo memInfo;
41     memInfo.initialize(pid);
42     memInfo.update();
43     return memInfo;
44 }
45 
46 private @nogc @safe double percent(ulong total, ulong part) pure nothrow {
47     if (total) {
48         return part / cast(double)total * 100.0;
49     }
50     return 0.0;
51 }
52 
53 version(ResUsageDocs)
54 {
55     ///System-wide memory information.
56     struct SystemMemInfo
57     {
58         ///Total physycal memory in the system, in bytes.
59         @nogc @safe ulong totalRAM() const nothrow;
60 
61         ///Amout of physycal memory currently available (free), in bytes.
62         @nogc @safe ulong freeRAM() const nothrow;
63 
64         ///Amout of physycal memory currently available (free), in percents of total physycal memory.
65         @nogc @safe double freeRAMPercent() const nothrow;
66 
67         ///Amout of physycal memory currently in use, in bytes.
68         @nogc @safe ulong usedRAM() const nothrow;
69 
70         ///Amout of physycal memory currently in use, in percents of total physycal memory.
71         @nogc @safe double usedRAMPercent() const nothrow;
72 
73         ///Total virtual memory in the system, in bytes.
74         @nogc @safe ulong totalVirtMem() const nothrow;
75 
76         ///Amout of virtual memory currently available (free), in bytes.
77         @nogc @safe ulong freeVirtMem() const nothrow;
78 
79         ///Amout of virtual memory currently available (free), in percents of total virtual memory.
80         @nogc @safe double freeVirtMemPercent() const nothrow;
81 
82         ///Amout of virtual memory currently in use, in bytes.
83         @nogc @safe ulong usedVirtMem() const nothrow;
84 
85         ///Amout of virtual memory currently in use, in percents of total virtual memory.
86         @nogc @safe double usedVirtMemPercent() const nothrow;
87 
88         ///Actualize values.
89         @trusted void update();
90     }
91 
92     ///Single process memory information.
93     struct ProcessMemInfo
94     {
95         ///Amount of physycal memory (RAM) currently used by single process, in bytes.
96         @nogc @safe ulong usedRAM() const nothrow;
97 
98         ///Amount of virtual memory currently used by single process, in bytes.
99         @nogc @safe ulong usedVirtMem() const nothrow;
100 
101         ///Actualize values.
102         @trusted void update();
103 
104         ///ID of underlying process.
105         @nogc @safe int processID() const nothrow;
106 
107     private:
108         void initialize();
109         void initialize(int pid);
110     }
111 } else version(Windows) {
112 
113     @trusted ProcessMemInfo processMemInfo(HANDLE procHandle) {
114         ProcessMemInfo memInfo;
115         memInfo.initialize(procHandle);
116         memInfo.update();
117         return memInfo;
118     }
119 
120     private {
121         alias ulong DWORDLONG;
122 
123         struct MEMORYSTATUSEX {
124             DWORD     dwLength;
125             DWORD     dwMemoryLoad;
126             DWORDLONG ullTotalPhys;
127             DWORDLONG ullAvailPhys;
128             DWORDLONG ullTotalPageFile;
129             DWORDLONG ullAvailPageFile;
130             DWORDLONG ullTotalVirtual;
131             DWORDLONG ullAvailVirtual;
132             DWORDLONG ullAvailExtendedVirtual;
133         };
134 
135         extern(Windows) BOOL GlobalMemoryStatusEx(MEMORYSTATUSEX* lpBuffer) @system nothrow;
136 
137         struct PROCESS_MEMORY_COUNTERS {
138             DWORD  cb;
139             DWORD  PageFaultCount;
140             SIZE_T PeakWorkingSetSize;
141             SIZE_T WorkingSetSize;
142             SIZE_T QuotaPeakPagedPoolUsage;
143             SIZE_T QuotaPagedPoolUsage;
144             SIZE_T QuotaPeakNonPagedPoolUsage;
145             SIZE_T QuotaNonPagedPoolUsage;
146             SIZE_T PagefileUsage;
147             SIZE_T PeakPagefileUsage;
148         };
149 
150         struct PROCESS_MEMORY_COUNTERS_EX {
151             DWORD  cb;
152             DWORD  PageFaultCount;
153             SIZE_T PeakWorkingSetSize;
154             SIZE_T WorkingSetSize;
155             SIZE_T QuotaPeakPagedPoolUsage;
156             SIZE_T QuotaPagedPoolUsage;
157             SIZE_T QuotaPeakNonPagedPoolUsage;
158             SIZE_T QuotaNonPagedPoolUsage;
159             SIZE_T PagefileUsage;
160             SIZE_T PeakPagefileUsage;
161             SIZE_T PrivateUsage;
162         };
163 
164         extern(Windows) @nogc BOOL dummy(in HANDLE Process, PROCESS_MEMORY_COUNTERS* ppsmemCounters, DWORD cb) @system nothrow { return 0; }
165 
166         alias typeof(&dummy) func_GetProcessMemoryInfo;
167         __gshared func_GetProcessMemoryInfo GetProcessMemoryInfo;
168         __gshared DWORD psApiError;
169     }
170 
171     @nogc @trusted bool isPsApiLoaded() {
172         return GetProcessMemoryInfo !is null;
173     }
174 
175     shared static this()
176     {
177         HMODULE psApiLib = LoadLibraryA("Psapi");
178         if (psApiLib) {
179             GetProcessMemoryInfo = cast(func_GetProcessMemoryInfo)GetProcAddress(psApiLib, "GetProcessMemoryInfo");
180         }
181 
182         if (GetProcessMemoryInfo is null) {
183             psApiError = GetLastError();
184         }
185     }
186 
187     struct SystemMemInfo
188     {
189         @nogc @safe ulong totalRAM() const nothrow {
190             return memInfo.ullTotalPhys;
191         }
192         @nogc @safe ulong freeRAM() const nothrow {
193             return memInfo.ullAvailPhys;
194         }
195         @nogc @safe double freeRAMPercent() const nothrow {
196             return percent(totalRAM, freeRAM);
197         }
198         @nogc @safe ulong usedRAM() const nothrow {
199             return memInfo.ullTotalPhys - memInfo.ullAvailPhys;
200         }
201         @nogc @safe double usedRAMPercent() const nothrow {
202             return percent(totalRAM, usedRAM);
203         }
204 
205         @nogc @safe ulong totalVirtMem() const nothrow {
206             return memInfo.ullTotalPageFile;
207         }
208         @nogc @safe ulong freeVirtMem() const nothrow {
209             return memInfo.ullAvailPageFile;
210         }
211         @nogc @safe double freeVirtMemPercent() const nothrow {
212             return percent(totalVirtMem, freeVirtMem);
213         }
214         @nogc @safe ulong usedVirtMem() const nothrow {
215             return memInfo.ullTotalPageFile - memInfo.ullAvailPageFile;
216         }
217         @nogc @safe double usedVirtMemPercent() const nothrow {
218             return percent(totalVirtMem, usedVirtMem);
219         }
220 
221         @trusted void update() {
222             memInfo.dwLength = MEMORYSTATUSEX.sizeof;
223             wenforce(GlobalMemoryStatusEx(&memInfo), "Could not get memory status");
224         }
225     private:
226         MEMORYSTATUSEX memInfo;
227     }
228 
229     struct ProcessMemInfo
230     {
231         @nogc @safe ulong usedRAM() const nothrow {
232             return pmc.WorkingSetSize;
233         }
234         @nogc @safe ulong usedVirtMem() const nothrow {
235             return pmc.PrivateUsage;
236         }
237 
238         @trusted void update() {
239             if (!isPsApiLoaded()) {
240                 throw new WindowsException(psApiError, "Psapi.dll is not loaded");
241             }
242 
243             HANDLE handle = openProcess(pid);
244             scope(exit) CloseHandle(handle);
245 
246             wenforce(GetProcessMemoryInfo(handle, cast(PROCESS_MEMORY_COUNTERS*)&pmc, pmc.sizeof), "Could not get process memory info");
247         }
248 
249         @nogc @safe int processID() const nothrow {
250             return pid;
251         }
252 
253     private:
254         void initialize() {
255             pid = thisProcessID;
256         }
257 
258         void initialize(int procId) {
259             pid = procId;
260         }
261 
262         void initialize(HANDLE procHandle) {
263             pid = GetProcessId(procHandle);
264         }
265 
266         int pid;
267         PROCESS_MEMORY_COUNTERS_EX pmc;
268     }
269 
270 } else version(linux) {
271     private {
272         static if (is(typeof({ import core.sys.linux.sys.sysinfo; }))) {
273             import core.sys.linux.sys.sysinfo;
274         } else {
275             pragma(msg, "core.sys.linux.sys.sysinfo not found, fallback will be used.");
276             extern(C) @nogc nothrow:
277             struct sysinfo_
278             {
279                 c_long uptime;     /* Seconds since boot */
280                 c_ulong[3] loads;  /* 1, 5, and 15 minute load averages */
281                 c_ulong totalram;  /* Total usable main memory size */
282                 c_ulong freeram;   /* Available memory size */
283                 c_ulong sharedram; /* Amount of shared memory */
284                 c_ulong bufferram; /* Memory used by buffers */
285                 c_ulong totalswap; /* Total swap space size */
286                 c_ulong freeswap;  /* swap space still available */
287                 ushort procs;      /* Number of current processes */
288                 ushort pad;        /* Explicit padding for m68k */
289                 c_ulong totalhigh; /* Total high memory size */
290                 c_ulong freehigh;  /* Available high memory size */
291                 uint mem_unit;     /* Memory unit size in bytes */
292                 ubyte[20-2 * c_ulong.sizeof - uint.sizeof] _f; /* Padding: libc5 uses this.. */
293             }
294             int sysinfo(sysinfo_ *info);
295         }
296     }
297 
298     private @trusted void memoryUsedHelper(const(char)* proc, ref c_ulong vsize, ref c_long rss)
299     {
300         FILE* f = errnoEnforce(fopen(proc, "r"));
301         scope(exit) fclose(f);
302         errnoEnforce(fscanf(f,
303                      "%*d " ~//pid
304                      "%*s " ~//comm
305                      "%*c " ~//state
306                      "%*d " ~//ppid
307                      "%*d " ~//pgrp
308                      "%*d " ~//session
309                      "%*d " ~//tty_nr
310                      "%*d " ~//tpgid
311                      "%*u " ~//flags
312                      "%*lu " ~//minflt
313                      "%*lu " ~//cminflt
314                      "%*lu " ~//majflt
315                      "%*lu " ~//cmajflt
316                      "%*lu " ~//utime
317                      "%*lu " ~//stime
318                      "%*ld " ~//cutime
319                      "%*ld " ~//cstime
320                      "%*ld " ~//priority
321                      "%*ld " ~//nice
322                      "%*ld " ~//num_threads
323                      "%*ld " ~//itrealvalue
324                      "%*llu " ~//starttime
325                      "%lu " ~//vsize
326                      "%ld ", //rss
327                &vsize, &rss
328               ) == 2);
329     }
330 
331     struct SystemMemInfo
332     {
333         @nogc @safe ulong totalRAM() const nothrow {
334             ulong total = memInfo.totalram;
335             total *= memInfo.mem_unit;
336             return total;
337         }
338         @nogc @safe ulong freeRAM() const nothrow {
339             ulong free = memInfo.freeram;
340             free *= memInfo.mem_unit;
341             return free;
342         }
343         @nogc @safe double freeRAMPercent() const nothrow {
344             return percent(totalRAM, freeRAM);
345         }
346         @nogc @safe ulong usedRAM() const nothrow {
347             return totalRAM() - freeRAM();
348         }
349         @nogc @safe double usedRAMPercent() const nothrow {
350             return percent(totalRAM, usedRAM);
351         }
352 
353         @nogc @safe ulong totalVirtMem() const nothrow {
354             ulong total = memInfo.totalram + memInfo.totalswap;
355             total *= memInfo.mem_unit;
356             return total;
357         }
358         @nogc @safe ulong freeVirtMem() const nothrow {
359             ulong free = memInfo.freeram + memInfo.freeswap;
360             free *= memInfo.mem_unit;
361             return free;
362         }
363         @nogc @safe double freeVirtMemPercent() const nothrow {
364             return percent(totalVirtMem, freeVirtMem);
365         }
366         @nogc @safe ulong usedVirtMem() const nothrow {
367             return totalVirtMem() - freeVirtMem();
368         }
369         @nogc @safe double usedVirtMemPercent() const nothrow {
370             return percent(totalVirtMem, usedVirtMem);
371         }
372 
373         @trusted void update() {
374             errnoEnforce(sysinfo(&memInfo) == 0);
375         }
376     private:
377         sysinfo_ memInfo;
378     }
379 
380     struct ProcessMemInfo
381     {
382         @nogc @trusted ulong usedRAM() const nothrow {
383             return rss * PAGE_SIZE;
384         }
385         @nogc @safe ulong usedVirtMem() const nothrow {
386             return vsize;
387         }
388 
389         @trusted void update() {
390             memoryUsedHelper(proc, vsize, rss);
391         }
392 
393         @nogc @safe int processID() const nothrow {
394             return pid;
395         }
396 
397     private:
398         void initialize() {
399             pid = thisProcessID;
400             proc = procSelf;
401         }
402 
403         void initialize(int procId) {
404             pid = procId;
405             proc = procOfPid(pid);
406         }
407 
408         int pid;
409         const(char)* proc;
410         c_ulong vsize;
411         c_long rss;
412     }
413 
414     __gshared size_t PAGE_SIZE;
415 
416     shared static this()
417     {
418         PAGE_SIZE = sysconf(_SC_PAGESIZE);
419     }
420 
421 } else version(FreeBSD) {
422 
423     private {
424         import core.sys.posix.fcntl;
425 
426         struct kvm_t;
427 
428         struct kvm_swap {
429             char[32] ksw_devname;
430             int ksw_used;
431             int ksw_total;
432             int ksw_flags;
433             int ksw_reserved1;
434             int ksw_reserved2;
435         };
436 
437         extern(C) @nogc @system nothrow {
438             int sysctl(const(int)* name, uint namelen, void *oldp, size_t *oldlenp, const(void)* newp, size_t newlen);
439             int sysctlbyname(const(char)* name, void *oldp, size_t *oldlenp, const(void)* newp, size_t newlen);
440 
441             kvm_t *kvm_open(const(char)*, const(char)*, const(char)*, int, const(char)*);
442             int kvm_close(kvm_t *);
443             int kvm_getswapinfo(kvm_t*, kvm_swap*, int, int);
444 
445             int getpagesize();
446         }
447 
448     }
449 
450     struct SystemMemInfo
451     {
452         @nogc @safe ulong totalRAM() const nothrow {
453             return _totalRam;
454         }
455         @nogc @safe ulong freeRAM() const nothrow {
456             return _freeRam;
457         }
458         @nogc @safe double freeRAMPercent() const nothrow {
459             return percent(totalRAM, freeRAM);
460         }
461         @nogc @safe ulong usedRAM() const nothrow {
462             return totalRAM() - freeRAM();
463         }
464         @nogc @safe double usedRAMPercent() const nothrow {
465             return percent(totalRAM, usedRAM);
466         }
467 
468         @nogc @safe ulong totalVirtMem() const nothrow {
469             return _totalVirtMem;
470         }
471         @nogc @safe ulong freeVirtMem() const nothrow {
472             return _freeVirtMem;
473         }
474         @nogc @safe double freeVirtMemPercent() const nothrow {
475             return percent(totalVirtMem, freeVirtMem);
476         }
477         @nogc @safe ulong usedVirtMem() const nothrow {
478             return totalVirtMem() - freeVirtMem();
479         }
480         @nogc @safe double usedVirtMemPercent() const nothrow {
481             return percent(totalVirtMem, usedVirtMem);
482         }
483 
484         @trusted void update() {
485             kvm_t* kvmh = errnoEnforce(kvm_open(null, "/dev/null", "/dev/null", O_RDONLY, "kvm_open"));
486             scope(exit) kvm_close(kvmh);
487             kvm_swap k_swap;
488 
489             errnoEnforce(kvm_getswapinfo(kvmh, &k_swap, 1, 0) != -1);
490 
491             ulong pageSize = cast(ulong)getpagesize();
492 
493             static @trusted int ctlValueByName(const(char)* name) {
494                 int value;
495                 size_t len = int.sizeof;
496                 errnoEnforce(sysctlbyname(name, &value, &len, null, 0) == 0);
497                 return value;
498             }
499 
500             int totalPages = ctlValueByName("vm.stats.vm.v_page_count");
501             int freePages = ctlValueByName("vm.stats.vm.v_free_count");
502 
503             _totalRam = cast(ulong)totalPages * pageSize;
504             _freeRam = cast(ulong)freePages * pageSize;
505 
506             _totalVirtMem = cast(ulong)k_swap.ksw_total * pageSize + _totalRam;
507             _freeVirtMem = cast(ulong)(k_swap.ksw_total - k_swap.ksw_used) * pageSize + _freeRam;
508 
509         }
510     private:
511         ulong _totalRam;
512         ulong _freeRam;
513 
514         ulong _totalVirtMem;
515         ulong _freeVirtMem;
516     }
517 
518     struct ProcessMemInfo
519     {
520         @nogc @safe ulong usedRAM() const nothrow {
521             return 0;
522         }
523         @nogc @safe ulong usedVirtMem() const nothrow {
524             return 0;
525         }
526 
527         @trusted void update() {
528 
529         }
530 
531         @nogc @safe int processID() const nothrow {
532             return pid;
533         }
534 
535     private:
536         void initialize() {
537             pid = thisProcessID;
538         }
539 
540         void initialize(int procId) {
541             pid = procId;
542         }
543 
544         int pid;
545     }
546 } else version(OSX) {
547     struct SystemMemInfo
548     {
549         @nogc @safe ulong totalRAM() const nothrow {
550             return 0;
551         }
552         @nogc @safe ulong freeRAM() const nothrow {
553             return 0;
554         }
555         @nogc @safe double freeRAMPercent() const nothrow {
556             return 0;
557         }
558         @nogc @safe ulong usedRAM() const nothrow {
559             return 0;
560         }
561         @nogc @safe double usedRAMPercent() const nothrow {
562             return 0;
563         }
564 
565         @nogc @safe ulong totalVirtMem() const nothrow {
566             return 0;
567         }
568         @nogc @safe ulong freeVirtMem() const nothrow {
569             return 0;
570         }
571         @nogc @safe double freeVirtMemPercent() const nothrow {
572             return percent(totalVirtMem, freeVirtMem);
573         }
574         @nogc @safe ulong usedVirtMem() const nothrow {
575             return totalVirtMem() - freeVirtMem();
576         }
577         @nogc @safe double usedVirtMemPercent() const nothrow {
578             return percent(totalVirtMem, usedVirtMem);
579         }
580         @trusted void update() {
581         }
582     }
583 
584     struct ProcessMemInfo
585     {
586         @nogc @safe ulong usedRAM() const nothrow {
587             return 0;
588         }
589         @nogc @safe ulong usedVirtMem() const nothrow {
590             return 0;
591         }
592 
593         @trusted void update() {
594         }
595 
596         @nogc @safe int processID() const nothrow {
597             return pid;
598         }
599 
600     private:
601         void initialize() {
602         }
603 
604         void initialize(int procId) {
605         }
606 
607         int pid;
608         const(char)* proc;
609     }
610 }