[Lldb-commits] [lldb] e3ccbae - [lldb-vscode] Send Statistics Dump in terminated event

Wanyi Ye via lldb-commits lldb-commits at lists.llvm.org
Thu Nov 3 18:16:53 PDT 2022


Author: Wanyi Ye
Date: 2022-11-03T18:15:38-07:00
New Revision: e3ccbae309273900a42e30b606c15c873d57f1ea

URL: https://github.com/llvm/llvm-project/commit/e3ccbae309273900a42e30b606c15c873d57f1ea
DIFF: https://github.com/llvm/llvm-project/commit/e3ccbae309273900a42e30b606c15c873d57f1ea.diff

LOG: [lldb-vscode] Send Statistics Dump in terminated event

This patch will gather debug info & breakpoint info from the statistics dump (from `(SBTarget.GetStatistics())` func) and send to DAP in terminated event.

The statistics content can be huge (especially the `modules`) and dumping in full JSON can create delay in the IDE's debugging UI. (For more details, please read: https://github.com/llvm/llvm-project/commit/7bbd0fba986c241162b77b7e424ad82bc7e17b41 ). Hence, we will filter out large contents before returning it in terminated event.

It will keep all the metadata fields (those starts with "total"). For large contents, it uses the opt-out strategy. Currently it only removes the "modules" field. This way every time a new top-level field being added, we will be able to capture them from DAP log without changing lldb-vscode.

The DAP terminated event should look like
```
{
  "event":"terminated",
  "seq":0,
  "statistics": {
    "memory": <JSON string>
    "targets": <JSON string>, // it's a JSON array, breakpoints info included in each target
    <metadata_key: value> // pairs
  },
  "type":"event"
}
```

All the info above will be append to statistics field in the terminated event

Test Plan

Debugged a simple hello world program from VSCode. Exit debug session in two ways: 1) run to program exit; 2) user initiated debug session end (quit debugging before program exit).
Check DAP log and see both debug sessions have statistics returned in terminated event.

Here's an example when debugging the test program:

```
{"event":"terminated","seq":0,"statistics":{"memory":"{\"strings\":{\"bytesTotal\":1843200,\"bytesUnused\":897741,\"bytesUsed\":945459}}","targets":"[{\"breakpoints\":[{\"details\":{\"Breakpoint\":{\"BKPTOptions\":{\"AutoContinue\":false,\"ConditionText\":\"\",\"EnabledState\":true,\"IgnoreCount\":0,\"OneShotState\":false},\"BKPTResolver\":{\"Options\":{\"NameMask\":[56],\"Offset\":0,\"SkipPrologue\":true,\"SymbolNames\":[\"foo\"]},\"Type\":\"SymbolName\"},\"Hardware\":false,\"Names\":[\"vscode\"],\"SearchFilter\":{\"Options\":{},\"Type\":\"Unconstrained\"}}},\"id\":1,\"internal\":false,\"numLocations\":1,\"numResolvedLocations\":1,\"resolveTime\":0.002232},{\"details\":{\"Breakpoint\":{\"BKPTOptions\":{\"AutoContinue\":false,\"ConditionText\":\"\",\"EnabledState\":true,\"IgnoreCount\":0,\"OneShotState\":false},\"BKPTResolver\":{\"Options\":{\"Column\":0,\"Exact\":false,\"FileName\":\"/data/users/wanyi/llvm-sand/external/llvm-project/lldb/test/API/tools/lldb-vscode/terminated-event/main.cpp\",\"Inlines\":true,\"LineNumber\":5,\"Offset\":0,\"SkipPrologue\":true},\"Type\":\"FileAndLine\"},\"Hardware\":false,\"Names\":[\"vscode\"],\"SearchFilter\":{\"Options\":{},\"Type\":\"Unconstrained\"}}},\"id\":2,\"internal\":false,\"numLocations\":0,\"numResolvedLocations\":0,\"resolveTime\":0.23203799999999999},{\"details\":{\"Breakpoint\":{\"BKPTOptions\":{\"AutoContinue\":false,\"ConditionText\":\"\",\"EnabledState\":true,\"IgnoreCount\":0,\"OneShotState\":false},\"BKPTResolver\":{\"Options\":{\"Language\":\"c\",\"NameMask\":[4,4,4,4,4,4],\"Offset\":0,\"SkipPrologue\":false,\"SymbolNames\":[\"_dl_debug_state\",\"rtld_db_dlactivity\",\"__dl_rtld_db_dlactivity\",\"r_debug_state\",\"_r_debug_state\",\"_rtld_debug_state\"]},\"Type\":\"SymbolName\"},\"Hardware\":false,\"SearchFilter\":{\"Options\":{\"ModuleList\":[\"/usr/lib64/ld-2.28.so\"]},\"Type\":\"Modules\"}}},\"id\":-1,\"internal\":true,\"kindDescription\":\"shared-library-event\",\"numLocations\":1,\"numResolvedLocations\":1,\"resolveTime\":0.00026699999999999998}],\"expressionEvaluation\":{\"failures\":0,\"successes\":0},\"firstStopTime\":0.087458974999999994,\"frameVariable\":{\"failures\":0,\"successes\":0},\"launchOrAttachTime\":0.052953161999999998,\"moduleIdentifiers\":[94554748126576,94554747837792,94554747149216,139800112130176,139800112161056,139800112206064,139800112340224,139800112509552,139800112236528],\"signals\":[{\"SIGSTOP\":1}],\"sourceMapDeduceCount\":0,\"stopCount\":8,\"targetCreateTime\":0.00057700000000000004,\"totalBreakpointResolveTime\":0.234537}]","totalDebugInfoByteSize":1668056,"totalDebugInfoEnabled":3,"totalDebugInfoIndexLoadedFromCache":0,"totalDebugInfoIndexSavedToCache":0,"totalDebugInfoIndexTime":0.027963000000000002,"totalDebugInfoParseTime":0.34354800000000002,"totalModuleCount":10,"totalModuleCountHasDebugInfo":3,"totalSymbolTableIndexTime":0.056050000000000003,"totalSymbolTableParseTime":0.23930000000000001,"totalSymbolTableStripped":0,"totalSymbolTablesLoadedFromCache":0,"totalSymbolTablesSavedToCache":0},"type":"event"}
```

Differential Revision: https://reviews.llvm.org/D137003

Added: 
    lldb/test/API/tools/lldb-vscode/terminated-event/Makefile
    lldb/test/API/tools/lldb-vscode/terminated-event/TestVSCode_terminatedEvent.py
    lldb/test/API/tools/lldb-vscode/terminated-event/foo.cpp
    lldb/test/API/tools/lldb-vscode/terminated-event/foo.h
    lldb/test/API/tools/lldb-vscode/terminated-event/main.cpp

Modified: 
    lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
    lldb/tools/lldb-vscode/JSONUtils.cpp
    lldb/tools/lldb-vscode/JSONUtils.h
    lldb/tools/lldb-vscode/lldb-vscode.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
index d6a6abca53e38..c2de4ad5c7d9a 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
@@ -369,7 +369,13 @@ def wait_for_stopped(self, timeout=None):
     def wait_for_exited(self):
         event_dict = self.wait_for_event('exited')
         if event_dict is None:
-            raise ValueError("didn't get stopped event")
+            raise ValueError("didn't get exited event")
+        return event_dict
+
+    def wait_for_terminated(self):
+        event_dict = self.wait_for_event('terminated')
+        if event_dict is None:
+            raise ValueError("didn't get terminated event")
         return event_dict
 
     def get_initialize_value(self, key):

diff  --git a/lldb/test/API/tools/lldb-vscode/terminated-event/Makefile b/lldb/test/API/tools/lldb-vscode/terminated-event/Makefile
new file mode 100644
index 0000000000000..b30baf48b972e
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/terminated-event/Makefile
@@ -0,0 +1,17 @@
+DYLIB_NAME := foo
+DYLIB_CXX_SOURCES := foo.cpp
+CXX_SOURCES := main.cpp
+
+LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)"
+USE_LIBDL :=1
+
+include Makefile.rules
+
+all: a.out.stripped
+
+a.out.stripped:
+	strip -o a.out.stripped a.out
+
+ifneq "$(CODESIGN)" ""
+	$(CODESIGN) -fs - a.out.stripped
+endif
\ No newline at end of file

diff  --git a/lldb/test/API/tools/lldb-vscode/terminated-event/TestVSCode_terminatedEvent.py b/lldb/test/API/tools/lldb-vscode/terminated-event/TestVSCode_terminatedEvent.py
new file mode 100644
index 0000000000000..bc516a0ed0e37
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/terminated-event/TestVSCode_terminatedEvent.py
@@ -0,0 +1,63 @@
+"""
+Test lldb-vscode terminated event
+"""
+
+import vscode
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+import lldbvscode_testcase
+import re
+import json
+
+class TestVSCode_terminatedEvent(lldbvscode_testcase.VSCodeTestCaseBase):
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_terminated_event(self):
+        '''
+            Terminated Event
+            Now contains the statistics of a debug session:
+            metatdata:
+                totalDebugInfoByteSize > 0
+                totalDebugInfoEnabled > 0
+                totalModuleCountHasDebugInfo > 0
+                ...
+            targetInfo:
+                totalBreakpointResolveTime > 0
+            breakpoints:
+                recognize function breakpoint
+                recognize source line breakpoint
+            It should contains the breakpoints info: function bp & source line bp
+        '''
+
+        program_basename = "a.out.stripped"
+        program = self.getBuildArtifact(program_basename)
+        self.build_and_launch(program)
+        # Set breakpoints
+        functions = ['foo']
+        breakpoint_ids = self.set_function_breakpoints(functions)
+        self.assertEquals(len(breakpoint_ids), len(functions), 'expect one breakpoint')
+        main_bp_line = line_number('main.cpp', '// main breakpoint 1')
+        breakpoint_ids.append(self.set_source_breakpoints('main.cpp', [main_bp_line]))
+
+        self.continue_to_breakpoints(breakpoint_ids)
+        self.continue_to_exit()
+
+        statistics = self.vscode.wait_for_terminated()['statistics']
+        self.assertTrue(statistics['totalDebugInfoByteSize'] > 0)
+        self.assertTrue(statistics['totalDebugInfoEnabled'] > 0)
+        self.assertTrue(statistics['totalModuleCountHasDebugInfo'] > 0)
+
+        self.assertIsNotNone(statistics['memory'])
+
+        # lldb-vscode debugs one target at a time
+        target = json.loads(statistics['targets'])[0]
+        self.assertTrue(target['totalBreakpointResolveTime'] > 0)
+
+        breakpoints = target['breakpoints']
+        self.assertIn('foo',
+                      breakpoints[0]['details']['Breakpoint']['BKPTResolver']['Options']['SymbolNames'],
+                      'foo is a symbol breakpoint')
+        self.assertTrue(breakpoints[1]['details']['Breakpoint']['BKPTResolver']['Options']['FileName'].endswith('main.cpp'),
+                        'target has source line breakpoint in main.cpp')

diff  --git a/lldb/test/API/tools/lldb-vscode/terminated-event/foo.cpp b/lldb/test/API/tools/lldb-vscode/terminated-event/foo.cpp
new file mode 100644
index 0000000000000..9dba85a9cccab
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/terminated-event/foo.cpp
@@ -0,0 +1,3 @@
+int foo() {
+    return 12;
+}

diff  --git a/lldb/test/API/tools/lldb-vscode/terminated-event/foo.h b/lldb/test/API/tools/lldb-vscode/terminated-event/foo.h
new file mode 100644
index 0000000000000..5d5f8f0c9e786
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/terminated-event/foo.h
@@ -0,0 +1 @@
+int foo();

diff  --git a/lldb/test/API/tools/lldb-vscode/terminated-event/main.cpp b/lldb/test/API/tools/lldb-vscode/terminated-event/main.cpp
new file mode 100644
index 0000000000000..cd984e560e0d2
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/terminated-event/main.cpp
@@ -0,0 +1,8 @@
+#include <iostream>
+#include "foo.h"
+
+int main(int argc, char const *argv[]) {
+  std::cout << "Hello World!" << std::endl; // main breakpoint 1
+  foo();
+  return 0;
+}

diff  --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp
index 39c24f8b23e39..bd8a9148c241f 100644
--- a/lldb/tools/lldb-vscode/JSONUtils.cpp
+++ b/lldb/tools/lldb-vscode/JSONUtils.cpp
@@ -19,6 +19,8 @@
 #include "lldb/API/SBBreakpoint.h"
 #include "lldb/API/SBBreakpointLocation.h"
 #include "lldb/API/SBDeclaration.h"
+#include "lldb/API/SBStringList.h"
+#include "lldb/API/SBStructuredData.h"
 #include "lldb/API/SBValue.h"
 #include "lldb/Host/PosixApi.h"
 
@@ -1140,6 +1142,73 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
   return reverse_request;
 }
 
+// Keep all the top level items from the statistics dump, except for the
+// "modules" array. It can be huge and cause delay
+// Array and dictionary value will return as <key, JSON string> pairs
+void FilterAndGetValueForKey(const lldb::SBStructuredData data, const char *key,
+                             llvm::json::Object &out) {
+  lldb::SBStructuredData value = data.GetValueForKey(key);
+  std::string key_utf8 = llvm::json::fixUTF8(key);
+  if (strcmp(key, "modules") == 0)
+    return;
+  switch (value.GetType()) {
+  case lldb::eStructuredDataTypeFloat:
+    out.try_emplace(key_utf8, value.GetFloatValue());
+    break;
+  case lldb::eStructuredDataTypeInteger:
+    out.try_emplace(key_utf8, value.GetIntegerValue());
+    break;
+  case lldb::eStructuredDataTypeArray: {
+    lldb::SBStream contents;
+    value.GetAsJSON(contents);
+    EmplaceSafeString(out, key, contents.GetData());
+  } break;
+  case lldb::eStructuredDataTypeBoolean:
+    out.try_emplace(key_utf8, value.GetBooleanValue());
+    break;
+  case lldb::eStructuredDataTypeString: {
+    // Get the string size before reading
+    const size_t str_length = value.GetStringValue(nullptr, 0);
+    std::string str(str_length + 1, 0);
+    value.GetStringValue(&str[0], str_length);
+    EmplaceSafeString(out, key, str);
+  } break;
+  case lldb::eStructuredDataTypeDictionary: {
+    lldb::SBStream contents;
+    value.GetAsJSON(contents);
+    EmplaceSafeString(out, key, contents.GetData());
+  } break;
+  case lldb::eStructuredDataTypeNull:
+  case lldb::eStructuredDataTypeGeneric:
+  case lldb::eStructuredDataTypeInvalid:
+    break;
+  }
+}
+
+void addStatistic(llvm::json::Object &event) {
+  lldb::SBStructuredData statistics = g_vsc.target.GetStatistics();
+  bool is_dictionary =
+      statistics.GetType() == lldb::eStructuredDataTypeDictionary;
+  if (!is_dictionary)
+    return;
+  llvm::json::Object stats_body;
+
+  lldb::SBStringList keys;
+  if (!statistics.GetKeys(keys))
+    return;
+  for (size_t i = 0; i < keys.GetSize(); i++) {
+    const char *key = keys.GetStringAtIndex(i);
+    FilterAndGetValueForKey(statistics, key, stats_body);
+  }
+  event.try_emplace("statistics", std::move(stats_body));
+}
+
+llvm::json::Object CreateTerminatedEventObject() {
+  llvm::json::Object event(CreateEventObject("terminated"));
+  addStatistic(event);
+  return event;
+}
+
 std::string JSONToString(const llvm::json::Value &json) {
   std::string data;
   llvm::raw_string_ostream os(data);

diff  --git a/lldb/tools/lldb-vscode/JSONUtils.h b/lldb/tools/lldb-vscode/JSONUtils.h
index bb81b88895938..c812ec87beab0 100644
--- a/lldb/tools/lldb-vscode/JSONUtils.h
+++ b/lldb/tools/lldb-vscode/JSONUtils.h
@@ -485,6 +485,12 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
                                   llvm::StringRef debug_adaptor_path,
                                   llvm::StringRef comm_file);
 
+/// Create a "Terminated" JSON object that contains statistics
+///
+/// \return
+///     A body JSON object with debug info and breakpoint info
+llvm::json::Object CreateTerminatedEventObject();
+
 /// Convert a given JSON object to a string.
 std::string JSONToString(const llvm::json::Value &json);
 

diff  --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp
index 1c6f9c829c388..21d2bc2229043 100644
--- a/lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -204,7 +204,7 @@ void SendTerminatedEvent() {
     g_vsc.sent_terminated_event = true;
     g_vsc.RunTerminateCommands();
     // Send a "terminated" event
-    llvm::json::Object event(CreateEventObject("terminated"));
+    llvm::json::Object event(CreateTerminatedEventObject());
     g_vsc.SendJSON(llvm::json::Value(std::move(event)));
   }
 }
@@ -2949,7 +2949,7 @@ void request_variables(const llvm::json::Object &request) {
       const uint32_t addr_size = g_vsc.target.GetProcess().GetAddressByteSize();
       lldb::SBValue reg_set = g_vsc.variables.registers.GetValueAtIndex(0);
       const uint32_t num_regs = reg_set.GetNumChildren();
-      for (uint32_t reg_idx=0; reg_idx<num_regs; ++reg_idx) {
+      for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) {
         lldb::SBValue reg = reg_set.GetChildAtIndex(reg_idx);
         const lldb::Format format = reg.GetFormat();
         if (format == lldb::eFormatDefault || format == lldb::eFormatHex) {


        


More information about the lldb-commits mailing list