From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 808D0385380A for ; Wed, 26 Jan 2022 00:58:39 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 808D0385380A Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-474-8oA-NzWLPq68vSw314IfUA-1; Tue, 25 Jan 2022 19:58:35 -0500 X-MC-Unique: 8oA-NzWLPq68vSw314IfUA-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id A21441083F61; Wed, 26 Jan 2022 00:58:34 +0000 (UTC) Received: from fedora.redhat.com (unknown [10.22.16.221]) by smtp.corp.redhat.com (Postfix) with ESMTP id 988CC5F91D; Wed, 26 Jan 2022 00:58:33 +0000 (UTC) From: Aaron Merey To: gdb-patches@sourceware.org Cc: patrick@monnerat.net, tom@tromey.com, Aaron Merey Subject: [PATCH v2] gdb/debuginfod: Rework progress updates Date: Tue, 25 Jan 2022 19:58:17 -0500 Message-Id: <20220126005817.56356-1-amerey@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII" X-Spam-Status: No, score=-13.2 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 26 Jan 2022 00:58:42 -0000 For more information regarding v1 see https://sourceware.org/pipermail/gdb-patches/2022-January/185034.html Previously debuginfod would use a progress bar to indicate the progress of each download: Downloading 1.23 MB separate debug info for /lib/libxyz.so.1... [############ ] This format assumed that an accurate transfer size would always become available. Progress updates would not start printing until the size could be displayed. However there are cases where the total size of the transfer is not available. In this case gdb would appear to hang while the transfer occured. Additionally there are cases where the transfer size was reported to gdb as -1. This resulted in progress updates indicating that a file of size -0.00 MB is downloading. This patch reworks progress updates to account for cases where the transfer size is not known. It also ensures that progress is promptly reported and it provides ways to update progress output when new information becomes available. When the transfer size is not known, a progress throbber now spins in front of the description of the download in order to indicate that the transfer is ongoing. The throbber consists of the characters -\|/ cycling at a rate of one tick per second. Downloading separate debug info for /lib/libxyz.so.1 When the transfer size is known, the percentage of the transfer that has completed will be displayed in front of the description of the download. (34%) Downloading 1.23 MB separate debug info for /lib/libxyz.so.1 Progress output is written on a single line that is overwritten with whitespace and rewritten to for each update or tick of the throbber. This facilitates seamlessly switching from a throbber to a percentage indicator part way through the transfer. To reduce visual noise, when a transfer completes without an error the line of progress output is again overwritten with whitespace. This prevents many lines of progress output from accumulating when many debuginfo files are downloaded. In case a user would like to see a persistant record of the files gdb aquired through debuginfod, an additional 'set debuginfod verbose N' setting has been added. If N is at least 2, then the following will be printed for each file that is downloaded or aquired from a local debuginfod cache: Retrieved 1.23 MB separate debug info for /lib/libxyz.so.1 When --interpreter=mi or the output stream is not a tty, progress updates consist of the following printed at the start of each download: Downloading separate debug info for /lib/libxyz.so.1... No further progress updating will occur in this case. --- gdb/cli-out.c | 149 +++++++++++++++++++++++++++++---------- gdb/cli-out.h | 30 +++----- gdb/debuginfod-support.c | 140 +++++++++++++++++++++++++++--------- gdb/mi/mi-out.c | 33 +++++++++ gdb/mi/mi-out.h | 20 ++++-- gdb/ui-out.h | 62 ++++++++++++---- 6 files changed, 328 insertions(+), 106 deletions(-) diff --git a/gdb/cli-out.c b/gdb/cli-out.c index 5ff645b4a83..9cc7e456583 100644 --- a/gdb/cli-out.c +++ b/gdb/cli-out.c @@ -265,10 +265,17 @@ cli_ui_out::do_redirect (ui_file *outstream) m_streams.pop_back (); } -/* The cli_ui_out::do_progress_* functions result in the following: - - printed for tty, SHOULD_PRINT == true: - +/* The cli_ui_out::do_progress_{start, notify} functions result in + the following: + + - printed for tty, SHOULD_PRINT == true + - next state == PERCENT: + <(XX%) NAME\r> + - next state == SPIN: + <-\|/ NAME\r> + - next state == BAR: + - printed for tty, SHOULD_PRINT == false: <> - printed for not-a-tty: @@ -280,15 +287,14 @@ void cli_ui_out::do_progress_start (const std::string &name, bool should_print) { struct ui_file *stream = m_streams.back (); - cli_progress_info meter; + cli_progress_info info; - meter.last_value = 0; - meter.name = name; + info.name = name; if (!stream->isatty ()) { - fprintf_unfiltered (stream, "%s...", meter.name.c_str ()); + fprintf_unfiltered (stream, "%s...\n", info.name.c_str ()); gdb_flush (stream); - meter.printing = WORKING; + info.state = progress_update::WORKING; } else { @@ -296,74 +302,141 @@ cli_ui_out::do_progress_start (const std::string &name, bool should_print) 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; + info.state = should_print ? progress_update::START + : progress_update::NO_PRINT; } - m_meters.push_back (std::move (meter)); + m_progress_info.push_back (std::move (info)); } +/* Pick a reasonable limit for the progress update length. */ +#define MAX_CHARS_PER_LINE 4096 + void -cli_ui_out::do_progress_notify (double howmuch) +cli_ui_out::do_progress_notify (double howmuch, + progress_update::state next_state) { struct ui_file *stream = m_streams.back (); - cli_progress_info &meter (m_meters.back ()); + cli_progress_info &info (m_progress_info.back ()); - if (meter.printing == NO_PRINT) + if (info.state == progress_update::NO_PRINT) return; - if (meter.printing == START) + int chars_per_line = get_chars_per_line (); + if (chars_per_line > MAX_CHARS_PER_LINE) + chars_per_line = MAX_CHARS_PER_LINE; + + if (info.state == progress_update::START) { - fprintf_unfiltered (stream, "%s\n", meter.name.c_str ()); + fprintf_unfiltered (stream, "%s", info.name.c_str ()); + if (chars_per_line <= 0) + fprintf_unfiltered (stream, "...\n"); gdb_flush (stream); - meter.printing = WORKING; + info.state = progress_update::WORKING; } - if (meter.printing == WORKING && howmuch >= 1.0) + if (chars_per_line <= 0) + return; + + if (info.state == progress_update::WORKING && howmuch >= 1.0) return; if (!stream->isatty ()) return; - int chars_per_line = get_chars_per_line (); - if (chars_per_line > 0) + if (next_state == progress_update::PERCENT) + { + fprintf_unfiltered (stream, "\r(%2.0f%%) %s", + howmuch * 100, info.name.c_str ()); + gdb_flush (stream); + info.state = progress_update::PERCENT; + } + else if (next_state == progress_update::SPIN) + { + using namespace std::chrono; + seconds diff = duration_cast + (steady_clock::now () - info.last_update); + + /* Advance the spinner no faster than 1 tick per second. */ + if (diff.count () >= 1.0) + { + static int spin = 0; + + fprintf_unfiltered (stream, "\r%c %s", "-\\|/"[spin++ % 4], + info.name.c_str ()); + gdb_flush (stream); + info.last_update = steady_clock::now (); + } + info.state = progress_update::SPIN; + } + else if (next_state == progress_update::BAR) { int i, max; int width = chars_per_line - 3; - max = width * howmuch; + + if (info.state == progress_update::SPIN + || info.state == progress_update::PERCENT) + { + /* Ensure the progress bar prints on its own line so that + progress updates don't overwrite name. */ + fprintf_unfiltered (stream, "\r%s\n", info.name.c_str ()); + gdb_flush (stream); + } + fprintf_unfiltered (stream, "\r["); + for (i = 0; i < width; ++i) fprintf_unfiltered (stream, i < max ? "#" : " "); fprintf_unfiltered (stream, "]"); gdb_flush (stream); - meter.printing = PROGRESS; + info.state = progress_update::BAR; } + + return; +} + +/* Set NAME as the new description of the most recent progress update. */ + +void +cli_ui_out::update_progress_name (const std::string &name) +{ + cli_progress_info &info = m_progress_info.back (); + info.name = name; } +cli_ui_out::progress_update::state +cli_ui_out::get_progress_state () +{ + cli_progress_info &info = m_progress_info.back (); + return info.state; +} + +/* Clear the current line of the most recent progress update. */ + void cli_ui_out::do_progress_end () { struct ui_file *stream = m_streams.back (); - cli_progress_info &meter = m_meters.back (); + m_progress_info.pop_back (); if (!stream->isatty ()) - { - fprintf_unfiltered (stream, "\n"); - gdb_flush (stream); - } - else if (meter.printing == PROGRESS) - { - int i; - int width = get_chars_per_line () - 3; + return; - fprintf_unfiltered (stream, "\r"); - for (i = 0; i < width + 2; ++i) - fprintf_unfiltered (stream, " "); - fprintf_unfiltered (stream, "\r"); - gdb_flush (stream); - } + int chars_per_line = get_chars_per_line (); + if (chars_per_line <= 0 + || chars_per_line > MAX_CHARS_PER_LINE) + chars_per_line = MAX_CHARS_PER_LINE; + + 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"); - m_meters.pop_back (); + gdb_flush (stream); } /* local functions */ diff --git a/gdb/cli-out.h b/gdb/cli-out.h index 4af5524495a..ee287161cf0 100644 --- a/gdb/cli-out.h +++ b/gdb/cli-out.h @@ -21,6 +21,7 @@ #define CLI_OUT_H #include "ui-out.h" +#include #include class cli_ui_out : public ui_out @@ -72,8 +73,10 @@ class cli_ui_out : public ui_out virtual void do_redirect (struct ui_file *outstream) override; virtual void do_progress_start (const std::string &, bool) override; - virtual void do_progress_notify (double) override; + virtual void do_progress_notify (double, progress_update::state) override; virtual void do_progress_end () override; + virtual void update_progress_name (const std::string &) override; + virtual progress_update::state get_progress_state () override; bool suppress_output () { return m_suppress_output; } @@ -85,32 +88,19 @@ class cli_ui_out : public ui_out std::vector 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, - /* Progress printing has already started. */ - PROGRESS, - /* Printing should not be done. */ - NO_PRINT - }; - - /* The state of a recent progress meter. */ + /* The state of a recent progress update. */ struct cli_progress_info { /* The current state. */ - enum meter_state printing; + progress_update::state state; /* The name to print. */ std::string name; - /* The last notification value. */ - double last_value; + /* Time of last spinner update. */ + std::chrono::steady_clock::time_point last_update; }; - /* Stack of progress meters. */ - std::vector m_meters; + /* Stack of progress info. */ + std::vector m_progress_info; }; extern cli_ui_out *cli_out_new (struct ui_file *stream); diff --git a/gdb/debuginfod-support.c b/gdb/debuginfod-support.c index 56d8e7781c5..20cf6688ccc 100644 --- a/gdb/debuginfod-support.c +++ b/gdb/debuginfod-support.c @@ -23,6 +23,7 @@ #include "gdbsupport/gdb_optional.h" #include "cli/cli-cmds.h" #include "cli/cli-style.h" +#include "cli-out.h" #include "target.h" /* Set/show debuginfod commands. */ @@ -76,13 +77,13 @@ debuginfod_debuginfo_query (const unsigned char *build_id, struct user_data { - user_data (const char *desc, const char *fname) - : desc (desc), fname (fname) + user_data (const char *desc, string_file &styled_fname) + : desc (desc), styled_fname (styled_fname) { } const char * const desc; - const char * const fname; - gdb::optional meter; + string_file & styled_fname; + gdb::optional progress; }; /* Deleter for a debuginfod_client. */ @@ -98,6 +99,25 @@ struct debuginfod_client_deleter using debuginfod_client_up = std::unique_ptr; +/* Convert SIZE into a unit suitable for use with progress updates. + SIZE should in given in bytes and will be converted into KB or MB. + UNIT will be set to "KB" or "MB" accordingly. */ + +static void +get_size_and_unit (double *size, const char **unit) +{ + *size /= 1024; + + /* If size is greater than 0.01 MB then set unit to MB. */ + if (*size > 10.24) + { + *size /= 1024; + *unit = "MB"; + } + else + *unit = "KB"; +} + static int progressfn (debuginfod_client *c, long cur, long total) { @@ -106,31 +126,57 @@ progressfn (debuginfod_client *c, long cur, long total) if (check_quit_flag ()) { - printf_filtered ("Cancelling download of %s %ps...\n", - data->desc, - styled_string (file_name_style.style (), data->fname)); + if (data->progress.has_value ()) + data->progress.reset (); + + printf_filtered ("Cancelled download of %s %s\n", + data->desc, data->styled_fname.c_str ()); return 1; } - if (total == 0) + if (debuginfod_verbose == 0 + || (data->progress.has_value () + && data->progress->get_state () == ui_out::progress_update::WORKING)) return 0; - if (!data->meter.has_value ()) + /* Print progress update. Include the transfer size if available. */ + if (total > 0) { - float size_in_mb = 1.0f * total / (1024 * 1024); - string_file styled_filename (current_uiout->can_emit_style_escape ()); - fprintf_styled (&styled_filename, - file_name_style.style (), - "%s", - data->fname); - std::string message - = string_printf ("Downloading %.2f MB %s %s", size_in_mb, data->desc, - styled_filename.c_str()); - data->meter.emplace (current_uiout, message, 1); + /* Transfer size is known. */ + if (!data->progress.has_value () + || data->progress->get_state () != ui_out::progress_update::PERCENT) + { + double size = (double)total; + const char *unit = ""; + + get_size_and_unit (&size, &unit); + std::string message = string_printf ("Downloading %.2f %s %s %s", + size, unit, data->desc, + data->styled_fname.c_str ()); + + if (!data->progress.has_value ()) + data->progress.emplace (current_uiout, message, 1); + else + data->progress->update_name (message); + } + + double percent = (double)cur / (double)total; + if (percent >= 0.0 || percent <= 100.0) + { + current_uiout->update_progress_percent (percent); + return 0; + } } - current_uiout->progress ((double)cur / (double)total); + if (!data->progress.has_value () + || data->progress->get_state () != ui_out::progress_update::SPIN) + { + std::string message = string_printf ("Downloading %s %s", data->desc, + data->styled_fname.c_str ()); + data->progress.emplace (current_uiout, message, 1); + } + current_uiout->update_progress_spin (); return 0; } @@ -186,6 +232,40 @@ debuginfod_is_enabled () return true; } +/* Print the result of the most recent attempted download. */ + +static void +print_outcome (user_data &data, int fd) +{ + /* Clears the current line of progress output. */ + if (data.progress.has_value ()) + data.progress.reset (); + + if (debuginfod_verbose > 1 && fd >= 0) + { + struct stat s; + + if (fstat (fd, &s) == 0) + { + double size = (double)s.st_size; + const char *unit = ""; + + get_size_and_unit (&size, &unit); + printf_filtered (_("Retrieved %.02f %s %s %s\n"), size, unit, + data.desc, data.styled_fname.c_str ()); + } + else + warning (_("Retrieved %s %s but size cannot be read: %s\n"), + data.desc, data.styled_fname.c_str (), + safe_strerror (errno)); + } + else if (debuginfod_verbose > 0 && fd < 0 && fd != -ENOENT) + printf_filtered (_("Download failed: %s. " \ + "Continuing without %s %s.\n"), + safe_strerror (-fd), data.desc, + data.styled_fname.c_str ()); +} + /* See debuginfod-support.h */ scoped_fd @@ -202,7 +282,9 @@ debuginfod_source_query (const unsigned char *build_id, if (c == nullptr) return scoped_fd (-ENOMEM); - user_data data ("source file", srcpath); + string_file styled_srcpath (current_uiout->can_emit_style_escape ()); + fprintf_styled (&styled_srcpath, file_name_style.style (), "%s", srcpath); + user_data data ("source file", styled_srcpath); debuginfod_set_user_data (c, &data); gdb::optional term_state; @@ -218,11 +300,7 @@ debuginfod_source_query (const unsigned char *build_id, srcpath, nullptr)); debuginfod_set_user_data (c, nullptr); - - if (debuginfod_verbose > 0 && fd.get () < 0 && fd.get () != -ENOENT) - printf_filtered (_("Download failed: %s. Continuing without source file %ps.\n"), - safe_strerror (-fd.get ()), - styled_string (file_name_style.style (), srcpath)); + print_outcome (data, fd.get ()); if (fd.get () >= 0) *destname = make_unique_xstrdup (srcpath); @@ -247,7 +325,9 @@ debuginfod_debuginfo_query (const unsigned char *build_id, return scoped_fd (-ENOMEM); char *dname = nullptr; - user_data data ("separate debug info for", filename); + string_file styled_filename (current_uiout->can_emit_style_escape ()); + fprintf_styled (&styled_filename, file_name_style.style (), "%s", filename); + user_data data ("separate debug info for", styled_filename); debuginfod_set_user_data (c, &data); gdb::optional term_state; @@ -260,11 +340,7 @@ debuginfod_debuginfo_query (const unsigned char *build_id, scoped_fd fd (debuginfod_find_debuginfo (c, build_id, build_id_len, &dname)); debuginfod_set_user_data (c, nullptr); - - if (debuginfod_verbose > 0 && fd.get () < 0 && fd.get () != -ENOENT) - printf_filtered (_("Download failed: %s. Continuing without debug info for %ps.\n"), - safe_strerror (-fd.get ()), - styled_string (file_name_style.style (), filename)); + print_outcome (data, fd.get ()); if (fd.get () >= 0) destname->reset (dname); diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c index 20c6f0f9194..ad89d7400f8 100644 --- a/gdb/mi/mi-out.c +++ b/gdb/mi/mi-out.c @@ -258,6 +258,39 @@ mi_ui_out::main_stream () return (string_file *) m_streams.back (); } +/* Indicate that a task described by NAME is in progress: + + - SHOULD_PRINT == true: + + - SHOULD_PRINT == false: + <> +*/ + +void +mi_ui_out::do_progress_start (const std::string &name, bool should_print) +{ + struct ui_file *stream = gdb_stdout; + mi_progress_info info; + + if (should_print) + { + fprintf_unfiltered (stream, "%s...\n", name.c_str ()); + gdb_flush (stream); + } + info.state = progress_update::WORKING; + m_progress_info.push_back (std::move (info)); +} + +/* Get the state of the most recent progress update. */ + +mi_ui_out::progress_update::state +mi_ui_out::get_progress_state () +{ + mi_progress_info &info = m_progress_info.back (); + return info.state; +} + /* Clear the buffer. */ void diff --git a/gdb/mi/mi-out.h b/gdb/mi/mi-out.h index 1b7fa96a182..d915b35f633 100644 --- a/gdb/mi/mi-out.h +++ b/gdb/mi/mi-out.h @@ -25,7 +25,6 @@ struct ui_out; struct ui_file; - class mi_ui_out : public ui_out { public: @@ -83,15 +82,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 &, bool) override + virtual void do_progress_start (const std::string &, bool) override; + virtual progress_update::state get_progress_state () override; + + virtual void do_progress_notify (double, progress_update::state) override { } - virtual void do_progress_notify (double) override + virtual void do_progress_end () override { } - virtual void do_progress_end () override + virtual void update_progress_name (const std::string &) override { } @@ -101,6 +103,16 @@ class mi_ui_out : public ui_out void open (const char *name, ui_out_type type); void close (ui_out_type type); + /* The state of a recent progress_update. */ + struct mi_progress_info + { + /* The current state. */ + progress_update::state state; + }; + + /* Stack of progress info. */ + std::vector m_progress_info; + /* Convenience method that returns the MI out's string stream cast to its appropriate type. Assumes/asserts that output was not redirected. */ diff --git a/gdb/ui-out.h b/gdb/ui-out.h index 05312150c21..4c490c11b82 100644 --- a/gdb/ui-out.h +++ b/gdb/ui-out.h @@ -280,26 +280,52 @@ class ui_out escapes. */ virtual bool can_emit_style_escape () const = 0; - /* An object that starts and finishes a progress meter. */ - class progress_meter + /* An object that starts and finishes displaying progress updates. */ + class progress_update { public: + /* Represents the printing state of a progress update. */ + enum state + { + /* Printing will start with the next update. */ + START, + /* Printing has already started. */ + WORKING, + /* Progress bar printing has already started. */ + BAR, + /* Spinner printing has already started. */ + SPIN, + /* Percent printing has already started. */ + PERCENT, + /* Printing should not be done. */ + NO_PRINT + }; + /* SHOULD_PRINT indicates whether something should be printed for a tty. */ - progress_meter (struct ui_out *uiout, const std::string &name, - bool should_print) + progress_update (struct ui_out *uiout, const std::string &name, + bool should_print) : m_uiout (uiout) { m_uiout->do_progress_start (name, should_print); } - ~progress_meter () + ~progress_update () { - 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; + void update_name (std::string &name) + { + m_uiout->update_progress_name (name); + } + + state get_state () + { + return m_uiout->get_progress_state (); + } + + progress_update (const progress_update &) = delete; + progress_update &operator= (const progress_update &) = delete; private: @@ -307,10 +333,20 @@ class ui_out }; /* Emit some progress corresponding to the most recently created - progress meter. HOWMUCH may range from 0.0 to 1.0. */ - void progress (double howmuch) + progress_update object. */ + void update_progress_bar (double howmuch) + { + do_progress_notify (howmuch, progress_update::BAR); + } + + void update_progress_percent (double howmuch) + { + do_progress_notify (howmuch, progress_update::PERCENT); + } + + void update_progress_spin () { - do_progress_notify (howmuch); + do_progress_notify (0, progress_update::SPIN); } protected: @@ -348,8 +384,10 @@ class ui_out virtual void do_redirect (struct ui_file *outstream) = 0; virtual void do_progress_start (const std::string &, bool) = 0; - virtual void do_progress_notify (double) = 0; + virtual void do_progress_notify (double, progress_update::state) = 0; virtual void do_progress_end () = 0; + virtual void update_progress_name (const std::string &) = 0; + virtual progress_update::state get_progress_state () = 0; /* Set as not MI-like by default. It is overridden in subclasses if necessary. */ -- 2.34.1