aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-07-26 13:44:24 +0200
committerKaren Arutyunov <karen@codesynthesis.com>2017-07-27 12:22:19 +0300
commit72450e72e400eb03325ca182f0e118fde8cd1705 (patch)
tree8e7d7c9fba4b99d047c1f770dcb85479b2c0ede1
parent3fae4ef8b228374c865bc0afcb1041eabae5a111 (diff)
Add support for printing progress
-rw-r--r--libbutl/diagnostics.cxx93
-rw-r--r--libbutl/diagnostics.hxx57
-rw-r--r--libbutl/fdstream.hxx2
-rw-r--r--tests/buildfile2
-rw-r--r--tests/progress/buildfile7
-rw-r--r--tests/progress/driver.cxx62
6 files changed, 206 insertions, 17 deletions
diff --git a/libbutl/diagnostics.cxx b/libbutl/diagnostics.cxx
index e73412b..05661ec 100644
--- a/libbutl/diagnostics.cxx
+++ b/libbutl/diagnostics.cxx
@@ -4,9 +4,23 @@
#include <libbutl/diagnostics.hxx>
+#ifndef _WIN32
+# include <unistd.h> // write()
+#else
+# include <libbutl/win32-utility.hxx>
+
+# include <io.h> //_write()
+#endif
+
+#include <ios> // ios::failure
#include <mutex>
+#include <string>
+#include <cassert>
+#include <cstddef> // size_t
#include <iostream> // cerr
+#include <libbutl/fdstream.hxx> // stderr_fd()
+
using namespace std;
namespace butl
@@ -15,13 +29,82 @@ namespace butl
static mutex diag_mutex;
- diag_lock::diag_lock ()
+ string diag_progress;
+ static string diag_progress_blank; // Being printed blanks out the line.
+ static size_t diag_progress_size; // Size of the last printed progress.
+
+ // Print the progress string to STDERR. Ignore underlying OS errors (this is
+ // a progress bar after all, and throwing from dtors wouldn't be nice). Must
+ // be called with the diag_mutex being aquired.
+ //
+ // Note that the output will not interleave with that of independent writers,
+ // given that the printed strings are not longer than PIPE_BUF for POSIX
+ // (which is at least 512 bytes on all platforms).
+ //
+ // @@ Is there any atomicity guarantees on Windows?
+ //
+ static inline void
+ progress_print (string& s)
{
+ // If the new progress string is shorter than the printed one, then we will
+ // complement it with the required number of spaces (to overwrite the
+ // trailing junk) prior to printing, and restore it afterwards.
+ //
+ size_t n (s.size ());
+
+ if (n < diag_progress_size)
+ s.append (diag_progress_size - n, ' ');
+
+ if (!s.empty ())
+ {
+ s += '\r'; // Position the cursor at the beginning of the line.
+
+ try
+ {
+#ifndef _WIN32
+ write (stderr_fd(), s.c_str (), s.size ());
+#else
+ _write (stderr_fd(), s.c_str (), static_cast<unsigned int> (s.size ()));
+#endif
+ }
+ catch (const ios::failure&) {}
+
+ s.resize (n); // Restore the progress string.
+ diag_progress_size = n; // Save the size of the printed progress string.
+ }
+ }
+
+ diag_stream_lock::diag_stream_lock ()
+ {
+ diag_mutex.lock ();
+
+ // If diagnostics shares the output stream with the progress bar, then
+ // blank out the line prior to printing the diagnostics, if required.
+ //
+ if (diag_stream == &cerr && diag_progress_size != 0)
+ progress_print (diag_progress_blank);
+ }
+
+ diag_stream_lock::~diag_stream_lock ()
+ {
+ // If diagnostics shares output stream with the progress bar, then reprint
+ // the current progress string, that was overwritten with the diagnostics.
+ //
+ if (diag_stream == &cerr && !diag_progress.empty ())
+ progress_print (diag_progress);
+
+ diag_mutex.unlock ();
+ }
+
+ diag_progress_lock::diag_progress_lock ()
+ {
+ assert (diag_stream == &cerr);
diag_mutex.lock ();
}
- diag_lock::~diag_lock ()
+ diag_progress_lock::~diag_progress_lock ()
{
+ progress_print (diag_progress);
diag_mutex.unlock ();
}
@@ -33,11 +116,7 @@ namespace butl
if (epilogue_ == nullptr)
{
os.put ('\n');
-
- {
- diag_lock l;
- *diag_stream << os.str ();
- }
+ diag_stream_lock () << os.str ();
// We can endup flushing the result of several writes. The last one may
// possibly be incomplete, but that's not a problem as it will also be
diff --git a/libbutl/diagnostics.hxx b/libbutl/diagnostics.hxx
index c195ba0..9544945 100644
--- a/libbutl/diagnostics.hxx
+++ b/libbutl/diagnostics.hxx
@@ -23,8 +23,8 @@ namespace butl
// Diagnostics destination stream (std::cerr by default). Note that its
// modification is not MT-safe. Also note that concurrent writing to the
// stream from multiple threads can result in interleaved characters. To
- // prevent this an object of diag_lock type (see below) must be created prior
- // to write operation.
+ // prevent this an object of diag_stream_lock type (see below) must be
+ // created prior to write operation.
//
LIBBUTL_SYMEXPORT extern std::ostream* diag_stream;
@@ -32,12 +32,51 @@ namespace butl
// An object of the type must be created prior to writing to diag_stream (see
// above).
//
- struct LIBBUTL_SYMEXPORT diag_lock
+ struct LIBBUTL_SYMEXPORT diag_stream_lock
{
- diag_lock ();
- ~diag_lock ();
+ diag_stream_lock ();
+ ~diag_stream_lock ();
+
+ // Support for one-liners, for example:
+ //
+ // diag_stream_lock () << "Hello, World!" << endl;
+ //
+ template <typename T>
+ std::ostream&
+ operator<< (const T& x) const
+ {
+ return *diag_stream << x;
+ }
+ };
+
+ // Progress line facility.
+ //
+ // The idea is to keep a progress line at the bottom of the terminal with
+ // other output scrolling above it. The printing of the progress line is
+ // integrated into diag_stream_lock and diag_progress_lock. To print or
+ // update the progress acquire diag_progress_lock and update the
+ // diag_progress string. To remove the progress line, set this string to
+ // empty. For better readability start the progress line with a space
+ // (which is where the cursor will be parked). Should only be used if
+ // diag_stream points to std::cerr.
+ //
+ // Note that child processes writing to the same stream may not completely
+ // overwrite the progress line so in this case it makes sense to keep the
+ // line as short as possible.
+ //
+ // To restore the progress line being overwritten by an independent writer
+ // (such as a child process), create and destroy the diag_progress_lock.
+ //
+ LIBBUTL_SYMEXPORT extern std::string diag_progress;
+
+ struct LIBBUTL_SYMEXPORT diag_progress_lock
+ {
+ diag_progress_lock ();
+ ~diag_progress_lock ();
};
+ //
+ //
struct diag_record;
template <typename> struct diag_prologue;
template <typename> struct diag_mark;
@@ -47,11 +86,11 @@ namespace butl
struct LIBBUTL_SYMEXPORT diag_record
{
template <typename T>
- friend const diag_record&
- operator<< (const diag_record& r, const T& x)
+ const diag_record&
+ operator<< (const T& x) const
{
- r.os << x;
- return r;
+ os << x;
+ return *this;
}
diag_record ()
diff --git a/libbutl/fdstream.hxx b/libbutl/fdstream.hxx
index 6673fed..fa62ff4 100644
--- a/libbutl/fdstream.hxx
+++ b/libbutl/fdstream.hxx
@@ -596,6 +596,8 @@ namespace butl
fdmode (int, fdstream_mode);
// Portable functions for obtaining file descriptors of standard streams.
+ // Throw ios::failure on the underlying OS error.
+ //
// Note that you normally wouldn't want to close them using fddup() to
// convert them to auto_fd, for example:
//
diff --git a/tests/buildfile b/tests/buildfile
index 9a11c82..4c1708c 100644
--- a/tests/buildfile
+++ b/tests/buildfile
@@ -2,4 +2,4 @@
# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
# license : MIT; see accompanying LICENSE file
-./: {*/ -build/ -curl/ -openssl/ -sendmail/}
+./: {*/ -build/ -curl/ -openssl/ -sendmail/ -progress/}
diff --git a/tests/progress/buildfile b/tests/progress/buildfile
new file mode 100644
index 0000000..19e85c0
--- /dev/null
+++ b/tests/progress/buildfile
@@ -0,0 +1,7 @@
+# file : tests/progress/buildfile
+# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+
+exe{driver}: {hxx cxx}{*} $libs
diff --git a/tests/progress/driver.cxx b/tests/progress/driver.cxx
new file mode 100644
index 0000000..702319a
--- /dev/null
+++ b/tests/progress/driver.cxx
@@ -0,0 +1,62 @@
+// file : tests/progress/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef _WIN32
+# include <thread> // this_thread::sleep_for()
+#else
+# include <libbutl/win32-utility.hxx>
+#endif
+
+#include <cstddef> // size_t
+#include <iostream>
+
+#include <libbutl/diagnostics.hxx>
+
+using namespace std;
+using namespace butl;
+
+int
+main ()
+{
+ auto sleep = [] (size_t ms = 100)
+ {
+ // MINGW GCC 4.9 doesn't implement this_thread so use Win32 Sleep().
+ //
+#ifndef _WIN32
+ this_thread::sleep_for (chrono::milliseconds (ms));
+#else
+ Sleep (static_cast<DWORD> (ms));
+#endif
+ };
+
+ for (size_t i (100); i != 0; --i)
+ {
+ if (i % 10 == 0)
+ diag_stream_lock () << "Line " << i / 10 << endl;
+
+ {
+ diag_progress_lock l;
+ diag_progress = " " + to_string (i) + "%";
+ }
+
+ sleep ();
+ }
+
+ sleep (1000);
+
+ // Test that the progress line is restored by the diag_stream_lock.
+ //
+ diag_stream_lock () << "Printed to diag_stream" << endl;
+
+ sleep (1000);
+
+ // Test that the progress line is restored after printing to cerr.
+ //
+ {
+ cerr << "Printed to std::cerr" << endl;
+ diag_progress_lock ();
+ }
+
+ sleep (1000);
+}