Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] [lldb][Progress] Report progress when completing types from DWARF #91452

Merged
merged 3 commits into from
Jun 12, 2024

Conversation

Michael137
Copy link
Member

This is an attempt at displaying the work that's being done by LLDB when waiting on type-completion events, e.g., when running an expression. We add three new progress reports (across three commits):

  1. When moving decls between ASTs.
  2. When creating Clang ASTs from DWARF.
  3. When doing a FindTypes lookup on a debug map.

Some remaining questions:

  1. When do we want to destroy these Progress objects? Since the progress completed event is only sent on object destruction
  2. How expensive is it to do this reporting unconditionally?
  3. Are there more interesting places to report progress on?

@Michael137 Michael137 requested a review from JDevlieghere as a code owner May 8, 2024 09:58
@Michael137 Michael137 requested review from clayborg and removed request for JDevlieghere May 8, 2024 09:59
@llvmbot llvmbot added the lldb label May 8, 2024
@Michael137 Michael137 requested review from adrian-prantl and labath May 8, 2024 09:59
@llvmbot
Copy link
Member

llvmbot commented May 8, 2024

@llvm/pr-subscribers-lldb

Author: Michael Buch (Michael137)

Changes

This is an attempt at displaying the work that's being done by LLDB when waiting on type-completion events, e.g., when running an expression. We add three new progress reports (across three commits):

  1. When moving decls between ASTs.
  2. When creating Clang ASTs from DWARF.
  3. When doing a FindTypes lookup on a debug map.

Some remaining questions:

  1. When do we want to destroy these Progress objects? Since the progress completed event is only sent on object destruction
  2. How expensive is it to do this reporting unconditionally?
  3. Are there more interesting places to report progress on?

Full diff: https://github.com/llvm/llvm-project/pull/91452.diff

5 Files Affected:

  • (modified) lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp (+35)
  • (modified) lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.h (+4)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp (+19)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h (+4)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp (+7)
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp
index 30b50df79da90..cf9f1a2d47922 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "lldb/Core/Module.h"
+#include "lldb/Core/Progress.h"
 #include "lldb/Utility/LLDBAssert.h"
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
@@ -17,6 +18,7 @@
 #include "clang/AST/RecordLayout.h"
 #include "clang/Sema/Lookup.h"
 #include "clang/Sema/Sema.h"
+#include "llvm/ADT/ScopeExit.h"
 #include "llvm/Support/raw_ostream.h"
 
 #include "Plugins/ExpressionParser/Clang/ClangASTImporter.h"
@@ -1131,6 +1133,7 @@ ClangASTImporter::ASTImporterDelegate::ImportImpl(Decl *From) {
     LLDB_LOG(log, "[ClangASTImporter] Complete definition not found");
   }
 
+  UpdateImportProgress(From);
   return ASTImporter::ImportImpl(From);
 }
 
@@ -1411,3 +1414,35 @@ clang::Decl *
 ClangASTImporter::ASTImporterDelegate::GetOriginalDecl(clang::Decl *To) {
   return m_main.GetDeclOrigin(To).decl;
 }
+
+void ClangASTImporter::ASTImporterDelegate::UpdateImportProgress(
+    clang::Decl const *From) {
+  assert(From &&
+         "Trying to report import progress using an invalid clang::Decl.");
+
+  // If we can't determine the decl's name, we don't know what to
+  // update the progress bar with. So bail out.
+  auto const *ND = dyn_cast<NamedDecl>(From);
+  if (!ND)
+    return;
+
+  if (!m_import_progress_up) {
+    auto const *from_ast =
+        TypeSystemClang::GetASTContext(&From->getASTContext());
+    auto const *to_ast = TypeSystemClang::GetASTContext(&getToContext());
+
+    assert(from_ast && to_ast);
+
+    llvm::SmallVector<llvm::StringRef> from_name_parts;
+    llvm::SplitString(from_ast->getDisplayName(), from_name_parts, "/");
+    auto from_name = from_name_parts.back();
+
+    llvm::SmallVector<llvm::StringRef> to_name_parts;
+    llvm::SplitString(to_ast->getDisplayName(), to_name_parts, "/");
+    auto to_name = to_name_parts.back();
+    m_import_progress_up = std::make_unique<Progress>(
+        llvm::formatv("Importing '{0}' to '{1}'", from_name, to_name));
+  }
+
+  m_import_progress_up->Increment(1, ND->getNameAsString());
+}
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.h
index bc962e544d2f1..f666d0c0fc52c 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.h
@@ -22,6 +22,7 @@
 #include "clang/Basic/FileManager.h"
 #include "clang/Basic/FileSystemOptions.h"
 
+#include "lldb/Core/Progress.h"
 #include "lldb/Host/FileSystem.h"
 #include "lldb/Symbol/CompilerDeclContext.h"
 #include "lldb/Utility/LLDBAssert.h"
@@ -346,6 +347,8 @@ class ClangASTImporter {
     llvm::Expected<clang::Decl *> ImportImpl(clang::Decl *From) override;
 
   private:
+    void UpdateImportProgress(clang::Decl const *From);
+
     /// Decls we should ignore when mapping decls back to their original
     /// ASTContext. Used by the CxxModuleHandler to mark declarations that
     /// were created from the 'std' C++ module to prevent that the Importer
@@ -356,6 +359,7 @@ class ClangASTImporter {
     CxxModuleHandler *m_std_handler = nullptr;
     /// The currently attached listener.
     NewDeclListener *m_new_decl_listener = nullptr;
+    std::unique_ptr<lldb_private::Progress> m_import_progress_up = nullptr;
   };
 
   typedef std::shared_ptr<ASTImporterDelegate> ImporterDelegateSP;
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index f8101aba5c627..605428619d805 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -479,6 +479,8 @@ TypeSP DWARFASTParserClang::ParseTypeFromDWARF(const SymbolContext &sc,
     return nullptr;
   }
 
+  UpdateParsingProgress(attrs.name.AsCString("(anonymous)"));
+
   if (type_is_new_ptr)
     *type_is_new_ptr = true;
 
@@ -3952,3 +3954,20 @@ void DWARFASTParserClang::ParseRustVariantPart(
 
   layout_info.field_offsets.insert({inner_field, 0});
 }
+
+void DWARFASTParserClang::UpdateParsingProgress(std::string message) {
+  if (!m_parsing_progress_up) {
+    SymbolFile *dwarf = m_ast.GetSymbolFile();
+    if (!dwarf)
+      return;
+
+    auto *obj = dwarf->GetObjectFile();
+    if (!obj)
+      return;
+
+    m_parsing_progress_up = std::make_unique<Progress>(
+        "Parsing DWARF in " + obj->GetFileSpec().GetFilename().GetString());
+  }
+
+  m_parsing_progress_up->Increment(1, std::move(message));
+}
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
index 8d4af203bb287..aed7c07d3f607 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
@@ -24,6 +24,7 @@
 
 #include "Plugins/ExpressionParser/Clang/ClangASTImporter.h"
 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
+#include "lldb/Core/Progress.h"
 
 #include <optional>
 #include <vector>
@@ -135,6 +136,7 @@ class DWARFASTParserClang : public lldb_private::plugin::dwarf::DWARFASTParser {
   DeclContextToDIEMap m_decl_ctx_to_die;
   DIEToModuleMap m_die_to_module;
   std::unique_ptr<lldb_private::ClangASTImporter> m_clang_ast_importer_up;
+  std::unique_ptr<lldb_private::Progress> m_parsing_progress_up;
   /// @}
 
   clang::DeclContext *
@@ -414,6 +416,8 @@ class DWARFASTParserClang : public lldb_private::plugin::dwarf::DWARFASTParser {
                        lldb_private::CompilerType &class_clang_type,
                        const lldb::AccessType default_accesibility,
                        lldb_private::ClangASTImporter::LayoutInfo &layout_info);
+
+  void UpdateParsingProgress(std::string message);
 };
 
 /// Parsed form of all attributes that are relevant for type reconstruction.
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
index f066f13d51c5d..b20f2f6df210d 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
@@ -14,6 +14,7 @@
 #include "lldb/Core/Module.h"
 #include "lldb/Core/ModuleList.h"
 #include "lldb/Core/PluginManager.h"
+#include "lldb/Core/Progress.h"
 #include "lldb/Core/Section.h"
 #include "lldb/Host/FileSystem.h"
 #include "lldb/Utility/RangeMap.h"
@@ -1240,7 +1241,13 @@ TypeSP SymbolFileDWARFDebugMap::FindCompleteObjCDefinitionTypeForDIE(
 void SymbolFileDWARFDebugMap::FindTypes(const TypeQuery &query,
                                         TypeResults &results) {
   std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
+  Progress progress(
+      llvm::formatv("Searching for type '{0}'",
+                    query.GetTypeBasename().AsCString("<<UNKNOWN>>")));
   ForEachSymbolFile([&](SymbolFileDWARF *oso_dwarf) {
+    if (auto *obj = oso_dwarf->GetObjectFile())
+      progress.Increment(1, obj->GetFileSpec().GetPath());
+
     oso_dwarf->FindTypes(query, results);
     return results.Done(query) ? IterationAction::Stop
                                : IterationAction::Continue;

@Michael137
Copy link
Member Author

Michael137 commented May 8, 2024

Example of what the DIE parsing progress looks like:

Screen.Recording.2024-05-08.at.10.44.13.mov

The fact that the last progress event isn't cleared from the console is a problem. We'd have to decide at what point we want to destroy the Progress object.

Copy link
Collaborator

@adrian-prantl adrian-prantl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@@ -1240,7 +1241,13 @@ TypeSP SymbolFileDWARFDebugMap::FindCompleteObjCDefinitionTypeForDIE(
void SymbolFileDWARFDebugMap::FindTypes(const TypeQuery &query,
TypeResults &results) {
std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
Progress progress(
llvm::formatv("Searching for type '{0}'",
query.GetTypeBasename().AsCString("<<UNKNOWN>>")));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<anonymous>?

ForEachSymbolFile([&](SymbolFileDWARF *oso_dwarf) {
if (auto *obj = oso_dwarf->GetObjectFile())
progress.Increment(1, obj->GetFileSpec().GetPath());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this get rendered to?

It would be nice if it were:

Searching type "Foo" in "main.o"

but it almost looks like this would become

Searching for type "main.o"

?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably get rendered as Searching for type 'Foo': main.o if that filename is used as the details for Increment.

auto from_name = from_name_parts.back();

llvm::SmallVector<llvm::StringRef> to_name_parts;
llvm::SplitString(to_ast->getDisplayName(), to_name_parts, "/");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does an ast's DisplayName look like?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StringRef::rsplit is going to be more efficient for this.

Copy link
Member Author

@Michael137 Michael137 May 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does an ast's DisplayName look like?

It's one of three:

  • Expression ASTContext for '" + m_filename + "' (expression AST)
  • scratch ASTContext (target-wide AST)
  • a module name

@@ -1411,3 +1414,35 @@ clang::Decl *
ClangASTImporter::ASTImporterDelegate::GetOriginalDecl(clang::Decl *To) {
return m_main.GetDeclOrigin(To).decl;
}

void ClangASTImporter::ASTImporterDelegate::UpdateImportProgress(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertions in this code are going to blow up only in development builds. In release builds, you'll fall through the first assertion, and return because you try to cast nullptr to a NamedDecl (which will give you nullptr) and the function will fail silently. The second assertion will fail if from_ast or to_ast is nullptr, but the function doesn't return early there. It will call getDisplayName on from_ast and to_ast, one of which will crash anyway. Maybe we should consider an early return here too? Or maybe the function should return an llvm::Error?

Copy link
Member Author

@Michael137 Michael137 May 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These asserts should never ever actually occur unless something about TypeSystemClang gets redesigned, at which point the intention was that these would flag in development builds/tests. But definitely valid question. For the cases where a nullptr could occur I did try to be consistent with early returns. I usually try to express the preconditions of the function in terms of asserts, but in this case I don't have strong opinion, I think doing an early return would make sense here. I remember some time last year @DavidSpickett proposed an assert macro that would fire in debug builds but early-return in release. Can't find that atm, not sure if it ever landed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast nullptr to a NamedDecl (which will give you nullptr)

Actually, dyn_cast will abort if it gets a null pointer (dyn_cast_or_null is the version that forwards nullness) -- which makes this assert unnecessary. However, I think this is a very good example of an assert -- checking for things that "should not happen" without introducing overhead in the production codepath.

Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am somewhat worried about this slowing down the actual operations it is reporting progress on. I didn't really measure this, but intuitively, I'd expect that a one of these operations (parsing/importing one type) would be pretty fast, and that the whole process takes so long simply because performing a very large number of these ops.

Can you get some numbers on this? E.g., the number of events (per second?) that this generates, the timings of expression evaluation with/without the patch, or something like that?

@@ -1411,3 +1414,35 @@ clang::Decl *
ClangASTImporter::ASTImporterDelegate::GetOriginalDecl(clang::Decl *To) {
return m_main.GetDeclOrigin(To).decl;
}

void ClangASTImporter::ASTImporterDelegate::UpdateImportProgress(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast nullptr to a NamedDecl (which will give you nullptr)

Actually, dyn_cast will abort if it gets a null pointer (dyn_cast_or_null is the version that forwards nullness) -- which makes this assert unnecessary. However, I think this is a very good example of an assert -- checking for things that "should not happen" without introducing overhead in the production codepath.

@adrian-prantl
Copy link
Collaborator

I am somewhat worried about this slowing down the actual operations it is reporting progress on. I didn't really measure this, but intuitively, I'd expect that a one of these operations (parsing/importing one type) would be pretty fast, and that the whole process takes so long simply because performing a very large number of these ops.

Can you get some numbers on this? E.g., the number of events (per second?) that this generates, the timings of expression evaluation with/without the patch, or something like that?

My understanding was that the progress increment is designed to be really cheap (writing two pointers) and that it's up to presentation layer to decide a t what frequency to update the UI.

@JDevlieghere — is that perception correct?

@labath
Copy link
Collaborator

labath commented May 9, 2024

I am somewhat worried about this slowing down the actual operations it is reporting progress on. I didn't really measure this, but intuitively, I'd expect that a one of these operations (parsing/importing one type) would be pretty fast, and that the whole process takes so long simply because performing a very large number of these ops.
Can you get some numbers on this? E.g., the number of events (per second?) that this generates, the timings of expression evaluation with/without the patch, or something like that?

My understanding was that the progress increment is designed to be really cheap (writing two pointers) and that it's up to presentation layer to decide a t what frequency to update the UI.

Even if the actual publishing of the progress event was free, there's still appears to be quite a bit of code executed before we reach that point. For example, the "if sending progress" block in UpdateImportProgress is 15 lines long, it definitely looks like it can end up allocating memory, etc.

@JDevlieghere
Copy link
Member

My understanding was that the progress increment is designed to be really cheap (writing two pointers) and that it's up to presentation layer to decide a t what frequency to update the UI.

@JDevlieghere — is that perception correct?

You're correct about the presentation layer, but creating the progress reports isn't free and more recently we've added some bookkeeping to coalesce them. In the grand scheme of things they should be relatively cheap, but like timers I wouldn't put them in hot loops.

@adrian-prantl
Copy link
Collaborator

If this turns out to be necessary, one way to rate-limit them would be to have a nesting depth counter. Assuming that these imports happen recursively we could create only Progress objects for the top n layers.

@JDevlieghere
Copy link
Member

If this turns out to be necessary, one way to rate-limit them would be to have a nesting depth counter. Assuming that these imports happen recursively we could create only Progress objects for the top n layers.

Is the code that emits the progress event recursive too? The reason I ask is because on the command line, nested progress events will get shadowed. The same is true for coalesced progress events. I'm not sure how VSCode/DAP clients deal with this, so maybe they're shown there?

Anyway, if the code is recursive, we might need to do something like we did for Swift, with one top-level event and callback that updates the details.

Copy link
Collaborator

@clayborg clayborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many notifications happen during an expression? We want to make sure this doesn't slow down expression evaluation by any appreciable amount. If we spam these notifications, without having any throttling in place (I have an open patch for this I haven't been getting back to: #75769) I fear this will cause things to slow down a bit. I know IDEs are more sensitive to being blasted with progress updates.

@Michael137
Copy link
Member Author

I am somewhat worried about this slowing down the actual operations it is reporting progress on. I didn't really measure this, but intuitively, I'd expect that a one of these operations (parsing/importing one type) would be pretty fast, and that the whole process takes so long simply because performing a very large number of these ops.

Can you get some numbers on this? E.g., the number of events (per second?) that this generates, the timings of expression evaluation with/without the patch, or something like that?

I've been living on this patch for the past two weeks, debugging Clang/LLDB and haven't noticed a slowdown in the expression evaluator, but I'll try to follow-up with some concrete numbers.

Is the code that emits the progress event recursive too? The reason I ask is because on the command line, nested progress events will get shadowed. The same is true for coalesced progress events. I'm not sure how VSCode/DAP clients deal with this, so maybe they're shown there?
Anyway, if the code is recursive, we might need to do something like we did for Swift, with one top-level event and callback that updates the details.

Yes it is recursive, and some of the progress events do shadow each other. I'll take a look at what Swift does, thanks for the pointer!

@Michael137
Copy link
Member Author

Michael137 commented May 17, 2024

I attached to clang and printed an expression. That resulted in 16801 calls to Progress::Increment, all of which I'm guessing translate to event broadcasts. I collected some timing numbers:

scenario	        debuggee mean (s) Configuration
expr var	        clang++	15.268	DWARFv5 Baseline
expr var	        clang++	15.356	DWARFv5 Patched
method call	        clang++	15.239	DWARFv5 Baseline
method call	        clang++	15.224	DWARFv5 Patched
expr re-eval	        clang++	15.902	DWARFv5 Baseline
expr re-eval	        clang++	15.898	DWARFv5 Patched
two stops	        clang++	18.236	DWARFv5 Baseline
two stops	        clang++	18.344	DWARFv5 Patched
break only	        clang++	13.493	DWARFv5 Baseline
break only	        clang++	13.451	DWARFv5 Patched
frame var	        clang++	14.445	DWARFv5 Baseline
frame var	        clang++	14.438	DWARFv5 Patched
frame var then expr	clang++	18.097	DWARFv5 Baseline
frame var then expr	clang++	18.145	DWARFv5 Patched
expr var	        lldb	13.676	DWARFv5 Baseline
expr var	        lldb	13.69	DWARFv5 Patched
method call	        lldb	14.213	DWARFv5 Baseline
method call	        lldb	14.243	DWARFv5 Patched
expr re-eval	        lldb	13.863	DWARFv5 Baseline
expr re-eval	        lldb	13.91	DWARFv5 Patched
two stops	        lldb	19.335	DWARFv5 Baseline
two stops	        lldb	19.486	DWARFv5 Patched
break only	        lldb	12.526	DWARFv5 Baseline
break only	        lldb	12.53	DWARFv5 Patched
frame var	        lldb	15.468	DWARFv5 Baseline
frame var	        lldb	15.464	DWARFv5 Patched
frame var then expr	lldb	18.054	DWARFv5 Baseline
frame var then expr	lldb	18.096	DWARFv5 Patched

So across the various scenarios i tested the slowdown is somewhere between 10-90 ms on average (though in other cases it's within noise).

I'd expect that a one of these operations (parsing/importing one type) would be pretty fast, and that the whole process takes so long simply because performing a very large number of these ops.

@labath Mostly that's true. Occasionally these operations can take longer if we end up searching for types/functions across multiple object files (especially with swiftlang#8222). We could certainly consider some sort of rate limiting here. Or maybe find a better place to report progress on?

Anyway, if the code is recursive, we might need to do something like we did for Swift, with one top-level event and callback that updates the details.

@JDevlieghere @adrian-prantl are there plans to change the presentation layer to prevent this kind of shadowing in the future? Would be nice if all we needed to do was report progress, and not worry about other progress events in the debugger being in-flight

@JDevlieghere
Copy link
Member

@JDevlieghere @adrian-prantl are there plans to change the presentation layer to prevent this kind of shadowing in the future? Would be nice if all we needed to do was report progress, and not worry about other progress events in the debugger being in-flight

No, on the terminal it works that way by design. Unless you switch to something that takes full control of your screen (like curses) there's no good way to display multiple progress events at the same time and not doing the shadowing (i.e. letting more recent progress events through) defeats the purpose of highlighting long running operations.

@adrian-prantl
Copy link
Collaborator

No, on the terminal it works that way by design. Unless you switch to something that takes full control of your screen (like curses) there's no good way to display multiple progress events at the same time and not doing the shadowing (i.e. letting more recent progress events through) defeats the purpose of highlighting long running operations.

@JDevlieghere I know we discussed this in the past and it made sense to me, but I'm right now failing to connect the dots. Why couldn't the same single-line interface be used to print nested events something like this?

Evaluating expression > Type Checking > Importing "vector<int>" > Finding "begin" in Foo.o

@JDevlieghere
Copy link
Member

@JDevlieghere I know we discussed this in the past and it made sense to me, but I'm right now failing to connect the dots. Why couldn't the same single-line interface be used to print nested events something like this?

Evaluating expression > Type Checking > Importing "vector<int>" > Finding "begin" in Foo.o

Shadowing happens with unrelated events. The problem is that you don't want to show something like this:

Evaluating expression > Type Checking > Some unrelated background operation > Importing "vector<int>"

Or let's say you're doing some work in parallel:

Doing some work on thread 1 > Doing some work on thread 2 > Some nested work on thread 2 > Some nested work on thread 1

For a single event, you can update it, as we do for importing the Swift modules. For your example, today you can show something like this:

Evaluating expression: Type Checking
Evaluating expression: Importing "vector<int>"
Evaluating expression: Finding "begin" in Foo.o 

Compared to your approach, you lose out on the additional context that's not part of the title/category before the colon. On the other hand, that's how we aggregate events in the progress manager.

If we want to support nested related events, I think that wouldn't be too hard. An event could have a parent event ID and LLDB could append events that belong to the currently displayed event. But we'd still shadow unrelated events.

@labath
Copy link
Collaborator

labath commented May 20, 2024

I attached to clang and printed an expression. That resulted in 16801 calls to Progress::Increment, all of which I'm guessing translate to event broadcasts. I collected some timing numbers:

Thanks for doing this. What do these numbers include? Is it just the operation it self, or everything leading up to it as well. For example, is "expr var" just the timing of the "expr" command, or attach command as well. If the latter, then what's the baseline?

@Michael137
Copy link
Member Author

Sorry for the delayed response, just got back from vacation.

What do these numbers include? Is it just the operation it self, or everything leading up to it as well.
Thanks for doing this. What do these numbers include? Is it just the operation it self, or everything leading up to it as well. For example, is "expr var" just the timing of the "expr" command, or attach command as well.

The benchmarks include the attach command as well. E.g., expr var would be the time it took to attach/stop at a breakpoint in Clang and run expr. I'll post the baseline shortly

@Michael137
Copy link
Member Author

Michael137 commented May 29, 2024

@labath baseline for just attaching is approximately 3 s for LLDB and 2.5 s. Baselines for attaching and stopping at the breakpoint are in the break only rows of the table.

@Michael137
Copy link
Member Author

Michael137 commented May 29, 2024

Now that we don't search for the definition DIE when initially parsing a DIE, we could probably move this progress report to FindDefinitionTypeForDIE, which seems to be the most expensive part of the DWARFASTParser

@labath
Copy link
Collaborator

labath commented May 29, 2024

@labath baseline for just attaching is approximately 3 s for LLDB and 2.5 s. Baselines for attaching and stopping at the breakpoint are in the break only rows of the table.

Thanks. I think this resolves my concerns about this patch. If we do find a particularly expensive/hot place reporting progress events, we can always remove it.

Now that we don't search for the definition DIE when initially parsing a DIE, we could probably move this progress report to FindDefinitionTypeForDIE, which seems to be the most expensive part of the DWARFASTParser

SGTM

@Michael137 Michael137 force-pushed the lldb/expr-progress-events branch from 4fc71f1 to d9d0e0d Compare June 11, 2024 21:56
@Michael137
Copy link
Member Author

Michael137 commented Jun 11, 2024

Decided to go with something much simpler. We now report progress only in ParseStructureLikeDIE, when we search for the definition DIE. Should reduce the number of broadcasts to some extent, and once #92328 lands again, it would naturally move into FindDefinitionTypeForDIE. Also, we're no longer storing the Progress object for the entire lifetime of the DWARFASTParserClang, meaning we clear the message from the terminal by the time the expression finishes, and we are less likely to block other in-flight progress events elsewhere.

@clayborg
Copy link
Collaborator

This PR rate limits things. Might be worth trying this out here to see if it helps:

#75769

@@ -1758,6 +1759,11 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
}

if (attrs.is_forward_declaration) {
Progress progress(llvm::formatv(
"Parsing forward declaration {0}: {1}",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should either be "Resolving forward declaration" or "Parsing type". Otherwise it sounds like we take a long time to load a forward declaration itself.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@Michael137
Copy link
Member Author

This PR rate limits things. Might be worth trying this out here to see if it helps:

#75769

Will try this out, though I haven't found any major performance issues as a result of the progress events added here. Maybe for different workloads this could get more noticeable. Should be easy enough to adjust if it turns out this does cause problems.

@Michael137 Michael137 merged commit d32afb3 into llvm:main Jun 12, 2024
4 of 5 checks passed
@Michael137 Michael137 deleted the lldb/expr-progress-events branch June 12, 2024 09:33
@Michael137
Copy link
Member Author

Only buildkite test failure was TestConcurrentVFork, which seemed unrelated.

Michael137 added a commit to swiftlang/llvm-project that referenced this pull request Jun 15, 2024
…om DWARF (llvm#91452)

This is an attempt at displaying the work that's being done by LLDB when waiting on type-completion events, e.g., when running an expression. This patch adds a single new progress event for cases where we search for the definition DIE of a forward declaration, which can be an expensive operation in the presence of many object files.

(cherry picked from commit d32afb3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants