[gdb/cli] Add a progress meter

Tom de Vries
2020-12-11T17:51:52Z

[-- Attachment #1: Type: text/plain, Size: 859 bytes --]

[ Re: [PATCH] gdb: print size of downloaded debuginfod binary ]

On 12/10/20 8:21 PM, Tom Tromey wrote:
> Tom> This updated patch fixes both problems, the latter by printing the %
> Tom> progress on its own line (which is cleared by a final '\r' after
> Tom> reaching 100%, in order not to waste lines on this).
>
> A long time ago I had patches to show a progress bar when reading DWARF.
>
> https://sourceware.org/pipermail/gdb-patches/2017-April/139882.html
>
> Maybe this could be resurrected.
> I've occasionally thought of adding this to the TUI as well, say briefly
> overwriting the status bar with a progress meter.

I've taken a look, and think that this code is more in line with gdb cli
setup, so I've reimplemented on top of this.

Here is the first patch, adding the progress meter infrastructure
factored out from your patch.

Thanks,
- Tom

[-- Attachment #2: 0001-gdb-cli-Add-a-progress-meter.patch --]
[-- Type: text/x-patch, Size: 7779 bytes --]

[gdb/cli] Add a progress meter

This patch adds a progress bar. It's not used anywhere yet.

gdb/ChangeLog:

2020-12-11 Tom Tromey <tom@tromey.com>
Tom de Vries <tdevries@suse.de>

* utils.h (get_chars_per_line): Declare.
* utils.c (get_chars_per_line): New function.
(fputs_maybe_filtered): Handle '\r'.
* ui-out.h (ui_out::progress_meter): New class.
(ui_out::progress, ui_out::do_progress_start)
(ui_out::do_progress_notify, ui_out::do_progress_end): New
methods.
* ui-out.c (do_progress_end)
(make_cleanup_ui_out_progress_begin_end, ui_out_progress): New
functions.
* mi/mi-out.h (mi_ui_out::do_progress_start)
(mi_ui_out::do_progress_notify, mi_ui_out::do_progress_end): New
methods.
* cli-out.h (struct cli_ui_out) <do_progress_start,
do_progress_notify, do_progress_end>: New methods.
<enum meter_stat, struct cli_progress_info>: New.
<m_meters>: New member.
* cli-out.c (cli_ui_out::do_progress_start)
(cli_ui_out::do_progress_notify, cli_ui_out::do_progress_end): New
methods.

---
gdb/cli-out.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
gdb/cli-out.h | 29 ++++++++++++++++++++
gdb/mi/mi-out.h | 12 ++++++++
gdb/ui-out.h | 36 ++++++++++++++++++++++++
gdb/utils.c | 8 ++++++
gdb/utils.h | 4 +++
6 files changed, 174 insertions(+)

diff --git a/gdb/cli-out.c b/gdb/cli-out.c
index e47272ad87..5d07426b73 100644
--- a/gdb/cli-out.c
+++ b/gdb/cli-out.c
@@ -265,6 +265,91 @@ cli_ui_out::do_redirect (ui_file *outstream)
m_streams.pop_back ();
}

+void
+cli_ui_out::do_progress_start (const std::string &name, int should_print)
+{
+ struct ui_file *stream = m_streams.back ();
+ cli_progress_info meter;
+
+ meter.last_value = 0;
+ meter.name = name;
+ if (!stream->isatty ())
+ {
+ fprintf_unfiltered (stream, "%s...", meter.name.c_str ());
+ gdb_flush (stream);
+ meter.printing = WORKING;
+ }
+ else
+ {
+ /* Don't actually emit anything until the first call notify us
+ of progress. This makes it so a second progress message can
+ be started before the first one has been notified, without
+ messy output. */
+ meter.printing = should_print ? START : NO_PRINT;
+ }
+
+ m_meters.push_back (meter);
+}
+
+void
+cli_ui_out::do_progress_notify (double howmuch)
+{
+ struct ui_file *stream = m_streams.back ();
+ cli_progress_info &meter (m_meters.back ());
+
+ if (meter.printing == NO_PRINT)
+ return;
+
+ if (meter.printing == START)
+ {
+ fprintf_unfiltered (stream, "%s\n", meter.name.c_str ());
+ gdb_flush (stream);
+ meter.printing = WORKING;
+ }
+
+ if (stream->isatty ())
+ {
+ int i, max;
+ int width = get_chars_per_line () - 3;
+
+ max = width * howmuch;
+ fprintf_unfiltered (stream, "\r[");
+ for (i = 0; i < width; ++i)
+ fprintf_unfiltered (stream, i < max ? "#" : " ");
+ fprintf_unfiltered (stream, "]");
+ gdb_flush (stream);
+ }
+}
+
+void
+cli_ui_out::do_progress_end ()
+{
+ struct ui_file *stream = m_streams.back ();
+ cli_progress_info &meter = m_meters.back ();
+
+ if (meter.printing != NO_PRINT)
+ {
+ if (stream->isatty ())
+ {
+ int i;
+ int width = get_chars_per_line () - 3;
+
+ fprintf_unfiltered (stream, "\r");
+ for (i = 0; i < width + 2; ++i)
+ fprintf_unfiltered (stream, " ");
+ fprintf_unfiltered (stream, "\r");
+ gdb_flush (stream);
+ }
+ else
+ {
+ fprintf_unfiltered (stream, "done.\n");
+ gdb_flush (stream);
+ }
+ }
+
+ m_meters.pop_back ();
+}
+
/* local functions */

void
diff --git a/gdb/cli-out.h b/gdb/cli-out.h
index 84e957ca89..0c45b77d07 100644
--- a/gdb/cli-out.h
+++ b/gdb/cli-out.h
@@ -71,6 +71,10 @@ class cli_ui_out : public ui_out
virtual void do_flush () override;
virtual void do_redirect (struct ui_file *outstream) override;

+ virtual void do_progress_start (const std::string &, int) override;
+ virtual void do_progress_notify (double) override;
+ virtual void do_progress_end () override;
+
bool suppress_output ()
{ return m_suppress_output; }

@@ -80,6 +84,31 @@ class cli_ui_out : public ui_out

std::vector<ui_file *> m_streams;
bool m_suppress_output;
+
+ /* Represents the printing state of a progress meter. */
+ enum meter_state
+ {
+ /* Printing will start with the next output. */
+ START,
+ /* Printing has already started. */
+ WORKING,
+ /* Printing should not be done. */
+ NO_PRINT
+ };
+
+ /* The state of a recent progress meter. */
+ struct cli_progress_info
+ {
+ /* The current state. */
+ enum meter_state printing;
+ /* The name to print. */
+ std::string name;
+ /* The last notification value. */
+ double last_value;
+ };
+
+ /* Stack of progress meters. */
+ std::vector<cli_progress_info> m_meters;
};

extern cli_ui_out *cli_out_new (struct ui_file *stream);
diff --git a/gdb/mi/mi-out.h b/gdb/mi/mi-out.h
index de4b3e01a4..4be0decd14 100644
--- a/gdb/mi/mi-out.h
+++ b/gdb/mi/mi-out.h
@@ -83,6 +83,18 @@ class mi_ui_out : public ui_out
virtual bool do_is_mi_like_p () const override
{ return true; }

+ virtual void do_progress_start (const std::string &, int) override
+ {
+ }
+
+ virtual void do_progress_notify (double) override
+ {
+ }
+
+ virtual void do_progress_end () override
+ {
+ }
+
private:

void field_separator ();
diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index 9fc60614d6..9ae4eeb83a 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -275,6 +275,38 @@ class ui_out
escapes. */
virtual bool can_emit_style_escape () const = 0;

+ /* An object that starts and finishes a progress meter. */
+ class progress_meter
+ {
+ public:
+ progress_meter (struct ui_out *uiout, const std::string &name,
+ int should_print)
+ : m_uiout (uiout)
+ {
+ m_uiout->do_progress_start (name, should_print);
+ }
+
+ ~progress_meter ()
+ {
+ m_uiout->do_progress_notify (1.0);
+ m_uiout->do_progress_end ();
+ }
+
+ progress_meter (const progress_meter &) = delete;
+ progress_meter &operator= (const progress_meter &) = delete;
+
+ private:
+
+ struct ui_out *m_uiout;
+ };
+
+ /* Emit some progress corresponding to the most recently created
+ progress meter. HOWMUCH may range from 0.0 to 1.0. */
+ void progress (double howmuch)
+ {
+ do_progress_notify (howmuch);
+ }
+
protected:

virtual void do_table_begin (int nbrofcols, int nr_rows, const char *tblid)
@@ -309,6 +341,10 @@ class ui_out
virtual void do_flush () = 0;
virtual void do_redirect (struct ui_file *outstream) = 0;

+ virtual void do_progress_start (const std::string &, int) = 0;
+ virtual void do_progress_notify (double) = 0;
+ virtual void do_progress_end () = 0;
+
/* Set as not MI-like by default. It is overridden in subclasses if
necessary. */

diff --git a/gdb/utils.c b/gdb/utils.c
index 3226656e2c..b1d1694584 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1579,6 +1579,14 @@ gdb_flush (struct ui_file *stream)
stream->flush ();
}

+/* See utils.h. */
+
+int
+get_chars_per_line (void)
+{
+ return chars_per_line;
+}
+
/* Indicate that if the next sequence of characters overflows the line,
a newline should be inserted here rather than when it hits the end.
If INDENT is non-null, it is a string to be printed to indent the
diff --git a/gdb/utils.h b/gdb/utils.h
index a8c65ed817..5ae97b7469 100644
--- a/gdb/utils.h
+++ b/gdb/utils.h
@@ -364,6 +364,10 @@ extern void wrap_here (const char *);

extern void reinitialize_more_filter (void);

+/* Return the number of characters in a line. */
+
+extern int get_chars_per_line (void);
+
extern bool pagination_enabled;

extern struct ui_file **current_ui_gdb_stdout_ptr (void);