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