1 /*******************************************************************************
2 
3     Module to display application information in the terminal. Does not keep
4     track of any values, only puts the information to the terminal in two
5     separate portions - a static portion in the bottom and a streaming portion
6     on top.
7 
8     Generally, the static portion contains only a few lines used to display the
9     progress/health of the application while the larger streaming portion is
10     used to output logs or other application output. However, this is not a rule
11     and applications are free to display whatever is needed in the static and
12     streaming portions.
13 
14     Since almost all applications that use this module also use Tango's logging
15     facility, a separate appender (ocean.util.log.InsertConsole) has been
16     developed to allow for application logs to be correctly displayed in the
17     streaming portion. The InsertConsole appender moves the cursor just above
18     the static lines, creates space by scrolling-up previously displayed content
19     in the streaming portion, and then "inserts" the given log message in the
20     newly created space. The existing static lines are not touched during this
21     process.
22 
23     The AppStatus + InsertConsole combination provides a convenient way to track
24     the status of a long running command-line application in a friendly manner.
25     However, there are few things that should be noted when using this module:
26 
27         1. Once content in the streaming portion scrolls past the top of the
28            terminal, it cannot be retrieved by scrolling up using a mouse or the
29            scrollbar.
30         2. When redirecting to a file from the command-line using ">", only the
31            contents of the streaming portion will be sent to the file, and not
32            the contents of the static portion.
33         3. Content sent to the top streaming portion should not have tab
34            characters or embedded newline characters. These would cause the
35            streaming portion to spill over into the static portion, thus messing
36            up the display.
37         4. Regular Stdout/Stderr calls should not be used as this would also
38            cause the streaming portion to spill over into the static portion.
39 
40     Copyright:
41         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
42         All rights reserved.
43 
44     License:
45         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
46         Alternatively, this file may be distributed under the terms of the Tango
47         3-Clause BSD License (see LICENSE_BSD.txt for details).
48 
49 *******************************************************************************/
50 
51 module ocean.io.console.AppStatus;
52 
53 import core.memory;
54 import core.stdc.math: lroundf;
55 import core.stdc.stdarg;
56 import core.stdc.stdlib: div;
57 import core.stdc.time: clock_t, clock, tm, time_t, time;
58 
59 import ocean.core.Array;
60 import ocean.core.StructConverter;
61 import ocean.core.TypeConvert;
62 import ocean.core.Verify;
63 import ocean.io.device.File;
64 import ocean.io.device.IODevice;
65 import ocean.io.Console;
66 import ocean.io.Stdout;
67 import ocean.io.Terminal;
68 import ocean.io.model.IConduit;
69 import ocean.text.convert.Formatter;
70 import ocean.time.Clock;
71 import ocean.time.MicrosecondsClock;
72 import ocean.transition;
73 import ocean.util.container.AppendBuffer;
74 import ocean.text.convert.Integer;
75 
76 import ocean.util.log.Event;
77 import ocean.util.log.ILogger;
78 import ocean.util.log.InsertConsole;
79 import ocean.util.log.layout.LayoutMessageOnly;
80 
81 
82 /// Ditto
83 public class AppStatus
84 {
85     /// Simplifies AppendBuffer usage by providing the sink
86     private final class StringBuffer : AppendBuffer!(char)
87     {
88         public void sink (in cstring chunk)
89         {
90             this ~= chunk;
91         }
92     }
93 
94     /***************************************************************************
95 
96         Message buffer used for formatting streaming lines. The buffer is public
97         so that, if more complex formatting is needed than is provided by the
98         displayStreamingLine() methods, then it can be used externally to format
99         any required messages. The version of displayStreamingLine() with no
100         arguments can then be called to print the contents of the buffer.
101 
102     ***************************************************************************/
103 
104     public StringBuffer msg;
105 
106 
107     /**************************************************************************
108 
109         Set of components to show on the heading line.
110 
111     **************************************************************************/
112 
113     public enum HeadingLineComponents
114     {
115         /// Don't show any components
116         None = 0,
117 
118         /// Show uptime info
119         Uptime = 1,
120 
121         /// Show CPU info
122         CpuUsage = 2,
123 
124         /// Show memory usage
125         MemoryUsage = 4,
126 
127         /// Convenience value to show All
128         All = MemoryUsage * 2 - 1,
129     }
130 
131 
132     /***************************************************************************
133 
134         Alias for system clock function.
135 
136     ***************************************************************************/
137 
138     private alias .clock system_clock;
139 
140 
141     /***************************************************************************
142 
143         Convenience aliases for derived classes.
144 
145     ***************************************************************************/
146 
147     protected alias .TerminalOutput TerminalOutput;
148 
149 
150     /**************************************************************************
151 
152         Number of columns in Terminal
153 
154     ***************************************************************************/
155 
156     private int terminal_columns;
157 
158 
159     /***************************************************************************
160 
161         One instance of the display properties of a line.
162 
163     ***************************************************************************/
164 
165     private struct DisplayProperties
166     {
167         /***********************************************************************
168 
169             String specifying the foreground colour (this is a string from the
170             Terminal.Foreground struct in `ocean.io.Terminal` and not a string
171             like "red").
172 
173         ***********************************************************************/
174 
175         char[] fg_colour;
176 
177 
178         /***********************************************************************
179 
180             String specifying the background colour (this is a string from the
181             Terminal.Background struct in `ocean.io.Terminal` and not a string
182             like "red").
183 
184         ***********************************************************************/
185 
186         char[] bg_colour;
187 
188 
189         /***********************************************************************
190 
191             Boolean value set to true if the line is in bold, false otherwise.
192 
193         ***********************************************************************/
194 
195         bool is_bold;
196     }
197 
198 
199     /***************************************************************************
200 
201         The currently configured display properties of a line. These properties
202         will be used when displaying the next streaming line or when formatting
203         the next static line.
204 
205     ***************************************************************************/
206 
207     private DisplayProperties current_display_props;
208 
209 
210     /***************************************************************************
211 
212         start time of the program. saved when first started and compared with
213         the current time to get the runtime of the program
214 
215     ***************************************************************************/
216 
217     private time_t start_time;
218 
219 
220     /***************************************************************************
221 
222         Saved value of the total time used by this application. Used to
223         calculate the cpu load of this program
224 
225     ***************************************************************************/
226 
227     private clock_t ticks = -1;
228 
229 
230     /***************************************************************************
231 
232         Expected milliseconds between calls to getCpuUsage. Needed to calculate
233         the cpu usage correctly. Defaults to 1000ms.
234 
235     ***************************************************************************/
236 
237     private ulong ms_between_calls;
238 
239 
240     /***************************************************************************
241 
242         private buffer for storing and formatting the static lines to display
243 
244     ***************************************************************************/
245 
246     private char[][] static_lines;
247 
248 
249     /***************************************************************************
250 
251         Buffer containing the display properties of each static line.
252 
253     ***************************************************************************/
254 
255     private DisplayProperties[] static_lines_display_props;
256 
257 
258     /***************************************************************************
259 
260         the name of the current application
261 
262     ***************************************************************************/
263 
264     private char[] app_name;
265 
266 
267     /***************************************************************************
268 
269         the version of the current application
270 
271     ***************************************************************************/
272 
273     private char[] app_version;
274 
275 
276     /***************************************************************************
277 
278         the build date of the current application
279 
280     ***************************************************************************/
281 
282     private char[] app_build_date;
283 
284 
285     /***************************************************************************
286 
287         who built the current application
288 
289     ***************************************************************************/
290 
291     private char[] app_build_author;
292 
293 
294     /***************************************************************************
295 
296         buffer used for the header line
297 
298     ***************************************************************************/
299 
300     private char[] heading_line;
301 
302 
303     /***************************************************************************
304 
305         buffer used for the footer
306 
307     ***************************************************************************/
308 
309     private char[] footer_line;
310 
311 
312     /***************************************************************************
313 
314         insert console used to display the streaming lines
315 
316     ***************************************************************************/
317 
318     private InsertConsole insert_console;
319 
320 
321     /***************************************************************************
322 
323         saved terminal size used to check if the terminal size has changed
324 
325     ***************************************************************************/
326 
327     private int old_terminal_size;
328 
329 
330     /***************************************************************************
331 
332         Output stream to output the streaming lines to. Can be null, in which
333         case output will be disabled.
334 
335     ***************************************************************************/
336 
337     private OutputStream stream;
338 
339 
340     /***************************************************************************
341 
342         TerminalOutput stream to output the static lines to. Can be null, in
343         which case output will be disabled.
344 
345     ***************************************************************************/
346 
347     private TerminalOutput terminal_output;
348 
349 
350     /**************************************************************************
351 
352         Indicator if the output is redirected. If so, no control characters
353         will be output.
354 
355     ***************************************************************************/
356 
357     private bool is_redirected;
358 
359 
360     /**************************************************************************
361 
362         Set of flags to show on the heading line of AppStatus.
363 
364     ***************************************************************************/
365 
366     private HeadingLineComponents heading_line_components;
367 
368 
369     /***************************************************************************
370 
371         Constructor. Saves the current time as the program start time.
372 
373         Params:
374             app_name = name of the application
375             app_version = version of the application
376             app_build_date = date the application was built
377             app_build_author = who built the current build
378             size = number of loglines that are to be displayed below the
379                     title line
380             ms_between_calls = expected milliseconds between calls to
381                                getCpuUsage (defaults to 1000ms)
382             stream = stream to write the streaming output into. Can be null
383                 if it's not yet available (the streaming output will then be
384                 disabled).
385             terminal_output = terminal to write the static output into. Can be
386                 null if it's not yet available (the static output will then be
387                 disabled).
388             heading_line_components = components to show on the heading line
389             terminal_columns = width of the terminal to assume. If zero, main
390                 terminal will be queried. Passing 0 will take the width from the
391                 current terminal (which must be present, otherwise no output will
392                 happen).
393 
394     ***************************************************************************/
395 
396     public this ( cstring app_name, cstring app_version, cstring app_build_date,
397         cstring app_build_author, uint size, ulong ms_between_calls = 1000,
398             OutputStream stream = Cout.stream, TerminalOutput terminal_output = Stdout,
399             HeadingLineComponents heading_line_components = HeadingLineComponents.All,
400             int terminal_columns = 80)
401     {
402         this.app_name.copy(app_name);
403         this.app_version.copy(app_version);
404         this.app_build_date.copy(app_build_date);
405         this.app_build_author.copy(app_build_author);
406         this.start_time = time(null);
407         this.static_lines.length = size;
408         this.static_lines_display_props.length = size;
409         this.ms_between_calls = ms_between_calls;
410         this.stream = stream;
411         this.terminal_output = terminal_output;
412         this.heading_line_components = heading_line_components;
413         this.old_terminal_size = Terminal.rows;
414 
415         if (terminal_columns > 0)
416         {
417             this.terminal_columns = terminal_columns;
418         }
419         else
420         {
421             this.terminal_columns = Terminal.columns;
422         }
423 
424         if (this.stream)
425         {
426             this.insert_console = new InsertConsole(this.stream, true,
427                 new LayoutMessageOnly);
428         }
429 
430         this.msg = new StringBuffer;
431 
432         // Care only about stdout redirection
433         if (stream == Cout.stream)
434         {
435             this.is_redirected = Cout.redirected;
436         }
437     }
438 
439 
440     /***************************************************************************
441 
442         Connects output for AppStatus to output. Useful if the output
443         doesn't exist during entire lifetime of AppStatus.
444 
445         stream = stream to write into
446         terminal_output = terminal to write into.
447 
448     ***************************************************************************/
449 
450     public void connectOutput ( OutputStream stream, TerminalOutput terminal_output )
451     {
452         verify(stream !is null);
453         verify(terminal_output !is null);
454 
455         this.stream = stream;
456         this.terminal_output = terminal_output;
457         if (this.insert_console is null)
458             this.insert_console = new InsertConsole(this.stream, true,
459                     new LayoutMessageOnly);
460         this.insert_console.connectOutput(stream);
461     }
462 
463 
464     /***************************************************************************
465 
466         Disconnects output from the AppStatus to output. All subsequent display*
467         methods will be no-op.
468 
469     ***************************************************************************/
470 
471     public void disconnectOutput ()
472     {
473         this.stream = null;
474         this.terminal_output = null;
475     }
476 
477 
478     /**************************************************************************
479 
480 
481         UnixSocketExt's handler which connects the connected socket to the
482         registered AppStatus instance, displays static lines and waits until
483         user disconnects. The optional parameter is the wanted terminal width
484         to assume.
485 
486         Params:
487             command = command used to call this handler
488             write_line = delegate to write data to the socket
489             read_line = delegate to read data from the socket
490             socket = IODevice instance of the connected socket.
491 
492     **************************************************************************/
493 
494     public void connectedSocketHandler ( cstring[] command,
495             scope void delegate (cstring) write_line,
496             scope void delegate (ref mstring) read_line, IODevice socket )
497     {
498         static File unix_socket_file;
499         static TerminalOutput unix_terminal_output;
500 
501         if (unix_socket_file is null)
502         {
503             unix_socket_file = new File;
504             unix_terminal_output = new TerminalOutput(unix_socket_file);
505         }
506 
507         unix_socket_file.setFileHandle(socket.fileHandle());
508         this.connectOutput(unix_socket_file,
509             unix_terminal_output);
510 
511         if (command.length > 0)
512         {
513             toInteger(command[0], this.terminal_columns);
514         }
515 
516         scope (exit)
517             this.disconnectOutput();
518 
519         this.displayStaticLines();
520 
521         // just wait for the user to disconnect
522         static mstring buffer;
523         buffer.length = 100;
524         while (true)
525         {
526             read_line(buffer);
527             if (buffer.length == 0)
528                 break;
529         }
530     }
531 
532 
533     /***************************************************************************
534 
535         Clear all the bottom static lines from the console (including header and
536         footer).
537 
538         This method is useful for erasing the bottom static lines when there is
539         nothing meaningful to be shown there anymore. This generally happens
540         close to the end of an application's run, and is thus expected to be
541         called only a single time.
542 
543         Note that the caller needs to make sure that the static lines are
544         already displayed before calling this method, otherwise unintended lines
545         might be deleted.
546 
547     ***************************************************************************/
548 
549     public void eraseStaticLines ( )
550     {
551         if (this.terminal_output is null)
552         {
553             return;
554         }
555 
556         // Add +2: One for header and one for footer
557         for (size_t i = 0; i < this.static_lines.length + 2; i++)
558         {
559             this.terminal_output.clearline.newline;
560         }
561 
562         // Each iteration in the previous loop moves the cursor one line to
563         // the bottom. We need to return it to the right position again.
564         // We can't combine both loops or we will be clearing and overwriting
565         // the same first line over and over.
566         for (size_t i = 0; i < this.static_lines.length + 2; i++)
567         {
568             this.terminal_output.up;
569         }
570 
571         this.terminal_output.flush;
572     }
573 
574 
575     /***************************************************************************
576 
577         Resizes the number of lines in the app status static display and clears
578         the current content of the static lines. Also resets the cursor
579         position so that the static lines are still at the bottom of the
580         display.
581 
582         Note:
583             A decrease in the number of static lines will result in one or more
584             blank lines appearing in the upper streaming portion of the output.
585             This is because on reducing the number of static lines, more space
586             is created for the streaming portion, but without anything to be
587             displayed there. The number of blank lines thus created will be
588             equal to the amount by which the number of static lines are being
589             reduced.
590 
591         Params:
592             size = number of loglines that are to be displayed below the
593                     title line
594 
595     ***************************************************************************/
596 
597     public void num_static_lines ( size_t size )
598     {
599         this.resetStaticLines();
600 
601         if ( this.is_redirected || this.terminal_output is null )
602         {
603             this.static_lines.length = size;
604 
605             this.static_lines_display_props.length = size;
606 
607             return;
608         }
609 
610         if ( this.static_lines.length > size )
611         {
612             // The number of static lines are being reduced
613 
614             // First remove the already displayed static lines from the console
615             this.resetCursorPosition();
616 
617             // ...and then remove the static lines header
618             this.terminal_output.clearline.cr.flush.up;
619         }
620         else if ( this.static_lines.length < size )
621         {
622             // The number of static lines are being increased
623 
624             // First remove the static lines header
625             this.terminal_output.clearline.cr.flush.up;
626 
627             // ...and then push up the streaming portion on the top by
628             //        the new number of static lines
629             //        + the static lines header
630             //        + the static lines footer
631             for ( auto i = 0; i < (size + 2); ++i )
632             {
633                 this.terminal_output.formatln("");
634             }
635         }
636 
637         this.static_lines.length = size;
638 
639         this.static_lines_display_props.length = size;
640 
641         this.resetCursorPosition();
642     }
643 
644 
645     /***************************************************************************
646 
647         Gets the current number of lines in the app status static display.
648 
649         Returns:
650             The current number of lines in the static bottom portion of the
651             split console.
652 
653     ***************************************************************************/
654 
655     public size_t num_static_lines ( )
656     {
657         return this.static_lines.length;
658     }
659 
660 
661     /***************************************************************************
662 
663         Print the current static lines set by the calling program to this.terminal_output
664         with a title line showing the current time, runtime, and memory and cpu
665         usage and a footer line showing the version information.
666 
667         Check if the size of the terminal has changed and if it has move the
668         cursor to the end of the terminal.
669 
670         Print a blank line for each logline and one for the footer. Then print
671         the footer and move up. Then in reverse order print a line and move the
672         cursor up. When all the lines have been printed, print the heading line.
673 
674         Note: This method doesn't do anything if the console output is being
675               redirected from the command-line.
676 
677     ***************************************************************************/
678 
679     public void displayStaticLines ( )
680     {
681         if ( this.is_redirected || this.terminal_output is null)
682         {
683             return;
684         }
685 
686         this.checkCursorPosition();
687 
688         foreach ( line; this.static_lines )
689         {
690             this.terminal_output.formatln("");
691         }
692         this.terminal_output.formatln("");
693 
694         this.printVersionInformation();
695         this.terminal_output.clearline.cr.flush.up;
696 
697         foreach_reverse ( index, line; this.static_lines )
698         {
699             if ( line.length )
700             {
701                 this.applyDisplayProps(this.static_lines_display_props[index]);
702 
703                 this.terminal_output.format(this.truncateLength(line));
704             }
705             this.terminal_output.clearline.cr.flush.up;
706         }
707 
708         this.printHeadingLine();
709     }
710 
711 
712     /***************************************************************************
713 
714         Convenience aliases for setting the foreground colour.
715 
716     ***************************************************************************/
717 
718     public alias saveColour!(true,  Terminal.Foreground.DEFAULT) default_colour;
719     public alias saveColour!(true,  Terminal.Foreground.BLACK)   black;
720     public alias saveColour!(true,  Terminal.Foreground.RED)     red;
721     public alias saveColour!(true,  Terminal.Foreground.GREEN)   green;
722     public alias saveColour!(true,  Terminal.Foreground.YELLOW)  yellow;
723     public alias saveColour!(true,  Terminal.Foreground.BLUE)    blue;
724     public alias saveColour!(true,  Terminal.Foreground.MAGENTA) magenta;
725     public alias saveColour!(true,  Terminal.Foreground.CYAN)    cyan;
726     public alias saveColour!(true,  Terminal.Foreground.WHITE)   white;
727 
728 
729     /***************************************************************************
730 
731         Convenience aliases for setting the background colour.
732 
733     ***************************************************************************/
734 
735     public alias saveColour!(false, Terminal.Background.DEFAULT) default_bg;
736     public alias saveColour!(false, Terminal.Background.BLACK)   black_bg;
737     public alias saveColour!(false, Terminal.Background.RED)     red_bg;
738     public alias saveColour!(false, Terminal.Background.GREEN)   green_bg;
739     public alias saveColour!(false, Terminal.Background.YELLOW)  yellow_bg;
740     public alias saveColour!(false, Terminal.Background.BLUE)    blue_bg;
741     public alias saveColour!(false, Terminal.Background.MAGENTA) magenta_bg;
742     public alias saveColour!(false, Terminal.Background.CYAN)    cyan_bg;
743     public alias saveColour!(false, Terminal.Background.WHITE)   white_bg;
744 
745 
746     /***************************************************************************
747 
748         Sets / unsets the configured boldness. This setting will be used when
749         displaying the next streaming line or when formatting the next static
750         line.
751 
752         Params:
753             is_bold = true if bold is desired, false otherwise
754 
755     ***************************************************************************/
756 
757     public typeof(this) bold ( bool is_bold = true )
758     {
759         this.current_display_props.is_bold = is_bold;
760 
761         return this;
762     }
763 
764 
765     /***************************************************************************
766 
767         Format one of the static lines. The application can set the number of
768         static lines either when constructing this module or by calling the
769         'num_static_lines' method.
770         This method is then used to format the contents of the static lines.
771 
772         Params:
773             index = the index of the static line to format
774             format = format string of the message
775             args = list of any extra arguments for the message
776 
777         Returns:
778             this object for method chaining
779 
780     ***************************************************************************/
781 
782     public typeof(this) formatStaticLine (T...) ( uint index, cstring format,
783         T args )
784     {
785         verify(index < this.static_lines.length, "adding too many static lines" );
786 
787         this.static_lines[index].length = 0;
788         assumeSafeAppend(this.static_lines[index]);
789         sformat(this.static_lines[index], format, args);
790 
791         structConvert!(DisplayProperties)(
792             this.current_display_props,
793             this.static_lines_display_props[index]
794         );
795 
796         return this;
797     }
798 
799 
800     /***************************************************************************
801 
802         Print a formatted streaming line above the static lines.
803 
804         Params:
805             Args = Tuple of arguments to format
806             format = format string of the streaming line
807             args = Arguments for the streaming line
808 
809         Returns:
810             this object for method chaining
811 
812     ***************************************************************************/
813 
814     public typeof(this) displayStreamingLine (Args...) ( cstring format, Args args )
815     {
816         this.msg.length = 0;
817         sformat(&this.msg.sink, format, args);
818 
819         return this.displayStreamingLine();
820     }
821 
822 
823     /***************************************************************************
824 
825         Print the contents of this.msg as streaming line above the static lines.
826 
827         Returns:
828             this object for method chaining
829 
830     ***************************************************************************/
831 
832     public typeof(this) displayStreamingLine () ( )
833     {
834         if (this.stream is null)
835         {
836             return this;
837         }
838 
839         if ( this.is_redirected )
840         {
841             this.terminal_output.formatln("{}", this.msg[]).newline.flush;
842 
843             return this;
844         }
845 
846         LogEvent event = {
847             time: Clock.now,
848             msg: this.msg[],
849         };
850 
851         this.applyDisplayProps(this.current_display_props);
852 
853         this.insert_console.append(event);
854 
855         return this;
856     }
857 
858     /***************************************************************************
859 
860         Get the current uptime for the program using the start time and current
861         time. Then divide the uptime in to weeks, days, hours, minutes, and
862         seconds.
863 
864         Params:
865             weeks = weeks of runtime
866             days = days of runtime
867             hours = hours of runtime
868             mins = minutes of runtime
869             secs = seconds of runtime
870 
871     ***************************************************************************/
872 
873     public void getUptime ( out uint weeks, out uint days, out uint hours,
874         out uint mins, out uint secs )
875     {
876         time_t _uptime = time(null) - this.start_time;
877         verify(_uptime < int.max && _uptime > int.min);
878         uint uptime = castFrom!(long).to!(int)(_uptime);
879 
880         uint uptimeFract ( uint denom )
881         {
882             with ( div(uptime, denom) )
883             {
884                 uptime = quot;
885                 return rem;
886             }
887         }
888 
889         secs = uptimeFract(60);
890         mins = uptimeFract(60);
891         hours = uptimeFract(24);
892         days = uptimeFract(7);
893         weeks = uptime;
894     }
895 
896 
897     /***************************************************************************
898 
899         Calculate the current memory usage of this program using the GC stats
900 
901         Params:
902             mem_allocated = the amount of memory currently allocated
903             mem_free = the amount of allocated memory that is currently free
904 
905         Returns:
906             true if the memory usage was properly gathered, false if is not
907             available.
908 
909     ***************************************************************************/
910 
911     public bool getMemoryUsage ( out float mem_allocated, out float mem_free )
912     {
913         static immutable float Mb = 1024 * 1024;
914         auto stats = GC.stats();
915 
916         if (stats.usedSize == 0 && stats.freeSize == 0)
917             return false;
918 
919         mem_allocated = cast(float)(stats.usedSize + stats.freeSize) / Mb;
920         mem_free = cast(float)stats.freeSize / Mb;
921 
922         return true;
923     }
924 
925 
926     /***************************************************************************
927 
928         Get the current cpu usage of this program. Uses the clock() method
929         that returns the total current time used by this program. This is then
930         used to compute the current cpu load of this program.
931 
932         Params:
933             usage = the current CPU usage of this program as a percentage
934 
935     ***************************************************************************/
936 
937     public void getCpuUsage ( out long usage )
938     {
939         clock_t ticks = system_clock();
940         if ( this.ticks >= 0 )
941         {
942             usage =
943                 lroundf((ticks - this.ticks) / (this.ms_between_calls * 10.0f));
944         }
945         this.ticks = ticks;
946     }
947 
948 
949     /***************************************************************************
950 
951         Print the heading line. Includes the current time, runtime, and memory
952         and cpu usage of this application (prints in bold).
953 
954     ***************************************************************************/
955 
956     private void printHeadingLine ( )
957     {
958         ulong us; // unused
959         auto dt = MicrosecondsClock.toDateTime(MicrosecondsClock.now(), us);
960 
961         this.heading_line.length = 0;
962         assumeSafeAppend(this.heading_line);
963 
964         sformat(this.heading_line, "[{:d2}/{:d2}/{:d2} "
965           ~ "{:d2}:{:d2}:{:d2}] {}", dt.date.day, dt.date.month, dt.date.year,
966             dt.time.hours, dt.time.minutes, dt.time.seconds, this.app_name);
967 
968         if (this.heading_line_components & HeadingLineComponents.Uptime)
969         {
970             this.formatUptime();
971         }
972 
973         if (this.heading_line_components & HeadingLineComponents.MemoryUsage)
974         {
975             this.formatMemoryUsage();
976         }
977 
978         if (this.heading_line_components & HeadingLineComponents.CpuUsage)
979         {
980             this.formatCpuUsage();
981         }
982 
983         this.terminal_output.default_colour.default_bg.bold(true)
984             .format(this.truncateLength(this.heading_line)).bold(false)
985             .clearline.cr.flush;
986     }
987 
988 
989     /***************************************************************************
990 
991         Format the memory usage for the current program to using the
992         GC stats to calculate current usage (if available).
993 
994     ***************************************************************************/
995 
996     private void formatMemoryUsage ( )
997     {
998         float mem_allocated, mem_free;
999 
1000         bool stats_available = this.getMemoryUsage(mem_allocated, mem_free);
1001 
1002         if (stats_available)
1003         {
1004             sformat(this.heading_line,
1005                 " Memory: Used {}Mb/Free {}Mb", mem_allocated, mem_free);
1006         }
1007         else
1008         {
1009             sformat(this.heading_line, " Memory: n/a");
1010         }
1011     }
1012 
1013 
1014     /***************************************************************************
1015 
1016         Format the current uptime for the current program.
1017 
1018     ***************************************************************************/
1019 
1020     private void formatUptime ( )
1021     {
1022         uint weeks, days, hours, mins, secs;
1023         this.getUptime(weeks, days, hours, mins, secs);
1024         sformat(this.heading_line, " Uptime: {}w{:d1}d{:d2}:"
1025             ~ "{:d2}:{:d2}", weeks, days, hours, mins, secs);
1026     }
1027 
1028 
1029     /***************************************************************************
1030 
1031         Format the current cpu usage of this program.
1032 
1033     ***************************************************************************/
1034 
1035     private void formatCpuUsage ( )
1036     {
1037         long usage = 0;
1038         this.getCpuUsage(usage);
1039         sformat(this.heading_line, " CPU: {}%", usage);
1040     }
1041 
1042 
1043     /***************************************************************************
1044 
1045         Print the version and build information for this application (prints in
1046         bold).
1047 
1048         Additional text may be printed at this point by sub-classes which
1049         override the protected printExtraVersionInformation(). This method is
1050         only called if there are > 0 character remaining on the terminal line
1051         after the standard version info has been displayed. Note that the sub-
1052         class is responsible for making sure that any extra text printed does
1053         not exceed the specified number of characters (presumably by calling
1054         truncateLength()).
1055 
1056     ***************************************************************************/
1057 
1058     private void printVersionInformation ( )
1059     {
1060         this.footer_line.length = 0;
1061         assumeSafeAppend(this.footer_line);
1062 
1063         sformat(this.footer_line, "Version {} built on {} by {}",
1064             this.app_version, this.app_build_date, this.app_build_author);
1065 
1066         this.terminal_output.default_colour.default_bg.bold(true)
1067             .format(this.truncateLength(this.footer_line)).bold(false);
1068 
1069         auto remaining = Terminal.columns - this.footer_line.length;
1070         if ( remaining )
1071         {
1072             this.footer_line.length = 0;
1073             assumeSafeAppend(this.footer_line);
1074             this.printExtraVersionInformation(this.terminal_output, this.footer_line,
1075                 remaining);
1076         }
1077     }
1078 
1079 
1080     /***************************************************************************
1081 
1082         Prints additional text after the standard version info. The default
1083         implementation prints nothing, but sub-classes may override this method
1084         to provide specialised version information.
1085 
1086         Params:
1087             output = terminal output to use
1088             buffer = buffer which may be used for formatting (initially empty)
1089             max_length = the maximum number of characters remaining in the
1090                 terminal line. It is the sub-class' responsiblity to check that
1091                 printed text does not exceed this length, presumably by calling
1092                 truncateLength()
1093 
1094     ***************************************************************************/
1095 
1096     protected void printExtraVersionInformation ( TerminalOutput output,
1097         ref char[] buffer, size_t max_length )
1098     {
1099     }
1100 
1101 
1102     /***************************************************************************
1103 
1104         Check the length of the buffer against the number of columns in the
1105         terminal. If the buffer is too long, set it to the terminal width.
1106 
1107         Params:
1108             buffer = buffer to check the length of
1109 
1110         Returns:
1111             the truncated buffer
1112 
1113     ***************************************************************************/
1114 
1115     protected char[] truncateLength ( ref char[] buffer )
1116     {
1117         return this.truncateLength(buffer, this.terminal_columns);
1118     }
1119 
1120 
1121     /***************************************************************************
1122 
1123         Check the length of the buffer against the specified maximum length. If
1124         the buffer is too long, set it to maximum.
1125 
1126         Params:
1127             buffer = buffer to check the length of
1128             max = maximum number of characters in buffer
1129 
1130         Returns:
1131             the truncated buffer
1132 
1133     ***************************************************************************/
1134 
1135     protected char[] truncateLength ( ref char[] buffer, size_t max )
1136     {
1137         if ( buffer.length > max )
1138         {
1139             buffer.length = max;
1140         }
1141         return buffer;
1142     }
1143 
1144 
1145     /***************************************************************************
1146 
1147         Check the height of the terminal. If the height has changed, reset the
1148         cursor position.
1149 
1150     ***************************************************************************/
1151 
1152     private void checkCursorPosition ( )
1153     {
1154         if ( this.old_terminal_size != Terminal.rows )
1155         {
1156             this.resetCursorPosition();
1157         }
1158     }
1159 
1160 
1161     /***************************************************************************
1162 
1163         Reset the cursor position to the end of the terminal and then move the
1164         cursor up by the number of static lines that are being printed.
1165 
1166     ***************************************************************************/
1167 
1168     private void resetCursorPosition ( )
1169     {
1170         this.terminal_output.endrow;
1171         this.old_terminal_size = Terminal.rows;
1172 
1173         foreach ( line; this.static_lines )
1174         {
1175             this.terminal_output.clearline.cr.flush.up;
1176         }
1177         this.terminal_output.clearline.cr.flush.up;
1178     }
1179 
1180 
1181     /***************************************************************************
1182 
1183         Reset the content of all the static lines by setting the length to 0.
1184 
1185     ***************************************************************************/
1186 
1187     private void resetStaticLines ( )
1188     {
1189         foreach ( ref line; this.static_lines )
1190         {
1191             line.length = 0;
1192             assumeSafeAppend(line);
1193         }
1194     }
1195 
1196 
1197     /***************************************************************************
1198 
1199         Save the given foreground or background colour. This colour will be used
1200         when displaying the next streaming line or when formatting the next
1201         static line.
1202 
1203         Params:
1204             is_foreground = true if the given colour is for the foreground,
1205                             false if it is for the background
1206             colour = the colour to be saved (this is a string from the
1207                      Terminal.Foreground or Terminal.Background struct in
1208                      `ocean.io.Terminal` and not a string like "red")
1209 
1210         Returns:
1211             this object for method chaining
1212 
1213     ***************************************************************************/
1214 
1215     private typeof(this) saveColour ( bool is_foreground, string colour ) ( )
1216     {
1217         static if ( is_foreground )
1218         {
1219             this.current_display_props.fg_colour.copy(colour);
1220         }
1221         else
1222         {
1223             this.current_display_props.bg_colour.copy(colour);
1224         }
1225 
1226         return this;
1227     }
1228 
1229 
1230     /***************************************************************************
1231 
1232         Apply the given foreground colour.
1233 
1234         Params:
1235             fg_colour = the foreground colour to be applied (this is a string
1236                         from the Terminal.Foreground struct in
1237                         `ocean.io.Terminal` and not a string like "red")
1238 
1239     ***************************************************************************/
1240 
1241     private void applyFgColour ( char[] fg_colour )
1242     {
1243         switch ( fg_colour )
1244         {
1245             case Terminal.Foreground.BLACK:
1246             {
1247                 this.terminal_output.black();
1248                 break;
1249             }
1250 
1251             case Terminal.Foreground.RED:
1252             {
1253                 this.terminal_output.red();
1254                 break;
1255             }
1256 
1257             case Terminal.Foreground.GREEN:
1258             {
1259                 this.terminal_output.green();
1260                 break;
1261             }
1262 
1263             case Terminal.Foreground.YELLOW:
1264             {
1265                 this.terminal_output.yellow();
1266                 break;
1267             }
1268 
1269             case Terminal.Foreground.BLUE:
1270             {
1271                 this.terminal_output.blue();
1272                 break;
1273             }
1274 
1275             case Terminal.Foreground.MAGENTA:
1276             {
1277                 this.terminal_output.magenta();
1278                 break;
1279             }
1280 
1281             case Terminal.Foreground.CYAN:
1282             {
1283                 this.terminal_output.cyan();
1284                 break;
1285             }
1286 
1287             case Terminal.Foreground.WHITE:
1288             {
1289                 this.terminal_output.white();
1290                 break;
1291             }
1292 
1293             case Terminal.Foreground.DEFAULT:
1294             default:
1295             {
1296                 this.terminal_output.default_colour();
1297                 break;
1298             }
1299         }
1300     }
1301 
1302 
1303     /***************************************************************************
1304 
1305         Apply the given background colour.
1306 
1307         Params:
1308             bg_colour = the background colour to be applied (this is a string
1309                         from the Terminal.Background struct in
1310                         `ocean.io.Terminal` and not a string like "red")
1311 
1312     ***************************************************************************/
1313 
1314     private void applyBgColour ( char[] bg_colour )
1315     {
1316         switch ( bg_colour )
1317         {
1318             case Terminal.Background.BLACK:
1319             {
1320                 this.terminal_output.black_bg();
1321                 break;
1322             }
1323 
1324             case Terminal.Background.RED:
1325             {
1326                 this.terminal_output.red_bg();
1327                 break;
1328             }
1329 
1330             case Terminal.Background.GREEN:
1331             {
1332                 this.terminal_output.green_bg();
1333                 break;
1334             }
1335 
1336             case Terminal.Background.YELLOW:
1337             {
1338                 this.terminal_output.yellow_bg();
1339                 break;
1340             }
1341 
1342             case Terminal.Background.BLUE:
1343             {
1344                 this.terminal_output.blue_bg();
1345                 break;
1346             }
1347 
1348             case Terminal.Background.MAGENTA:
1349             {
1350                 this.terminal_output.magenta_bg();
1351                 break;
1352             }
1353 
1354             case Terminal.Background.CYAN:
1355             {
1356                 this.terminal_output.cyan_bg();
1357                 break;
1358             }
1359 
1360             case Terminal.Background.WHITE:
1361             {
1362                 this.terminal_output.white_bg();
1363                 break;
1364             }
1365 
1366             case Terminal.Background.DEFAULT:
1367             default:
1368             {
1369                 this.terminal_output.default_bg();
1370                 break;
1371             }
1372         }
1373     }
1374 
1375 
1376     /***************************************************************************
1377 
1378         Apply the currently configured display properties to standard output.
1379 
1380         Params:
1381             display_props = struct instance containing the display properties to
1382                             be applied
1383 
1384     ***************************************************************************/
1385 
1386     private void applyDisplayProps ( DisplayProperties display_props )
1387     {
1388         this.applyFgColour(display_props.fg_colour);
1389 
1390         this.applyBgColour(display_props.bg_colour);
1391 
1392         this.terminal_output.bold(display_props.is_bold);
1393     }
1394 }
1395 
1396 ///
1397 unittest
1398 {
1399     void example ()
1400     {
1401         static immutable number_of_static_lines = 2;
1402         static immutable ms_between_calls = 1000;
1403 
1404         AppStatus app_status = new AppStatus("test",
1405             "revision", "build_date", "build_author",
1406             number_of_static_lines, ms_between_calls);
1407 
1408         ulong c1, c2, c3, c4, c5, c6;
1409 
1410         app_status.formatStaticLine(0, "{} count1, {} count2", c1, c2);
1411         app_status.formatStaticLine(1, "{} count3, {} count4", c3, c4);
1412 
1413         app_status.displayStaticLines();
1414 
1415         app_status.displayStreamingLine("{} count5, {} count6", c5, c6);
1416 
1417         // The colour and boldness of the static/streaming lines can be
1418         // controlled in the following manner:
1419 
1420         app_status.bold.red
1421             .formatStaticLine(0, "this static line will be in red and bold");
1422         app_status.bold(false).green
1423             .formatStaticLine(1, "and this one will be in green and not bold");
1424 
1425         app_status.blue.displayStreamingLine("here's a blue streaming line");
1426     }
1427 }
1428 
1429 unittest
1430 {
1431     // tests if array stomping assertion triggers during line formatting
1432 
1433     auto number_of_static_lines = 2;
1434 
1435     AppStatus app_status = new AppStatus("test", "version",
1436         "build_date", "build_author", number_of_static_lines);
1437 
1438     ulong c1, c2, c3, c4;
1439 
1440     app_status.formatStaticLine(0, "{} count1, {} count2", c1, c2);
1441     app_status.formatStaticLine(0, "{} count1, {} count2", c1, c2);
1442     app_status.formatStaticLine(1, "{} count3, {} count4", c3, c4);
1443     app_status.formatStaticLine(1, "{} count3, {} count4", c3, c4);
1444 }