1 /*******************************************************************************
2 
3     Contains API to obtain various information about the running application
4     from /proc VFS.
5 
6     Copyright:
7         Copyright (c) 2009-2017 dunnhumby Germany GmbH.
8         All rights reserved.
9 
10     License:
11         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
12         Alternatively, this file may be distributed under the terms of the Tango
13         3-Clause BSD License (see LICENSE_BSD.txt for details).
14 
15 *******************************************************************************/
16 
17 module ocean.sys.stats.linux.ProcVFS;
18 
19 import ocean.meta.types.Qualifiers;
20 import Path = ocean.io.Path;
21 import ocean.sys.ErrnoException;
22 import core.sys.posix.stdio;
23 import core.sys.posix.dirent;
24 import core.stdc.errno;
25 import ocean.core.Tuple;
26 import ocean.io.device.File;
27 import ocean.text.Search;
28 import ocean.text.convert.Integer;
29 import ocean.core.Buffer;
30 import ocean.core.Enforce;
31 import core.stdc.stdlib;
32 import ocean.meta.traits.Basic;
33 import ocean.meta.codegen.Identifier;
34 import ocean.text.util.StringC;
35 
36 version (unittest)
37 {
38     import ocean.core.Test;
39 }
40 
41 private
42 {
43     // TODO: use druntime bindings when available
44     extern (C) ssize_t getline (char** lineptr, size_t* n, FILE* stream);
45 }
46 
47 /*******************************************************************************
48 
49     Reusable procvfs_exception instance.
50 
51 *******************************************************************************/
52 
53 private ErrnoException procvfs_exception;
54 
55 /*******************************************************************************
56 
57     Buffer for processing files.
58 
59 *******************************************************************************/
60 
61 private Buffer!(char) procvfs_file_buf;
62 
63 /***************************************************************************
64 
65     Gets the number of the fd open in a process.
66 
67     Note:
68         In order to get the number of open files, this method iterates
69         through the directory entires in /proc VFS. This implies that it should
70         not be called multiple times a second, as there might be performance
71         implications. Ideally, it's called every 30 seconds, or so, just
72         to generate the stats.log as needed.
73 
74     Returns:
75         number of the open file descriptors
76 
77 ***************************************************************************/
78 
79 public int getOpenFdCount ()
80 {
81     int count;
82 
83     auto dir = opendir("/proc/self/fdinfo".ptr);
84 
85     if (dir is null)
86         return 0;
87 
88     scope (exit)
89     {
90         // ignore EBADF
91         closedir(dir);
92     }
93 
94     /*
95         This uses `readdir` and not `readdir_r` since the latter
96         is de-facto deprecated.
97 
98         From http://man7.org/linux/man-pages/man3/readdir_r.3.html
99 
100         In the current POSIX.1 specification (POSIX.1-2008), readdir(3) is
101         not required to be thread-safe. However, in modern
102         implementations (including the glibc implementation), concurrent
103         calls to readdir(3) that specify different directory streams are
104         thread-safe. Therefore, the use of readdir_r() is generally
105         unnecessary in multithreaded programs. In cases where multiple
106         threads must read from the same directory stream, using readdir(3)
107         with external synchronization is still preferable to the use of
108         readdir_r(), for the reasons given in the points above.
109     */
110     dirent* dp;
111     while ((dp = readdir(dir)) !is null)
112     {
113         auto entry_name = StringC.toDString(dp.d_name.ptr);
114         if (entry_name != "." && entry_name != "..")
115         {
116             count++;
117         }
118     }
119 
120     return count;
121 }
122 
123 /*******************************************************************************
124 
125     Information reported by /proc/pid/stat file
126 
127     Information about individual fields can be found in `man 5 proc`:
128     http://man7.org/linux/man-pages/man5/proc.5.html
129 
130 *******************************************************************************/
131 
132 struct ProcStat
133 {
134     int pid;
135     mstring cmd;
136     char state;
137     int ppid;
138     int pgrp;
139     int session;
140     int tty_nr;
141     int tpgid;
142     uint flags;
143     ulong minflt;
144     ulong cminflt;
145     ulong majflt;
146     ulong cmajflt;
147     ulong utime;
148     ulong stime;
149     long cutime;
150     long cstime;
151     long priority;
152     long nice;
153     long num_threads;
154     long itrealvalue;
155     ulong starttime;
156     ulong vsize;
157     ulong rss;
158     ulong rsslim;
159     ulong startcode;
160     ulong endcode;
161     ulong startstack;
162     ulong kstkesp;
163     ulong kstkeip;
164     ulong signal;
165     ulong blocked;
166     ulong sigingore;
167     ulong sigcatch;
168     ulong wchan;
169     ulong nswap;
170     ulong cnswap;
171     int exit_signal;
172     int processor;
173     uint rt_priority;
174     uint policy;
175     ulong delayacct_blkio_ticks;
176     ulong guest_time;
177     long cguest_time;
178     ulong start_data;
179     ulong end_data;
180     ulong start_brk;
181     ulong arg_start;
182     ulong arg_end;
183     ulong env_start;
184     ulong env_end;
185     int exit_code;
186 }
187 
188 
189 /*******************************************************************************
190 
191     Reads /proc/<pid>/stat and extracts the data.
192 
193     Params:
194         path = path of the file to query
195 
196     Returns:
197         filled ProcStat structure based on /proc/self/stat or empty
198         ProcStat instance in case parsing has failed.
199 
200 *******************************************************************************/
201 
202 public ProcStat getProcStat (cstring path)
203 {
204     ProcStat s;
205     return getProcStat(path, s) ? s : ProcStat.init;
206 }
207 
208 /*******************************************************************************
209 
210     Reads /proc/<pid>/stat and extracts the data.
211 
212     Params:
213         path = path of the file to query
214         stats = struct instance where to extract data
215 
216     Returns:
217         'false' if parsing has failed, 'true' otherwise
218 
219 *******************************************************************************/
220 
221 public bool getProcStat (cstring path, ref ProcStat stat)
222 {
223     // Get the data from file
224     scope file = new File(path);
225 
226     // stat(2) generally on the special files returns 0,
227     // so we can't figure out in advance how much to allocate
228     procvfs_file_buf.length = 8192; // Should be enough to cover maximum path of the
229                             // executable stored in the file
230     auto num_read = file.read(procvfs_file_buf[]);
231     enforce(*procvfs_file_buf[num_read-1] == '\n');
232 
233     char[] data = procvfs_file_buf[0..num_read];
234 
235     return parseProcStatData(data, stat);
236 }
237 
238 /*******************************************************************************
239 
240     Parses /proc/self/stat and extracts the data.
241 
242     Returns:
243         filled ProcStat structure based on /proc/self/stat or empty
244         ProcStat instance in case parsing has failed.
245 
246 *******************************************************************************/
247 
248 public ProcStat getProcSelfStat ()
249 {
250     ProcStat s;
251     return getProcSelfStat(s) ? s : ProcStat.init;
252 }
253 
254 /*******************************************************************************
255 
256     Parses /proc/self/stat and extracts the data.
257 
258     Params:
259         stat = reusable struct instance to extract data to
260 
261     Returns:
262         'false' if parsing has failed, 'true' otherwise
263 
264 *******************************************************************************/
265 
266 public bool getProcSelfStat (ref ProcStat stat)
267 {
268     return getProcStat("/proc/self/stat", stat);
269 }
270 
271 /*******************************************************************************
272 
273     Structure representing data found in /proc/uptime.
274 
275     Contains the uptime of the system and the amount of time spent in idle
276     process, both in seconds.
277 
278 *******************************************************************************/
279 
280 public struct ProcUptime
281 {
282     /***************************************************************************
283 
284         Helper struct containing whole and fractional part of the second.
285 
286     ***************************************************************************/
287 
288     public struct Time
289     {
290         import ocean.math.Math: abs;
291 
292         /// Whole part of seconds
293         public long seconds;
294         /// Fractional part of the second
295         public long cents;
296 
297         /***********************************************************************
298 
299             Returns:
300                 Floating point representation of the Time struct
301 
302         ***********************************************************************/
303 
304         public double as_double ()
305         {
306             return seconds + cents / 100.0;
307         }
308 
309 
310         /***********************************************************************
311 
312             Params:
313                 rhs = Time value to subtract
314 
315             Returns:
316                 current time subtracted by rhs
317 
318         ***********************************************************************/
319 
320         Time opBinary ( string op : "-" ) (Time rhs)
321         {
322             Time res_time;
323 
324             auto t = (this.seconds * 100 + this.cents) -
325                 (rhs.seconds * 100 + rhs.cents);
326 
327             res_time.seconds = t / 100;
328             res_time.cents = abs(t) % 100;
329 
330             return res_time;
331         }
332 
333         unittest
334         {
335             auto t1 = Time(2, 0);
336             auto t2 = Time(1, 0);
337             test!("==")(t1 - t2, Time(1, 0));
338 
339             t1 = Time(2, 0);
340             t2 = Time(2, 0);
341             test!("==")(t1 - t2, Time(0, 0));
342 
343             t1 = Time(1, 0);
344             t2 = Time(2, 0);
345             test!("==")(t1 - t2, Time(-1, 0));
346 
347             t1 = Time(2, 50);
348             t2 = Time(2, 30);
349             test!("==")(t1 - t2, Time(0, 20));
350 
351             t1 = Time(2, 30);
352             t2 = Time(1, 50);
353             test!("==")(t1 - t2, Time(0, 80));
354 
355             t1 = Time(2, 50);
356             t2 = Time(1, 50);
357             test!("==")(t1 - t2, Time(1, 0));
358 
359             t1 = Time(1, 30);
360             t2 = Time(2, 50);
361             test!("==")(t1 - t2, Time(-1, 20));
362 
363         }
364     }
365 
366     /// Uptime time
367     public Time uptime;
368 
369     /// Idle time
370     public Time idle;
371 }
372 
373 unittest
374 {
375     auto t = ProcUptime.Time(1, 50);
376     test!("==")(t.as_double(), 1.5);
377     t.cents = 25;
378     test!("==")(t.as_double(), 1.25);
379     t.cents = 0;
380     test!("==")(t.as_double, 1.0);
381 }
382 
383 /*******************************************************************************
384 
385     Parses and returns data found in /proc/uptime.
386 
387     Returns:
388         ProcUptime instance representing /proc/uptime
389 
390     Throws:
391         ErrnoException on failure to parse this file.
392 
393 *******************************************************************************/
394 
395 public ProcUptime getProcUptime ()
396 {
397     auto f = fopen("/proc/uptime".ptr, "r".ptr);
398 
399     if (!f)
400     {
401         throwException("fopen");
402     }
403 
404     scope (exit) fclose(f);
405 
406     ProcUptime t;
407     if (fscanf(f, "%lld.%lld %lld.%lld\n".ptr,
408                 &t.uptime.seconds,
409                 &t.uptime.cents,
410                 &t.idle.seconds,
411                 &t.idle.cents) != 4)
412     {
413         throwException("fscanf");
414     }
415 
416     return t;
417 }
418 
419 /*******************************************************************************
420 
421     Information reported by /proc/meminfo file
422 
423     Information about individual fields can be found in `man 5 proc`:
424     http://man7.org/linux/man-pages/man5/proc.5.html
425 
426 *******************************************************************************/
427 
428 struct ProcMemInfo
429 {
430     ulong MemTotal;
431     ulong MemFree;
432     ulong MemAvailable;
433     ulong Buffers;
434     ulong Cached;
435     ulong SwapCached;
436     ulong Active;
437     ulong Inactive;
438     ulong Unevictable;
439     ulong Mlocked;
440     ulong HighTotal;
441     ulong HighFree;
442     ulong LowTotal;
443     ulong LowFree;
444     ulong MmapCopy;
445     ulong SwapTotal;
446     ulong SwapFree;
447     ulong Dirty;
448     ulong Writeback;
449     ulong AnonPages;
450     ulong Mapped;
451     ulong Shmem;
452     ulong Slab;
453     ulong SReclaimable;
454     ulong SUnreclaim;
455     ulong KernelStack;
456     ulong PageTables;
457     ulong QuickList;
458     ulong NFS_Unstable;
459     ulong Bounce;
460     ulong WritebackTmp;
461     ulong CommitLimit;
462     ulong Committed_As;
463     ulong VmallocTotal;
464     ulong VmallocUsed;
465     ulong VmallocChunk;
466 }
467 
468 /*******************************************************************************
469 
470     Returns:
471         Filled in ProcMemInfo structure
472 
473     Throws:
474         ErrnoException on failure to parse this file.
475 
476 *******************************************************************************/
477 
478 public ProcMemInfo getProcMemInfo ()
479 {
480     /***************************************************************************
481 
482         Buffer used for getline reading. Since getline may reallocate buffer,
483         this must be heap allocate and managed with malloc/free.
484 
485     ***************************************************************************/
486 
487     static struct getline_buffer_t
488     {
489         /// Pointer to the buffer
490         char* ptr;
491         /// size of the buffer
492         size_t length;
493     }
494 
495     // Instance of the getline_buffer_t.
496     static getline_buffer_t getline_buf;
497 
498     auto f = fopen("/proc/meminfo".ptr, "r".ptr);
499 
500     if (!f)
501     {
502         throwException("fopen");
503     }
504 
505     scope (exit) fclose(f);
506 
507     cstring get_next_line ()
508     {
509         ssize_t read = getline(&getline_buf.ptr, &getline_buf.length, f);
510 
511         if (read <= 0)
512         {
513             return null;
514         }
515 
516         // getline gets the last \n
517         auto data = getline_buf.ptr[0 .. read - 1];
518 
519         return data;
520     }
521 
522     return parseProcMemInfoData(&get_next_line);
523 }
524 
525 /*******************************************************************************
526 
527     Returns:
528         size of machine's RAM in bytes, as reported in /proc/meminfo
529 
530     Throws:
531         ErrnoException on failure to parse this file.
532 
533 *******************************************************************************/
534 
535 public ulong getTotalMemoryInBytes ()
536 {
537     return getProcMemInfo().MemTotal;
538 }
539 
540 /*******************************************************************************
541 
542     Initializes .exception object if necessary and throws it, carrying the
543     last errno.
544 
545     Params:
546         name = name of the method that failed
547 
548 *******************************************************************************/
549 
550 private void throwException( istring name )
551 {
552     auto saved_errno = .errno;
553 
554     if (.procvfs_exception is null)
555     {
556         .procvfs_exception = new ErrnoException();
557     }
558 
559     throw .procvfs_exception.set(saved_errno, name);
560 }
561 
562 /*******************************************************************************
563 
564     Provides parsing for /proc/meminfo data.
565 
566     Params:
567         read_next_line = delegate providing next line, or `null` when
568         no more data is to be parsed
569 
570     Returns:
571         Filled in ProcMemInfo structure
572 
573 *******************************************************************************/
574 
575 private ProcMemInfo parseProcMemInfoData (scope cstring delegate() read_next_line)
576 {
577     /// Helper function to strip leading spaces
578     /// Params:
579     ///     data: string to strip spaces from
580     /// Returns:
581     ///     passed string with no leading spaces
582     cstring strip_spaces (cstring data)
583     {
584         while (data.length && data[0] == ' ')
585         {
586             data = data[1..$];
587         }
588 
589         return data;
590     }
591 
592     ProcMemInfo meminfo;
593 
594     cstring colon = ":";
595     cstring space = " ";
596     auto colon_it = find(colon);
597     auto space_it = find(space);
598 
599     while (true)
600     {
601         auto data = read_next_line();
602 
603         if (data.length == 0)
604         {
605             break;
606         }
607 
608         // MemTotal:          32 kB
609         auto colon_pos = colon_it.forward(data);
610         auto field_name = data[0..colon_pos];
611 
612         // Swallow all spaces between colon and number
613         data = data[colon_pos+1..$];
614         enforce(data.length);
615         data = strip_spaces(data);
616 
617         // Find separator between value and units
618         auto space_pos = space_it.forward(data);
619         auto field_value = data[0..space_pos];
620         auto field_units = space_pos < data.length ?
621             strip_spaces(data[space_pos + 1 .. $]) : "";
622 
623         /***********************************************************************
624 
625             Multiplies value by the power of two derived from units.
626 
627             Params:
628                 value = value to adjust
629                 units = units to take in acount
630 
631         ***********************************************************************/
632 
633         static void adjustUnits (ref ulong value, cstring units)
634         {
635             if (units.length == 0)
636                 return;
637 
638             switch (units[0])
639             {
640                 case 'k':
641                     value *= 1024;
642                     break;
643                 case 'M':
644                     value *= 1024 * 1024;
645                     break;
646                 case 'G':
647                     value *= 1024 * 1024 * 1024;
648                     break;
649                 case 'T':
650                     value *= 1024 * 1024 * 1024 * 1024;
651                     break;
652                 default:
653                     break;
654             }
655         }
656 
657         switch (field_name)
658         {
659             foreach (i, FieldT; typeof(meminfo.tupleof))
660             {
661                 case identifier!(typeof(meminfo.init).tupleof[i]):
662                     // Using tupleof[i] instead of field
663                     // as a workaround for
664                     // https://issues.dlang.org/show_bug.cgi?id=16521
665                     toInteger(field_value, meminfo.tupleof[i]);
666                     adjustUnits(meminfo.tupleof[i], field_units);
667                     break;
668             }
669 
670             default:
671                 break;
672         }
673     }
674 
675     return meminfo;
676 }
677 
678 version (unittest)
679 {
680     import ocean.io.Stdout;
681 }
682 
683 unittest
684 {
685     // test empty string
686     cstring empty () { return null; }
687     test!("==")(parseProcMemInfoData(&empty), ProcMemInfo.init);
688 
689     // test input in expected format, all lines supported
690     cstring[] data =
691         [
692         "MemTotal:        8131024 kB",
693         "MemFree:           35664 MB",
694         "MemAvailable:          4 GB",
695         "Buffers:               1   "
696         ];
697 
698     int line_read;
699     cstring read_data(cstring[] data) {
700         if (line_read >= data.length)
701             return null;
702         return data[line_read++];
703     }
704 
705     auto res = parseProcMemInfoData({ return read_data(data); });
706 
707     ProcMemInfo expected;
708     expected.MemTotal = 8131024UL * 1024;
709     expected.MemFree  = 35664UL * 1024 * 1024;
710     expected.MemAvailable = 4UL * 1024 * 1024 * 1024;
711     expected.Buffers = 1UL;
712 
713     test!("==")(res, expected);
714 
715     // test input in expected format, but add some
716     // lines that don't correspond ProcMemInfo fields (kernel update)
717     cstring[] new_data =
718         [
719         "SomeNewStat:       11111 kB",
720         "MemTotal:        8131024 kB",
721         "MemFree:           35664 MB",
722         "MemAvailable:          4 GB",
723         "ExtraStats:        11111 kB",
724         "Buffers:               1   "
725         ];
726 
727     line_read = 0;
728     res = parseProcMemInfoData({ return read_data(new_data); });
729     test!("==")(res, expected);
730 
731     // Let's see if we can handle misalignment
732     // Separator between name and value is ':',
733     // and between value and units is ' '. Any number of spaces
734     // should be irrelevant
735     data =
736         [
737         "MemTotal:8131024 kB",
738         "MemFree:35664  MB",
739         "MemAvailable:          4 GB",
740         "Buffers:               1   "
741         ];
742 
743     line_read = 0;
744     res = parseProcMemInfoData({ return read_data(data); });
745     test!("==")(res, expected);
746 }
747 
748 /*******************************************************************************
749 
750     Parses /proc/<pid>/stat data and extracts the data.
751 
752     Params:
753         path = path of the file to query
754         s = reused ProcStat struct instance, all its array/string field will
755             be reset to 0 length and overwritten
756 
757     Returns:
758         'false' if parsing has failed, 'true' otherwise
759 
760 *******************************************************************************/
761 
762 private bool parseProcStatData (cstring data, ref ProcStat s)
763 {
764     cstring space = " ";
765     cstring parenth = ")";
766 
767     auto space_it = find(space);
768     auto parenth_it = find(parenth);
769 
770     // The /proc/self/stat file is based on the following format
771     // (consult the man proc(5) page for details):
772     // pid (<cmd>) C # # # # .... #
773     // Where pid is a number, cmd is a file name, with arbitrary amount of
774     // spaces, but always with parentheses,
775     // C being the character describing the status of the program
776     // and # being again numbers, for every of the entries in the ProcStat
777 
778     // consume pid
779     auto pid_pos = space_it.forward(data);
780     if (pid_pos == data.length)
781         return false;
782 
783     toInteger(data[0..pid_pos], s.pid);
784 
785     // chop pid
786     data = data[pid_pos+1..$];
787 
788     // chop the left bracket from cmd name
789     data = data[1 .. $];
790 
791     // Find the last closing bracket (as the process name can contain bracket
792     // itself.
793     auto last_bracket = parenth_it.reverse(data);
794     // There should be space for the space and process status ("(cat) R ")
795     if (last_bracket >= data.length - 3)
796         return false;
797 
798     s.cmd.length = last_bracket;
799     assumeSafeAppend(s.cmd);
800     s.cmd[] = data[0..last_bracket];
801 
802     // chop last bracket and space
803     data = data[last_bracket+2..$];
804 
805     s.state = data[0];
806 
807     // Cut the process status and the trailing space
808     data = data[2..$];
809 
810     // Now we have a list of numbers, parse one after another
811     foreach (i, ref field; s.tupleof)
812     {
813         static if (i > 2)
814         {
815             static assert (isIntegerType!(typeof(field)));
816             auto next_space = space_it.forward(data);
817             toInteger(data[0..next_space], field);
818 
819             // Last field doesn't have space after it
820             if (next_space < data.length)
821             {
822                 data = data[next_space+1..$];
823             }
824             else
825             {
826                 break;
827             }
828         }
829     }
830 
831     return true;
832 }
833 
834 /*******************************************************************************
835 
836     Convenience wrapper over another `parseProcStatData` overload for cases when
837     repeated allocation is not a problem.
838 
839 *******************************************************************************/
840 
841 private ProcStat parseProcStatData ( cstring data )
842 {
843     ProcStat s;
844     return parseProcStatData(data, s) ? s : ProcStat.init;
845 }
846 
847 unittest
848 {
849     // Test with invalid inputs, all should return empty struct
850     auto data = "13300 (cat R 32559 13300 32559 34817 13300 4194304 116 0 0 0 "[];
851     test!("==")(parseProcStatData(data), ProcStat.init);
852 
853     data = "";
854     test!("==")(parseProcStatData(data), ProcStat.init);
855 
856     data = "13300 (cat)";
857     test!("==")(parseProcStatData(data), ProcStat.init);
858 
859     // Test with no fields except process state
860     data = "13300 (cat) R";
861     test!("==")(parseProcStatData(data), ProcStat.init);
862 
863     // Test with just three first fields filled
864     data = "13300 (cat) R 32559 13300 32559";
865     auto res = parseProcStatData(data);
866 
867     // Compare the strings separatelly
868     test!("==")(res.cmd, "cat");
869 
870     // workaround for easy comparing structs without going into field by field
871     // comparison
872     char[] empty_string = "".dup;
873     res.cmd = empty_string;
874     test!("==")(res, ProcStat(13300, empty_string, 'R', 32559, 13300, 32559));
875 
876     // Test will full data
877     data = "13300 (cat) R 32559 13300 32559 34817 13300 4194304 116 0 0 0 "
878         ~ "0 0 0 0 20 0 1 0 28524130 9216000 173 18446744073709551615 4194304 "
879         ~ "4240332 140734453962304 140734453961656 139949142898304 0 0 0 0 0 "
880         ~ "0 0 17 1 0 0 0 0 0 6340112 6341364 21700608 140734453969735 "
881         ~ "140734453969755 140734453969755 140734453972975 0";
882 
883     res = parseProcStatData(data);
884     test!("==")(res.cmd, "cat");
885     res.cmd = empty_string;
886 
887     test!("==")(res, ProcStat(13300, empty_string, 'R', 32559, 13300, 32559, 34817, 13300,
888                 4194304, 116, 0, 0, 0, 0, 0, 0, 0, 20, 0, 1, 0, 28524130,
889                 9216000, 173, 18446744073709551615UL, 4194304, 4240332,
890                 140734453962304, 140734453961656, 139949142898304, 0, 0, 0, 0,
891                 0, 0, 0, 17, 1, 0, 0, 0, 0, 0, 6340112, 6341364, 21700608,
892                 140734453969735, 140734453969755, 140734453969755,
893                 140734453972975, 0));
894 }