Skip to content

Commit e320392

Browse files
Jlalondjph-13
authored andcommitted
[LLDB-DAP] SBDebugger don't prefix title on progress updates (llvm#124648)
In my last DAP patch (llvm#123837), we piped the DAP update message into the update event. However, we had the title embedded into the update message. This makes sense for progress Start, but makes the update message look pretty wonky. ![image](https://github.com/user-attachments/assets/9f6083d1-fc50-455c-a1a7-a2f9bdaba22e) Instead, we only use the title when it's the first message, removing the duplicate title problem. ![image](https://github.com/user-attachments/assets/ee7aefd1-1852-46f7-94bc-84b8faef6dac)
1 parent bc5a8e2 commit e320392

File tree

4 files changed

+246
-32
lines changed

4 files changed

+246
-32
lines changed

lldb/bindings/interface/SBProgressDocstrings.i

+42-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,48 @@ The Progress class helps make sure that progress is correctly reported
1111
and will always send an initial progress update, updates when
1212
Progress::Increment() is called, and also will make sure that a progress
1313
completed update is reported even if the user doesn't explicitly cause one
14-
to be sent.") lldb::SBProgress;
14+
to be sent.
15+
16+
Progress can either be deterministic, incrementing up to a known total or non-deterministic
17+
with an unbounded total. Deterministic is better if you know the items of work in advance, but non-deterministic
18+
exposes a way to update a user during a long running process that work is taking place.
19+
20+
For all progresses the details provided in the constructor will be sent until an increment detail
21+
is provided. This detail will also continue to be broadcasted on any subsequent update that doesn't
22+
specify a new detail. Some implementations differ on throttling updates and this behavior differs primarily
23+
if the progress is deterministic or non-deterministic. For DAP, non-deterministic update messages have a higher
24+
throttling rate than deterministic ones.
25+
26+
Below are examples in Python for deterministic and non-deterministic progresses.
27+
28+
deterministic_progress1 = lldb.SBProgress('Deterministic Progress', 'Detail', 3, lldb.SBDebugger)
29+
for i in range(3):
30+
deterministic_progress1.Increment(1, f'Update {i}')
31+
# The call to Finalize() is a no-op as we already incremented the right amount of
32+
# times and caused the end event to be sent.
33+
deterministic_progress1.Finalize()
34+
35+
deterministic_progress2 = lldb.SBProgress('Deterministic Progress', 'Detail', 10, lldb.SBDebugger)
36+
for i in range(3):
37+
deterministic_progress2.Increment(1, f'Update {i}')
38+
# Cause the progress end event to be sent even if we didn't increment the right
39+
# number of times. Real world examples would be in a try-finally block to ensure
40+
# progress clean-up.
41+
deterministic_progress2.Finalize()
42+
43+
If you don't call Finalize() when the progress is not done, the progress object will eventually get
44+
garbage collected by the Python runtime, the end event will eventually get sent, but it is best not to
45+
rely on the garbage collection when using lldb.SBProgress.
46+
47+
Non-deterministic progresses behave the same, but omit the total in the constructor.
48+
49+
non_deterministic_progress = lldb.SBProgress('Non deterministic progress, 'Detail', lldb.SBDebugger)
50+
for i in range(10):
51+
non_deterministic_progress.Increment(1)
52+
# Explicitly send a progressEnd, otherwise this will be sent
53+
# when the python runtime cleans up this object.
54+
non_deterministic_progress.Finalize()
55+
") lldb::SBProgress;
1556

1657
%feature("docstring",
1758
"Finalize the SBProgress, which will cause a progress end event to be emitted. This

lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ def create_options(cls):
3737
)
3838

3939
parser.add_option(
40-
"--total", dest="total", help="Total to count up.", type="int"
40+
"--total",
41+
dest="total",
42+
help="Total items in this progress object. When this option is not specified, this will be an indeterminate progress.",
43+
type="int",
44+
default=None,
4145
)
4246

4347
parser.add_option(
@@ -47,6 +51,14 @@ def create_options(cls):
4751
type="int",
4852
)
4953

54+
parser.add_option(
55+
"--no-details",
56+
dest="no_details",
57+
help="Do not display details",
58+
action="store_true",
59+
default=False,
60+
)
61+
5062
return parser
5163

5264
def get_short_help(self):
@@ -68,12 +80,30 @@ def __call__(self, debugger, command, exe_ctx, result):
6880
return
6981

7082
total = cmd_options.total
71-
progress = lldb.SBProgress("Progress tester", "Detail", total, debugger)
83+
if total is None:
84+
progress = lldb.SBProgress(
85+
"Progress tester", "Initial Indeterminate Detail", debugger
86+
)
87+
else:
88+
progress = lldb.SBProgress(
89+
"Progress tester", "Initial Detail", total, debugger
90+
)
91+
92+
# Check to see if total is set to None to indicate an indeterminate progress
93+
# then default to 10 steps.
94+
if total is None:
95+
total = 10
7296

7397
for i in range(1, total):
74-
progress.Increment(1, f"Step {i}")
98+
if cmd_options.no_details:
99+
progress.Increment(1)
100+
else:
101+
progress.Increment(1, f"Step {i}")
75102
time.sleep(cmd_options.seconds)
76103

104+
# Not required for deterministic progress, but required for indeterminate progress.
105+
progress.Finalize()
106+
77107

78108
def __lldb_init_module(debugger, dict):
79109
# Register all classes that have a register_lldb_command method

lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py

+104-18
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,54 @@
44

55
from lldbsuite.test.decorators import *
66
from lldbsuite.test.lldbtest import *
7+
import json
78
import os
89
import time
910

1011
import lldbdap_testcase
1112

1213

1314
class TestDAP_progress(lldbdap_testcase.DAPTestCaseBase):
15+
def verify_progress_events(
16+
self,
17+
expected_title,
18+
expected_message=None,
19+
expected_not_in_message=None,
20+
only_verify_first_update=False,
21+
):
22+
self.dap_server.wait_for_event("progressEnd", 15)
23+
self.assertTrue(len(self.dap_server.progress_events) > 0)
24+
start_found = False
25+
update_found = False
26+
end_found = False
27+
for event in self.dap_server.progress_events:
28+
event_type = event["event"]
29+
if "progressStart" in event_type:
30+
title = event["body"]["title"]
31+
self.assertIn(expected_title, title)
32+
start_found = True
33+
if "progressUpdate" in event_type:
34+
message = event["body"]["message"]
35+
if only_verify_first_update and update_found:
36+
continue
37+
if expected_message is not None:
38+
self.assertIn(expected_message, message)
39+
if expected_not_in_message is not None:
40+
self.assertNotIn(expected_not_in_message, message)
41+
update_found = True
42+
if "progressEnd" in event_type:
43+
end_found = True
44+
45+
self.assertTrue(start_found)
46+
self.assertTrue(update_found)
47+
self.assertTrue(end_found)
48+
1449
@skipIfWindows
1550
def test_output(self):
1651
program = self.getBuildArtifact("a.out")
1752
self.build_and_launch(program)
1853
progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py")
19-
print(f"Progress emitter path: {progress_emitter}")
2054
source = "main.cpp"
21-
# Set breakpoint in the thread function so we can step the threads
2255
breakpoint_ids = self.set_source_breakpoints(
2356
source, [line_number(source, "// break here")]
2457
)
@@ -30,20 +63,73 @@ def test_output(self):
3063
"`test-progress --total 3 --seconds 1", context="repl"
3164
)
3265

33-
self.dap_server.wait_for_event("progressEnd", 15)
34-
# Expect at least a start, an update, and end event
35-
# However because the underlying Progress instance is an RAII object and we can't guaruntee
36-
# it's deterministic destruction in the python API, we verify just start and update
37-
# otherwise this test could be flakey.
38-
self.assertTrue(len(self.dap_server.progress_events) > 0)
39-
start_found = False
40-
update_found = False
41-
for event in self.dap_server.progress_events:
42-
event_type = event["event"]
43-
if "progressStart" in event_type:
44-
start_found = True
45-
if "progressUpdate" in event_type:
46-
update_found = True
66+
self.verify_progress_events(
67+
expected_title="Progress tester",
68+
expected_not_in_message="Progress tester",
69+
)
4770

48-
self.assertTrue(start_found)
49-
self.assertTrue(update_found)
71+
@skipIfWindows
72+
def test_output_nodetails(self):
73+
program = self.getBuildArtifact("a.out")
74+
self.build_and_launch(program)
75+
progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py")
76+
source = "main.cpp"
77+
breakpoint_ids = self.set_source_breakpoints(
78+
source, [line_number(source, "// break here")]
79+
)
80+
self.continue_to_breakpoints(breakpoint_ids)
81+
self.dap_server.request_evaluate(
82+
f"`command script import {progress_emitter}", context="repl"
83+
)
84+
self.dap_server.request_evaluate(
85+
"`test-progress --total 3 --seconds 1 --no-details", context="repl"
86+
)
87+
88+
self.verify_progress_events(
89+
expected_title="Progress tester",
90+
expected_message="Initial Detail",
91+
)
92+
93+
@skipIfWindows
94+
def test_output_indeterminate(self):
95+
program = self.getBuildArtifact("a.out")
96+
self.build_and_launch(program)
97+
progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py")
98+
source = "main.cpp"
99+
breakpoint_ids = self.set_source_breakpoints(
100+
source, [line_number(source, "// break here")]
101+
)
102+
self.continue_to_breakpoints(breakpoint_ids)
103+
self.dap_server.request_evaluate(
104+
f"`command script import {progress_emitter}", context="repl"
105+
)
106+
self.dap_server.request_evaluate("`test-progress --seconds 1", context="repl")
107+
108+
self.verify_progress_events(
109+
expected_title="Progress tester: Initial Indeterminate Detail",
110+
expected_message="Step 1",
111+
only_verify_first_update=True,
112+
)
113+
114+
@skipIfWindows
115+
def test_output_nodetails_indeterminate(self):
116+
program = self.getBuildArtifact("a.out")
117+
self.build_and_launch(program)
118+
progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py")
119+
source = "main.cpp"
120+
breakpoint_ids = self.set_source_breakpoints(
121+
source, [line_number(source, "// break here")]
122+
)
123+
self.dap_server.request_evaluate(
124+
f"`command script import {progress_emitter}", context="repl"
125+
)
126+
127+
self.dap_server.request_evaluate(
128+
"`test-progress --seconds 1 --no-details", context="repl"
129+
)
130+
131+
self.verify_progress_events(
132+
expected_title="Progress tester: Initial Indeterminate Detail",
133+
expected_message="Initial Indeterminate Detail",
134+
only_verify_first_update=True,
135+
)

lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp

+67-10
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,32 @@ using namespace lldb;
1818

1919
namespace lldb_dap {
2020

21-
static void ProgressEventThreadFunction(DAP &dap) {
22-
llvm::set_thread_name(dap.name + ".progress_handler");
21+
static std::string GetStringFromStructuredData(lldb::SBStructuredData &data,
22+
const char *key) {
23+
lldb::SBStructuredData keyValue = data.GetValueForKey(key);
24+
if (!keyValue)
25+
return std::string();
26+
27+
const size_t length = keyValue.GetStringValue(nullptr, 0);
28+
29+
if (length == 0)
30+
return std::string();
31+
32+
std::string str(length + 1, 0);
33+
keyValue.GetStringValue(&str[0], length + 1);
34+
return str;
35+
}
36+
37+
static uint64_t GetUintFromStructuredData(lldb::SBStructuredData &data,
38+
const char *key) {
39+
lldb::SBStructuredData keyValue = data.GetValueForKey(key);
40+
41+
if (!keyValue.IsValid())
42+
return 0;
43+
return keyValue.GetUnsignedIntegerValue();
44+
}
45+
46+
void ProgressEventThreadFunction(DAP &dap) {
2347
lldb::SBListener listener("lldb-dap.progress.listener");
2448
dap.debugger.GetBroadcaster().AddListener(
2549
listener, lldb::SBDebugger::eBroadcastBitProgress |
@@ -35,14 +59,47 @@ static void ProgressEventThreadFunction(DAP &dap) {
3559
done = true;
3660
}
3761
} else {
38-
uint64_t progress_id = 0;
39-
uint64_t completed = 0;
40-
uint64_t total = 0;
41-
bool is_debugger_specific = false;
42-
const char *message = lldb::SBDebugger::GetProgressFromEvent(
43-
event, progress_id, completed, total, is_debugger_specific);
44-
if (message)
45-
dap.SendProgressEvent(progress_id, message, completed, total);
62+
lldb::SBStructuredData data =
63+
lldb::SBDebugger::GetProgressDataFromEvent(event);
64+
65+
const uint64_t progress_id =
66+
GetUintFromStructuredData(data, "progress_id");
67+
const uint64_t completed = GetUintFromStructuredData(data, "completed");
68+
const uint64_t total = GetUintFromStructuredData(data, "total");
69+
const std::string details =
70+
GetStringFromStructuredData(data, "details");
71+
72+
if (completed == 0) {
73+
if (total == UINT64_MAX) {
74+
// This progress is non deterministic and won't get updated until it
75+
// is completed. Send the "message" which will be the combined title
76+
// and detail. The only other progress event for thus
77+
// non-deterministic progress will be the completed event So there
78+
// will be no need to update the detail.
79+
const std::string message =
80+
GetStringFromStructuredData(data, "message");
81+
dap.SendProgressEvent(progress_id, message.c_str(), completed,
82+
total);
83+
} else {
84+
// This progress is deterministic and will receive updates,
85+
// on the progress creation event VSCode will save the message in
86+
// the create packet and use that as the title, so we send just the
87+
// title in the progressCreate packet followed immediately by a
88+
// detail packet, if there is any detail.
89+
const std::string title =
90+
GetStringFromStructuredData(data, "title");
91+
dap.SendProgressEvent(progress_id, title.c_str(), completed, total);
92+
if (!details.empty())
93+
dap.SendProgressEvent(progress_id, details.c_str(), completed,
94+
total);
95+
}
96+
} else {
97+
// This progress event is either the end of the progress dialog, or an
98+
// update with possible detail. The "detail" string we send to VS Code
99+
// will be appended to the progress dialog's initial text from when it
100+
// was created.
101+
dap.SendProgressEvent(progress_id, details.c_str(), completed, total);
102+
}
46103
}
47104
}
48105
}

0 commit comments

Comments
 (0)