[lldb] Show progress events in the command line driver
authorJonas Devlieghere <jonas@devlieghere.com>
Sat, 5 Mar 2022 23:45:52 +0000 (15:45 -0800)
committerJonas Devlieghere <jonas@devlieghere.com>
Wed, 9 Mar 2022 02:24:26 +0000 (18:24 -0800)
This patch adds support for showing progress events when using lldb on
the command line. It spawns a separate thread that listens for progress
events and prints them to the debugger's output stream.

It's nothing fancy (yet), for now it just prints the progress message.
If we know the total number of items being processed, we prefix the
message with something like [1/100], similar to ninja's output.

This patch doesn't use any fancy terminal manipulation: it uses a simple
carriage return (\r) to bring the cursor to the front of the line and
vt100 escape codes to clear the (rest) of the line.

Differential revision: https://reviews.llvm.org/D120972

lldb/include/lldb/Core/Debugger.h
lldb/source/Core/CoreProperties.td
lldb/source/Core/Debugger.cpp
lldb/source/Symbol/LocateSymbolFile.cpp

index d801e2d..ec66459 100644 (file)
@@ -329,6 +329,8 @@ public:
 
   bool SetUseColor(bool use_color);
 
+  bool GetShowProgress() const;
+
   bool GetUseAutosuggestion() const;
 
   llvm::StringRef GetAutosuggestionAnsiPrefix() const;
@@ -439,6 +441,8 @@ protected:
                              uint64_t completed, uint64_t total,
                              llvm::Optional<lldb::user_id_t> debugger_id);
 
+  void PrintProgress(const Debugger::ProgressEventData &data);
+
   bool StartEventHandlerThread();
 
   void StopEventHandlerThread();
@@ -466,6 +470,8 @@ protected:
 
   void HandleThreadEvent(const lldb::EventSP &event_sp);
 
+  void HandleProgressEvent(const lldb::EventSP &event_sp);
+
   // Ensures two threads don't attempt to flush process output in parallel.
   std::mutex m_output_flush_mutex;
   void FlushProcessOutput(Process &process, bool flush_stdout,
@@ -514,6 +520,8 @@ protected:
   IOHandlerStack m_io_handler_stack;
   std::recursive_mutex m_io_handler_synchronous_mutex;
 
+  llvm::Optional<uint64_t> m_current_event_id;
+
   llvm::StringMap<std::weak_ptr<llvm::raw_ostream>> m_log_streams;
   std::shared_ptr<llvm::raw_ostream> m_log_callback_stream_sp;
   ConstString m_instance_name;
index 73539db..04e2f4e 100644 (file)
@@ -131,6 +131,10 @@ let Definition = "debugger" in {
     Global,
     DefaultTrue,
     Desc<"Whether to use Ansi color codes or not.">;
+  def ShowProgress: Property<"show-progress", "Boolean">,
+    Global,
+    DefaultTrue,
+    Desc<"Whether to show progress or not if the debugger's output is an interactive color-enabled terminal.">;
   def UseSourceCache: Property<"use-source-cache", "Boolean">,
     Global,
     DefaultTrue,
index eb0f6b5..4df7c98 100644 (file)
@@ -378,6 +378,12 @@ bool Debugger::SetUseColor(bool b) {
   return ret;
 }
 
+bool Debugger::GetShowProgress() const {
+  const uint32_t idx = ePropertyShowProgress;
+  return m_collection_sp->GetPropertyAtIndexAsBoolean(
+      nullptr, idx, g_debugger_properties[idx].default_uint_value != 0);
+}
+
 bool Debugger::GetUseAutosuggestion() const {
   const uint32_t idx = ePropertyShowAutosuggestion;
   return m_collection_sp->GetPropertyAtIndexAsBoolean(
@@ -1615,6 +1621,11 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
           CommandInterpreter::eBroadcastBitAsynchronousOutputData |
           CommandInterpreter::eBroadcastBitAsynchronousErrorData);
 
+  if (!m_broadcaster.EventTypeHasListeners(Debugger::eBroadcastBitProgress)) {
+    listener_sp->StartListeningForEvents(&m_broadcaster,
+                                         Debugger::eBroadcastBitProgress);
+  }
+
   // Let the thread that spawned us know that we have started up and that we
   // are now listening to all required events so no events get missed
   m_sync_broadcaster.BroadcastEvent(eBroadcastBitEventThreadIsListening);
@@ -1664,6 +1675,9 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
                 }
               }
             }
+          } else if (broadcaster == &m_broadcaster) {
+            if (event_type & Debugger::eBroadcastBitProgress)
+              HandleProgressEvent(event_sp);
           }
         }
 
@@ -1729,6 +1743,61 @@ lldb::thread_result_t Debugger::IOHandlerThread() {
   return {};
 }
 
+void Debugger::HandleProgressEvent(const lldb::EventSP &event_sp) {
+  auto *data =
+      Debugger::ProgressEventData::GetEventDataFromEvent(event_sp.get());
+  if (!data)
+    return;
+
+  // Do some bookkeeping for the current event, regardless of whether we're
+  // going to show the progress.
+  const uint64_t id = data->GetID();
+  if (m_current_event_id) {
+    if (id != *m_current_event_id)
+      return;
+    if (data->GetCompleted())
+      m_current_event_id.reset();
+  } else {
+    m_current_event_id = id;
+  }
+
+  // Decide whether we actually are going to show the progress. This decision
+  // can change between iterations so check it inside the loop.
+  if (!GetShowProgress())
+    return;
+
+  // Determine whether the current output file is an interactive terminal with
+  // color support. We assume that if we support ANSI escape codes we support
+  // vt100 escape codes.
+  File &output = GetOutputFile();
+  if (!output.GetIsInteractive() || !output.GetIsTerminalWithColors())
+    return;
+
+  if (data->GetCompleted()) {
+    // Clear the current line.
+    output.Printf("\33[2K\r");
+    return;
+  }
+
+  // Print over previous line, if any.
+  output.Printf("\r");
+
+  // Print the progress message.
+  std::string message = data->GetMessage();
+  if (data->GetTotal() != UINT64_MAX) {
+    output.Printf("[%llu/%llu] %s...", data->GetCompleted(), data->GetTotal(),
+                  message.c_str());
+  } else {
+    output.Printf("%s...", message.c_str());
+  }
+
+  // Clear until the end of the line.
+  output.Printf("\x1B[K");
+
+  // Flush the output.
+  output.Flush();
+}
+
 bool Debugger::HasIOHandlerThread() { return m_io_handler_thread.IsJoinable(); }
 
 bool Debugger::StartIOHandlerThread() {
index 4a7fbaa..589908e 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "lldb/Core/ModuleList.h"
 #include "lldb/Core/ModuleSpec.h"
+#include "lldb/Core/Progress.h"
 #include "lldb/Host/FileSystem.h"
 #include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Utility/ArchSpec.h"
@@ -262,6 +263,10 @@ Symbols::LocateExecutableSymbolFile(const ModuleSpec &module_spec,
       FileSystem::Instance().Exists(symbol_file_spec))
     return symbol_file_spec;
 
+  Progress progress(llvm::formatv(
+      "Locating external symbol file for {0}",
+      module_spec.GetFileSpec().GetFilename().AsCString("<Unknown>")));
+
   FileSpecList debug_file_search_paths = default_search_paths;
 
   // Add module directory.