[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: 7bbd0fba98 ). 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
This commit is contained in:
Wanyi Ye 2022-09-10 11:27:09 -07:00
parent 2bbafe04fe
commit e3ccbae309
9 changed files with 176 additions and 3 deletions

View File

@ -369,7 +369,13 @@ class DebugCommunication(object):
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):

View File

@ -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

View File

@ -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')

View File

@ -0,0 +1,3 @@
int foo() {
return 12;
}

View File

@ -0,0 +1 @@
int foo();

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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) {