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 }