clayborg updated this revision to Diff 376421.
clayborg added a comment.
Changes:
- Removed "target metrics" and took over "statistics dump"
- Hooked old statistics into for expression evaluation and frame variable
success and fails into this patch
- No more textual output for "statistics dump", it is now JSON.
- "statistics enable" and "statistics disable" are still valid commands, but
they don't enable or disable anything since all stats currently gathered are
very cheap to gather:
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D110804/new/
https://reviews.llvm.org/D110804
Files:
lldb/include/lldb/Breakpoint/Breakpoint.h
lldb/include/lldb/Breakpoint/BreakpointLocation.h
lldb/include/lldb/Core/FileSpecList.h
lldb/include/lldb/Core/Module.h
lldb/include/lldb/Core/Section.h
lldb/include/lldb/Symbol/SymbolContext.h
lldb/include/lldb/Symbol/SymbolFile.h
lldb/include/lldb/Target/PathMappingList.h
lldb/include/lldb/Target/Process.h
lldb/include/lldb/Target/Statistics.h
lldb/include/lldb/Target/Target.h
lldb/include/lldb/Utility/ElapsedTime.h
lldb/include/lldb/lldb-forward.h
lldb/packages/Python/lldbsuite/test/lldbtest.py
lldb/source/API/SBTarget.cpp
lldb/source/Breakpoint/Breakpoint.cpp
lldb/source/Breakpoint/BreakpointLocation.cpp
lldb/source/Commands/CommandObjectExpression.cpp
lldb/source/Commands/CommandObjectFrame.cpp
lldb/source/Commands/CommandObjectStats.cpp
lldb/source/Commands/Options.td
lldb/source/Core/FileSpecList.cpp
lldb/source/Core/Module.cpp
lldb/source/Core/Section.cpp
lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
lldb/source/Plugins/ObjectFile/JIT/ObjectFileJIT.cpp
lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp
lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h
lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h
lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp
lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h
lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp
lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.h
lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.cpp
lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.h
lldb/source/Symbol/SymbolContext.cpp
lldb/source/Symbol/Symtab.cpp
lldb/source/Target/CMakeLists.txt
lldb/source/Target/PathMappingList.cpp
lldb/source/Target/Process.cpp
lldb/source/Target/Statistics.cpp
lldb/source/Target/Target.cpp
lldb/test/API/commands/statistics/basic/TestStats.py
lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py
Index: lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py
===================================================================
--- lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py
+++ lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py
@@ -10,6 +10,8 @@
class TestStatsAPI(TestBase):
mydir = TestBase.compute_mydir(__file__)
+ NO_DEBUG_INFO_TESTCASE = True
+
def test_stats_api(self):
self.build()
exe = self.getBuildArtifact("a.out")
@@ -26,9 +28,18 @@
stats = target.GetStatistics()
stream = lldb.SBStream()
res = stats.GetAsJSON(stream)
- stats_json = sorted(json.loads(stream.GetData()))
- self.assertEqual(len(stats_json), 4)
- self.assertIn("Number of expr evaluation failures", stats_json)
- self.assertIn("Number of expr evaluation successes", stats_json)
- self.assertIn("Number of frame var failures", stats_json)
- self.assertIn("Number of frame var successes", stats_json)
+ stats_json = json.loads(stream.GetData())
+ self.assertEqual('expressionEvaluation' in stats_json, True,
+ 'Make sure the "expressionEvaluation" key in in target.GetStatistics()')
+ self.assertEqual('frameVariable' in stats_json, True,
+ 'Make sure the "frameVariable" key in in target.GetStatistics()')
+ expressionEvaluation = stats_json['expressionEvaluation']
+ self.assertEqual('successes' in expressionEvaluation, True,
+ 'Make sure the "successes" key in in "expressionEvaluation" dictionary"')
+ self.assertEqual('failures' in expressionEvaluation, True,
+ 'Make sure the "failures" key in in "expressionEvaluation" dictionary"')
+ frameVariable = stats_json['frameVariable']
+ self.assertEqual('successes' in frameVariable, True,
+ 'Make sure the "successes" key in in "frameVariable" dictionary"')
+ self.assertEqual('failures' in frameVariable, True,
+ 'Make sure the "failures" key in in "frameVariable" dictionary"')
Index: lldb/test/API/commands/statistics/basic/TestStats.py
===================================================================
--- lldb/test/API/commands/statistics/basic/TestStats.py
+++ lldb/test/API/commands/statistics/basic/TestStats.py
@@ -1,4 +1,5 @@
import lldb
+import json
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
@@ -7,22 +8,85 @@
mydir = TestBase.compute_mydir(__file__)
- def test(self):
+ def setUp(self):
+ TestBase.setUp(self)
self.build()
- lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.c"))
- self.expect("statistics disable", substrs=['need to enable statistics before disabling'], error=True)
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def test_enable_disable(self):
+ """
+ Test "statistics disable" and "statistics enable". These don't do
+ anything anymore for cheap to gather statistics. In the future if
+ statistics are expensive to gather, we can enable the feature inside
+ of LLDB and test that enabling and disabling stops expesive information
+ from being gathered.
+ """
+ target = self.createTestTarget()
- # 'expression' should change the statistics.
+ self.expect("statistics disable", substrs=['need to enable statistics before disabling'], error=True)
self.expect("statistics enable")
self.expect("statistics enable", substrs=['already enabled'], error=True)
- self.expect("expr patatino", substrs=['27'])
self.expect("statistics disable")
- self.expect("statistics dump", substrs=['expr evaluation successes : 1\n',
- 'expr evaluation failures : 0\n'])
+ self.expect("statistics disable", substrs=['need to enable statistics before disabling'], error=True)
- self.expect("statistics enable")
- # Doesn't parse.
+ def verify_key_in_dict(self, key, d, description):
+ self.assertEqual(key in d, True,
+ 'make sure key "%s" is in dictionary %s' % (key, description))
+
+ def verify_key_not_in_dict(self, key, d, description):
+ self.assertEqual(key in d, False,
+ 'make sure key "%s" is in dictionary %s' % (key, description))
+
+ def verify_keys(self, dict, description, keys_exist, keys_missing=None):
+ """
+ Verify that all keys in "keys_exist" list are top level items in
+ "dict", and that all keys in "keys_missing" do not exist as top
+ level items in "dict".
+ """
+ if keys_exist:
+ for key in keys_exist:
+ self.verify_key_in_dict(key, dict, description)
+ if keys_missing:
+ for key in keys_missing:
+ self.verify_key_not_in_dict(key, dict, description)
+
+ def verify_success_fail_count(self, stats, key, num_successes, num_fails):
+ self.verify_key_in_dict(key, stats, 'stats["%s"]' % (key))
+ success_fail_dict = stats[key]
+ self.assertEqual(success_fail_dict['successes'], num_successes,
+ 'make sure success count')
+ self.assertEqual(success_fail_dict['failures'], num_fails,
+ 'make sure success count')
+
+ def get_stats(self, options=None, log_path=None):
+ """
+ Get the output of the "statistics dump" with optional extra options
+ and return the JSON as a python dictionary.
+ """
+ if log_path is not None:
+ f = open(log_path, 'w')
+ else:
+ f = None
+ return_obj = lldb.SBCommandReturnObject()
+ command = "statistics dump "
+ if options is not None:
+ command += options
+ if f is not None:
+ f.write('(lldb) %s' % (command))
+ self.ci.HandleCommand(command, return_obj, False)
+ metrics_json = return_obj.GetOutput()
+ if f:
+ f.write(metrics_json)
+ return json.loads(metrics_json)
+
+ def test_expressions_frame_var_counts(self):
+ lldbutil.run_to_source_breakpoint(self, "// break here",
+ lldb.SBFileSpec("main.c"))
+
+ self.expect("expr patatino", substrs=['27'])
+ stats = self.get_stats()
+ self.verify_success_fail_count(stats, 'expressionEvaluation', 1, 0)
self.expect("expr doesnt_exist", error=True,
substrs=["undeclared identifier 'doesnt_exist'"])
# Doesn't successfully execute.
@@ -30,17 +94,367 @@
# Interpret an integer as an array with 3 elements is also a failure.
self.expect("expr -Z 3 -- 1", error=True,
substrs=["expression cannot be used with --element-count"])
- self.expect("statistics disable")
# We should have gotten 3 new failures and the previous success.
- self.expect("statistics dump", substrs=['expr evaluation successes : 1\n',
- 'expr evaluation failures : 3\n'])
-
- # 'frame var' with disabled statistics shouldn't change stats.
- self.expect("frame var", substrs=['27'])
+ stats = self.get_stats()
+ self.verify_success_fail_count(stats, 'expressionEvaluation', 1, 3)
self.expect("statistics enable")
# 'frame var' with enabled statistics will change stats.
self.expect("frame var", substrs=['27'])
- self.expect("statistics disable")
- self.expect("statistics dump", substrs=['frame var successes : 1\n',
- 'frame var failures : 0\n'])
+ stats = self.get_stats()
+ self.verify_success_fail_count(stats, 'frameVariable', 1, 0)
+
+ def find_module_in_metrics(self, path, stats):
+ modules = stats['modules']
+ for module in modules:
+ if module['path'] == path:
+ return module
+ return None
+
+ def test_default_no_run(self):
+ """Test "statistics dump" without running the target.
+
+ When we don't run the target, we expect to not see any 'firstStopTime'
+ or 'launchOrAttachTime' top level keys that measure the launch or
+ attach of the target.
+
+ Output expected to be something like:
+
+ (lldb) statistics dump
+ {
+ "targetCreateTime": 0.26566899599999999,
+ "totalBreakpointResolveTime": 0.0031409419999999999,
+ "totalDebugInfoIndexTime": 0,
+ "totalDebugInfoParseTime": 0,
+ "totalDebugInfoSize": 2193,
+ "totalSymbolTableIndexTime": 0.056834488000000009,
+ "totalSymbolTableParseTime": 0.093979421999999979
+ }
+
+ """
+ target = self.createTestTarget()
+ stats = self.get_stats(log_path="/tmp/test_default_no_run.txt")
+ keys_exist = [
+ 'expressionEvaluation',
+ 'frameVariable',
+ 'targetCreateTime',
+ 'totalBreakpointResolveTime',
+ 'totalDebugInfoIndexTime',
+ 'totalDebugInfoParseTime',
+ 'totalDebugInfoSize',
+ 'totalSymbolTableIndexTime',
+ 'totalSymbolTableParseTime',
+ ]
+ keys_missing = [
+ 'firstStopTime',
+ 'launchOrAttachTime',
+ 'breakpoints',
+ 'modules',
+ 'settings'
+ ]
+ self.verify_keys(stats, '"stats"', keys_exist, keys_missing)
+ self.assertGreater(stats['targetCreateTime'], 0.0)
+ self.assertEqual(stats['totalBreakpointResolveTime'], 0.0)
+ self.assertGreater(stats['totalDebugInfoSize'], 0)
+ self.assertGreater(stats['totalSymbolTableIndexTime'], 0.0)
+ self.assertGreater(stats['totalSymbolTableParseTime'], 0.0)
+
+ def test_default_with_run(self):
+ """Test "statistics dump" when running the target to a breakpoint.
+
+ When we run the target, we expect to see 'launchOrAttachTime' and
+ 'firstStopTime' top level keys.
+
+ Output expected to be something like:
+
+ (lldb) statistics dump
+ {
+ "targetCreateTime": 0.26566899599999999,
+ "totalBreakpointResolveTime": 0.0031409419999999999,
+ "totalDebugInfoIndexTime": 0,
+ "totalDebugInfoParseTime": 0,
+ "totalDebugInfoSize": 2193,
+ "totalSymbolTableIndexTime": 0.056834488000000009,
+ "totalSymbolTableParseTime": 0.093979421999999979
+ }
+
+ """
+ target = self.createTestTarget()
+ lldbutil.run_to_source_breakpoint(self, "// break here",
+ lldb.SBFileSpec("main.c"))
+ stats = self.get_stats(log_path="/tmp/test_default_no_run.txt")
+ keys_exist = [
+ 'expressionEvaluation',
+ 'firstStopTime',
+ 'frameVariable',
+ 'launchOrAttachTime',
+ 'targetCreateTime',
+ 'totalBreakpointResolveTime',
+ 'totalDebugInfoIndexTime',
+ 'totalDebugInfoParseTime',
+ 'totalDebugInfoSize',
+ 'totalSymbolTableIndexTime',
+ 'totalSymbolTableParseTime',
+ ]
+ keys_missing = [
+ 'breakpoints',
+ 'modules',
+ 'settings'
+ ]
+ self.verify_keys(stats, '"stats"', keys_exist, keys_missing)
+ self.assertGreater(stats['firstStopTime'], 0.0)
+ self.assertGreater(stats['launchOrAttachTime'], 0.0)
+ self.assertGreater(stats['targetCreateTime'], 0.0)
+ self.assertGreater(stats['totalBreakpointResolveTime'], 0.0)
+ self.assertGreater(stats['totalDebugInfoSize'], 0)
+ self.assertGreater(stats['totalSymbolTableIndexTime'], 0.0)
+ self.assertGreater(stats['totalSymbolTableParseTime'], 0.0)
+
+ def test_modules(self):
+ """Test "statistics dump --modules"
+
+ Output expected to be something like:
+
+ (lldb) statistics dump --modules
+ {
+ "targetCreateTime": 0.26566899599999999,
+ "totalBreakpointResolveTime": 0.0031409419999999999,
+ "totalDebugInfoIndexTime": 0,
+ "totalDebugInfoParseTime": 0.00037288300000000001,
+ "totalDebugInfoSize": 2193,
+ "totalSymbolTableIndexTime": 0.056834488000000009,
+ "totalSymbolTableParseTime": 0.093979421999999979,
+ "modules": [
+ {
+ "debugInfoIndexTime": 0,
+ "debugInfoParseTime": 0,
+ "debugInfoSize": 1139,
+ "path": "/.../TestStats.test_modules/a.out",
+ "symbolTableIndexTime": 2.3816e-05,
+ "symbolTableParseTime": 0.000163747,
+ "triple": "x86_64-apple-macosx11.0.0",
+ "uuid": "10531B95-4DF4-3FFE-9D51-549DD17435E2"
+ },
+ {
+ "debugInfoIndexTime": 0,
+ "debugInfoParseTime": 0,
+ "debugInfoSize": 1054,
+ "path": "/.../TestStats.test_modules/libload_a.dylib",
+ "symbolTableIndexTime": 2.7852999999999999e-05,
+ "symbolTableParseTime": 0.000110246,
+ "triple": "x86_64-apple-macosx11.0.0",
+ "uuid": "F0974CDE-309A-3837-9593-385ABDC93803"
+ },
+ ]
+ }
+
+ """
+ exe = self.getBuildArtifact("a.out")
+ target = self.createTestTarget(file_path=exe)
+ stats = self.get_stats(options="--modules",
+ log_path="/tmp/test_default_no_run.txt")
+ keys_exist = [
+ 'targetCreateTime',
+ 'totalBreakpointResolveTime',
+ 'totalDebugInfoIndexTime',
+ 'totalDebugInfoParseTime',
+ 'totalDebugInfoSize',
+ 'totalSymbolTableIndexTime',
+ 'totalSymbolTableParseTime',
+ 'modules',
+ ]
+ keys_missing = [
+ 'breakpoints',
+ 'settings'
+ ]
+ self.verify_keys(stats, '"stats"', keys_exist, keys_missing)
+ exe_module = self.find_module_in_metrics(exe, stats)
+ module_keys = [
+ 'debugInfoIndexTime',
+ 'debugInfoParseTime',
+ 'debugInfoSize',
+ 'path',
+ 'symbolTableIndexTime',
+ 'symbolTableParseTime',
+ 'triple',
+ 'uuid',
+ ]
+ self.assertNotEqual(exe_module, None)
+ self.verify_keys(exe_module, 'module dict for "%s"' % (exe),
+ module_keys)
+
+ def test_breakpoints(self):
+ """Test "statistics dump --breakpoints"
+
+ Output expected to be something like:
+
+ (lldb) statistics dump --breakpoints
+ {
+ "targetCreateTime": 0.26566899599999999,
+ "totalBreakpointResolveTime": 0.0031409419999999999,
+ "totalDebugInfoIndexTime": 0,
+ "totalDebugInfoParseTime": 0.00037288300000000001,
+ "totalDebugInfoSize": 2193,
+ "totalSymbolTableIndexTime": 0.056834488000000009,
+ "totalSymbolTableParseTime": 0.093979421999999979
+ "breakpoints": [
+ {
+ "details": {...}.
+ "id": 1,
+ "resolveTime": 0.0029114369999999998
+ },
+ ]
+ }
+
+ """
+ target = self.createTestTarget()
+ self.runCmd("b main.cpp:7")
+ self.runCmd("b a_function")
+ stats = self.get_stats(options="--breakpoints")
+ keys_exist = [
+ 'targetCreateTime',
+ 'totalBreakpointResolveTime',
+ 'totalDebugInfoIndexTime',
+ 'totalDebugInfoParseTime',
+ 'totalDebugInfoSize',
+ 'totalSymbolTableIndexTime',
+ 'totalSymbolTableParseTime',
+ 'breakpoints',
+ ]
+ keys_missing = [
+ 'modules',
+ 'settings'
+ ]
+ self.verify_keys(stats, '"stats"', keys_exist, keys_missing)
+ self.assertGreater(stats['totalBreakpointResolveTime'], 0.0)
+ self.assertGreater(stats['totalDebugInfoParseTime'], 0.0)
+ breakpoints = stats['breakpoints']
+ bp_keys_exist = [
+ 'details',
+ 'id',
+ 'resolveTime'
+ ]
+ bk_keys_missing = [
+ 'locations'
+ ]
+ for breakpoint in breakpoints:
+ self.verify_keys(breakpoint, 'stats["breakpoints"]',
+ bp_keys_exist, bk_keys_missing)
+
+ def test_locations(self):
+ """Test "statistics dump --locations"
+
+ Output expected to be something like:
+
+ (lldb) statistics dump --locations
+ {
+ "targetCreateTime": 0.26566899599999999,
+ "totalBreakpointResolveTime": 0.0031409419999999999,
+ "totalDebugInfoIndexTime": 0,
+ "totalDebugInfoParseTime": 0.00037288300000000001,
+ "totalDebugInfoSize": 2193,
+ "totalSymbolTableIndexTime": 0.056834488000000009,
+ "totalSymbolTableParseTime": 0.093979421999999979
+ "breakpoints": [
+ {
+ "details": {...}.
+ "id": 1,
+ "locations": [...],
+ "resolveTime": 0.0029114369999999998
+ },
+ ]
+ }
+
+ """
+ target = self.createTestTarget()
+ self.runCmd("b main.cpp:7")
+ self.runCmd("b a_function")
+ stats = self.get_stats(options="--locations")
+ keys_exist = [
+ 'targetCreateTime',
+ 'totalBreakpointResolveTime',
+ 'totalDebugInfoIndexTime',
+ 'totalDebugInfoParseTime',
+ 'totalDebugInfoSize',
+ 'totalSymbolTableIndexTime',
+ 'totalSymbolTableParseTime',
+ 'breakpoints',
+ ]
+ keys_missing = [
+ 'modules',
+ 'settings'
+ ]
+ self.verify_keys(stats, '"stats"', keys_exist, keys_missing)
+ self.assertGreater(stats['totalBreakpointResolveTime'], 0.0)
+ self.assertGreater(stats['totalDebugInfoParseTime'], 0.0)
+ breakpoints = stats['breakpoints']
+ bp_keys_exist = [
+ 'details',
+ 'id',
+ 'resolveTime',
+ 'locations'
+ ]
+ for breakpoint in breakpoints:
+ self.verify_keys(breakpoint, 'stats["breakpoints"]',
+ bp_keys_exist, None)
+
+ def test_settings(self):
+ """Test "statistics dump --settings"
+
+ Output expected to be something like:
+
+ (lldb) statistics dump --settings
+ {
+ "targetCreateTime": 0.26566899599999999,
+ "totalBreakpointResolveTime": 0.0031409419999999999,
+ "totalDebugInfoIndexTime": 0,
+ "totalDebugInfoParseTime": 0.00037288300000000001,
+ "totalDebugInfoSize": 2193,
+ "totalSymbolTableIndexTime": 0.056834488000000009,
+ "totalSymbolTableParseTime": 0.093979421999999979
+ "settings": {
+ "target.arg0": "/.../commands/target/metrics/TestStats.test_settings/a.out",
+ "target.clang-module-search-paths": [],
+ "target.debug-file-search-paths": [],
+ "target.exec-search-paths": [
+ "/.../commands/target/metrics/TestStats.test_settings"
+ ],
+ "target.inline-breakpoint-strategy": "always",
+ "target.preload-symbols": true,
+ "target.run-args": [],
+ "target.skip-prologue": true,
+ "target.source-map": []
+ },
+ }
+
+ """
+ target = self.createTestTarget()
+ stats = self.get_stats(options="--settings")
+ keys_exist = [
+ 'targetCreateTime',
+ 'totalBreakpointResolveTime',
+ 'totalDebugInfoIndexTime',
+ 'totalDebugInfoParseTime',
+ 'totalDebugInfoSize',
+ 'totalSymbolTableIndexTime',
+ 'totalSymbolTableParseTime',
+ 'settings'
+ ]
+ keys_missing = [
+ 'breakpoints',
+ 'modules',
+ ]
+ self.verify_keys(stats, '"stats"', keys_exist, keys_missing)
+ settings = stats['settings']
+ settings_keys_exist = [
+ "target.arg0",
+ "target.clang-module-search-paths",
+ "target.debug-file-search-paths",
+ "target.exec-search-paths",
+ "target.inline-breakpoint-strategy",
+ "target.preload-symbols",
+ "target.run-args",
+ "target.skip-prologue",
+ "target.source-map",
+ ]
+ self.verify_keys(settings, 'stats["settings"]', settings_keys_exist)
Index: lldb/source/Target/Target.cpp
===================================================================
--- lldb/source/Target/Target.cpp
+++ lldb/source/Target/Target.cpp
@@ -95,14 +95,10 @@
m_watchpoint_list(), m_process_sp(), m_search_filter_sp(),
m_image_search_paths(ImageSearchPathsChanged, this),
m_source_manager_up(), m_stop_hooks(), m_stop_hook_next_id(0),
- m_latest_stop_hook_id(0),
- m_valid(true), m_suppress_stop_hooks(false),
+ m_latest_stop_hook_id(0), m_valid(true), m_suppress_stop_hooks(false),
m_is_dummy_target(is_dummy_target),
m_frame_recognizer_manager_up(
- std::make_unique<StackFrameRecognizerManager>()),
- m_stats_storage(static_cast<int>(StatisticKind::StatisticMax))
-
-{
+ std::make_unique<StackFrameRecognizerManager>()) {
SetEventName(eBroadcastBitBreakpointChanged, "breakpoint-changed");
SetEventName(eBroadcastBitModulesLoaded, "modules-loaded");
SetEventName(eBroadcastBitModulesUnloaded, "modules-unloaded");
@@ -1400,6 +1396,7 @@
ClearModules(false);
if (executable_sp) {
+ ElapsedTime elapsed(m_stats.GetCreateTime());
LLDB_SCOPED_TIMERF("Target::SetExecutableModule (executable = '%s')",
executable_sp->GetFileSpec().GetPath().c_str());
@@ -2908,6 +2905,7 @@
void Target::ClearAllLoadedSections() { m_section_load_history.Clear(); }
Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
+ m_stats.SetLaunchOrAttachTime();
Status error;
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_TARGET));
@@ -3119,6 +3117,7 @@
}
Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) {
+ m_stats.SetLaunchOrAttachTime();
auto state = eStateInvalid;
auto process_sp = GetProcessSP();
if (process_sp) {
@@ -4464,3 +4463,9 @@
else
return m_mutex;
}
+
+/// Get metrics associated with this target in JSON format.
+llvm::json::Value Target::ReportStatistics(const StatsDumpOptions &options) {
+ m_stats.CollectModuleStats(*this);
+ return m_stats.ToJSON(*this, options);
+}
Index: lldb/source/Target/Statistics.cpp
===================================================================
--- /dev/null
+++ lldb/source/Target/Statistics.cpp
@@ -0,0 +1,203 @@
+//===-- Statistics.cpp ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/Statistics.h"
+
+#include "lldb/Core/Module.h"
+#include "lldb/Symbol/SymbolFile.h"
+#include "lldb/Target/Target.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace llvm;
+
+static void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key,
+ const std::string &str) {
+ if (str.empty())
+ return;
+ if (LLVM_LIKELY(llvm::json::isUTF8(str)))
+ obj.try_emplace(key, str);
+ else
+ obj.try_emplace(key, llvm::json::fixUTF8(str));
+}
+
+void SuccessFailStats::Notify(bool success) {
+ if (success)
+ ++successes;
+ else
+ ++failures;
+}
+
+json::Value SuccessFailStats::ToJSON() const {
+ return json::Value(json::Object{
+ {"successes", successes},
+ {"failures", failures},
+ });
+}
+
+void TargetStats::CollectModuleStats(Target &target) {
+ m_modules.clear();
+ for (ModuleSP module_sp : target.GetImages().Modules()) {
+ ModuleStats module;
+ module.path = module_sp->GetFileSpec().GetPath();
+ module.uuid = module_sp->GetUUID().GetAsString();
+ module.triple = module_sp->GetArchitecture().GetTriple().str();
+ SymbolFile *sym_file = module_sp->GetSymbolFile();
+ if (sym_file) {
+ module.debug_index_time = sym_file->GetDebugInfoIndexTime().count();
+ module.debug_parse_time = sym_file->GetDebugInfoParseTime().count();
+ }
+ module.symtab_parse_time = module_sp->GetSymtabParseTime().count();
+ module.symtab_names_time = module_sp->GetSymtabNamesTime().count();
+ module.debug_info_size = module_sp->GetDebugInfoSize();
+ m_modules.push_back(std::move(module));
+ }
+}
+
+static double elapsed(const std::chrono::steady_clock::time_point &start,
+ const std::chrono::steady_clock::time_point &end) {
+ std::chrono::duration<double> elapsed =
+ end.time_since_epoch() - start.time_since_epoch();
+ return elapsed.count();
+}
+
+json::Value TargetStats::ToJSON(Target &target,
+ const StatsDumpOptions &options) {
+ CollectModuleStats(target);
+
+ uint64_t debug_info_size = 0;
+ double debug_parse_time = 0.0;
+ double debug_index_time = 0.0;
+ double symtab_parse_time = 0.0;
+ double symtab_names_time = 0.0;
+
+ json::Array modules;
+ for (const auto &module : m_modules) {
+ modules.push_back(module.ToJSON());
+ debug_info_size += module.debug_info_size;
+ debug_parse_time += module.debug_parse_time;
+ debug_index_time += module.debug_index_time;
+ symtab_parse_time += module.symtab_parse_time;
+ symtab_names_time += module.symtab_names_time;
+ }
+
+ json::Array breakpoints_array;
+ std::unique_lock<std::recursive_mutex> lock;
+ target.GetBreakpointList().GetListMutex(lock);
+
+ const BreakpointList &breakpoints = target.GetBreakpointList();
+ size_t num_breakpoints = breakpoints.GetSize();
+ double totalBreakpointResolveTime = 0;
+ for (size_t i = 0; i < num_breakpoints; i++) {
+ Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get();
+ if (options.dump_breakpoints || options.dump_breakpoint_locations)
+ breakpoints_array.push_back(bp->GetStatistics(options));
+ totalBreakpointResolveTime += bp->GetResolveTime().count();
+ }
+
+ // Include settings that can change how executables, sources and breakpoints
+ // can be resolved.
+ json::Object settings;
+ if (options.dump_settings) {
+ settings.try_emplace("target.arg0", target.GetArg0());
+ Args target_args;
+ json::Array run_args;
+ target.GetRunArguments(target_args);
+ for (const auto &arg : target_args.entries())
+ run_args.emplace_back(arg.c_str());
+ settings.try_emplace("target.run-args", std::move(run_args));
+ settings.try_emplace("target.source-map",
+ target.GetSourcePathMap().ToJSON());
+ settings.try_emplace("target.exec-search-paths",
+ target.GetExecutableSearchPaths().ToJSON());
+ settings.try_emplace("target.debug-file-search-paths",
+ target.GetDebugFileSearchPaths().ToJSON());
+ settings.try_emplace("target.clang-module-search-paths",
+ target.GetClangModuleSearchPaths().ToJSON());
+ settings.try_emplace("target.preload-symbols", target.GetPreloadSymbols());
+ const char *strategy = nullptr;
+ switch (target.GetInlineStrategy()) {
+ case eInlineBreakpointsNever:
+ strategy = "never";
+ break;
+ case eInlineBreakpointsHeaders:
+ strategy = "headers";
+ break;
+ case eInlineBreakpointsAlways:
+ strategy = "always";
+ break;
+ }
+ settings.try_emplace("target.inline-breakpoint-strategy", strategy);
+ settings.try_emplace("target.skip-prologue", target.GetSkipPrologue());
+ }
+
+ json::Object target_metrics_json{
+ {"totalDebugInfoSize", (int64_t)debug_info_size},
+ {"totalDebugInfoParseTime", debug_parse_time},
+ {"totalDebugInfoIndexTime", debug_index_time},
+ {"totalSymbolTableParseTime", symtab_parse_time},
+ {"totalSymbolTableIndexTime", symtab_names_time},
+ {"totalBreakpointResolveTime", totalBreakpointResolveTime},
+ {"expressionEvaluation", m_expr_eval.ToJSON()},
+ {"frameVariable", m_frame_var.ToJSON()},
+ };
+ if (options.dump_breakpoints || options.dump_breakpoint_locations)
+ target_metrics_json.try_emplace("breakpoints",
+ std::move(breakpoints_array));
+ if (options.dump_modules)
+ target_metrics_json.try_emplace("modules", std::move(modules));
+ if (options.dump_settings)
+ target_metrics_json.try_emplace("settings", std::move(settings));
+ if (launch_or_attach_time.hasValue() && first_private_stop_time.hasValue()) {
+ double elapsed_time =
+ elapsed(*launch_or_attach_time, *first_private_stop_time);
+ target_metrics_json.try_emplace("launchOrAttachTime", elapsed_time);
+ }
+ if (launch_or_attach_time.hasValue() && first_public_stop_time.hasValue()) {
+ double elapsed_time =
+ elapsed(*launch_or_attach_time, *first_public_stop_time);
+ target_metrics_json.try_emplace("firstStopTime", elapsed_time);
+ }
+ target_metrics_json.try_emplace("targetCreateTime", create_time.count());
+
+ return target_metrics_json;
+}
+
+void TargetStats::SetLaunchOrAttachTime() {
+ launch_or_attach_time = std::chrono::steady_clock::now();
+ first_private_stop_time = llvm::None;
+}
+
+void TargetStats::SetFirstPrivateStopTime() {
+ // Launching and attaching has many paths depending on if synchronous mode
+ // was used or if we are stopping at the entry point or not. Only set the
+ // first stop time if it hasn't already been set.
+ if (!first_private_stop_time.hasValue())
+ first_private_stop_time = std::chrono::steady_clock::now();
+}
+
+void TargetStats::SetFirstPublicStopTime() {
+ // Launching and attaching has many paths depending on if synchronous mode
+ // was used or if we are stopping at the entry point or not. Only set the
+ // first stop time if it hasn't already been set.
+ if (!first_public_stop_time.hasValue())
+ first_public_stop_time = std::chrono::steady_clock::now();
+}
+
+json::Value ModuleStats::ToJSON() const {
+ json::Object module;
+ EmplaceSafeString(module, "path", path);
+ EmplaceSafeString(module, "uuid", uuid);
+ EmplaceSafeString(module, "triple", triple);
+ module.try_emplace("debugInfoParseTime", debug_parse_time);
+ module.try_emplace("debugInfoIndexTime", debug_index_time);
+ module.try_emplace("symbolTableParseTime", symtab_parse_time);
+ module.try_emplace("symbolTableIndexTime", symtab_names_time);
+ module.try_emplace("debugInfoSize", (int64_t)debug_info_size);
+ return module;
+}
Index: lldb/source/Target/Process.cpp
===================================================================
--- lldb/source/Target/Process.cpp
+++ lldb/source/Target/Process.cpp
@@ -1297,6 +1297,17 @@
}
void Process::SetPublicState(StateType new_state, bool restarted) {
+ const bool new_state_is_stopped = StateIsStoppedState(new_state, false);
+ if (new_state_is_stopped) {
+ // This will only set the time if the public stop time has no value, so
+ // it is ok to call this multiple times. With a public stop we can't look
+ // at the stop ID because many private stops might have happened, so we
+ // can't check for a stop ID of zero. This allows the "statistics" command
+ // to dump the time it takes to reach somewhere in your code, like a
+ // breakpoint you set.
+ GetTarget().GetStatistics().SetFirstPublicStopTime();
+ }
+
Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STATE |
LIBLLDB_LOG_PROCESS));
LLDB_LOGF(log, "Process::SetPublicState (state = %s, restarted = %i)",
@@ -1315,7 +1326,6 @@
m_public_run_lock.SetStopped();
} else {
const bool old_state_is_stopped = StateIsStoppedState(old_state, false);
- const bool new_state_is_stopped = StateIsStoppedState(new_state, false);
if ((old_state_is_stopped != new_state_is_stopped)) {
if (new_state_is_stopped && !restarted) {
LLDB_LOGF(log, "Process::SetPublicState (%s) -- unlocking run lock",
@@ -1446,7 +1456,9 @@
// before we get here.
m_thread_list.DidStop();
- m_mod_id.BumpStopID();
+ if (m_mod_id.BumpStopID() == 0)
+ GetTarget().GetStatistics().SetFirstPrivateStopTime();
+
if (!m_mod_id.IsLastResumeForUserExpression())
m_mod_id.SetStopEventForLastNaturalStopID(event_sp);
m_memory_cache.Clear();
Index: lldb/source/Target/PathMappingList.cpp
===================================================================
--- lldb/source/Target/PathMappingList.cpp
+++ lldb/source/Target/PathMappingList.cpp
@@ -297,3 +297,14 @@
}
return UINT32_MAX;
}
+
+llvm::json::Value PathMappingList::ToJSON() const {
+ llvm::json::Array mappings;
+ for (const auto &pair : m_pairs) {
+ llvm::json::Array mapping;
+ mapping.emplace_back(pair.first.GetCString());
+ mapping.emplace_back(pair.second.GetCString());
+ mappings.emplace_back(std::move(mapping));
+ }
+ return llvm::json::Value(std::move(mappings));
+}
Index: lldb/source/Target/CMakeLists.txt
===================================================================
--- lldb/source/Target/CMakeLists.txt
+++ lldb/source/Target/CMakeLists.txt
@@ -38,6 +38,7 @@
StackFrameList.cpp
StackFrameRecognizer.cpp
StackID.cpp
+ Statistics.cpp
StopInfo.cpp
StructuredDataPlugin.cpp
SystemRuntime.cpp
Index: lldb/source/Symbol/Symtab.cpp
===================================================================
--- lldb/source/Symbol/Symtab.cpp
+++ lldb/source/Symbol/Symtab.cpp
@@ -265,6 +265,7 @@
// Protected function, no need to lock mutex...
if (!m_name_indexes_computed) {
m_name_indexes_computed = true;
+ ElapsedTime elapsed(m_objfile->GetModule()->GetSymtabNamesTime());
LLDB_SCOPED_TIMER();
// Collect all loaded language plugins.
Index: lldb/source/Symbol/SymbolContext.cpp
===================================================================
--- lldb/source/Symbol/SymbolContext.cpp
+++ lldb/source/Symbol/SymbolContext.cpp
@@ -916,6 +916,26 @@
return nullptr; // no error; we just didn't find anything
}
+llvm::json::Value SymbolContext::ToJSON() const {
+ llvm::json::Object json;
+ if (module_sp)
+ json.try_emplace("module", module_sp->GetUUID().GetAsString());
+ if (comp_unit)
+ json.try_emplace("compUnit", comp_unit->GetPrimaryFile().GetPath());
+ if (function && function->GetName())
+ json.try_emplace("function", function->GetName().AsCString());
+ if (line_entry.file)
+ json.try_emplace("sourceFile", line_entry.file.GetPath());
+ if (line_entry.file != line_entry.original_file && line_entry.original_file)
+ json.try_emplace("origSourceFile", line_entry.original_file.GetPath());
+ json.try_emplace("sourceLine", (int64_t)line_entry.line);
+ if (line_entry.column)
+ json.try_emplace("sourceColumn", (int64_t)line_entry.column);
+ if (symbol)
+ json.try_emplace("symbol", symbol->GetName().AsCString("<unknown>"));
+ return llvm::json::Value(std::move(json));
+}
+
//
// SymbolContextSpecifier
//
Index: lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.h
===================================================================
--- lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.h
+++ lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.h
@@ -45,6 +45,8 @@
uint32_t CalculateAbilities() override;
+ uint64_t GetDebugInfoSize() override;
+
// Compile Unit function calls
lldb::LanguageType
ParseLanguage(lldb_private::CompileUnit &comp_unit) override;
Index: lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.cpp
===================================================================
--- lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.cpp
+++ lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.cpp
@@ -104,6 +104,13 @@
return abilities;
}
+uint64_t SymbolFileSymtab::GetDebugInfoSize() {
+ // This call isn't expected to look at symbols and figure out which ones are
+ // debug info or not, it is just to report sections full of debug info, so
+ // we should return 0 here.
+ return 0;
+}
+
uint32_t SymbolFileSymtab::CalculateNumCompileUnits() {
// If we don't have any source file symbols we will just have one compile
// unit for the entire object file
Index: lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.h
===================================================================
--- lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.h
+++ lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.h
@@ -55,6 +55,8 @@
uint32_t CalculateAbilities() override;
+ uint64_t GetDebugInfoSize() override;
+
void InitializeObject() override;
// Compile Unit function calls
Index: lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp
===================================================================
--- lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp
+++ lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp
@@ -2062,3 +2062,8 @@
return 0;
}
+
+uint64_t SymbolFilePDB::GetDebugInfoSize() {
+ // TODO: implement this
+ return 0;
+}
Index: lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h
===================================================================
--- lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h
+++ lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h
@@ -73,6 +73,8 @@
uint32_t CalculateAbilities() override;
+ uint64_t GetDebugInfoSize() override;
+
void InitializeObject() override;
// Compile Unit function calls
Index: lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
===================================================================
--- lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
+++ lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
@@ -296,6 +296,11 @@
return abilities;
}
+uint64_t SymbolFileNativePDB::GetDebugInfoSize() {
+ // PDB files are a separate file that contains all debug info.
+ return m_index->pdb().getFileSize();
+}
+
void SymbolFileNativePDB::InitializeObject() {
m_obj_load_address = m_objfile_sp->GetModule()
->GetObjectFile()
Index: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
===================================================================
--- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
+++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
@@ -10,6 +10,7 @@
#define LLDB_SOURCE_PLUGINS_SYMBOLFILE_DWARF_SYMBOLFILEDWARFDEBUGMAP_H
#include "lldb/Symbol/SymbolFile.h"
+#include "lldb/Utility/ElapsedTime.h"
#include "lldb/Utility/RangeMap.h"
#include "llvm/Support/Chrono.h"
#include <bitset>
@@ -142,6 +143,10 @@
// PluginInterface protocol
lldb_private::ConstString GetPluginName() override;
+ uint64_t GetDebugInfoSize() override;
+ lldb_private::ElapsedTime::Duration GetDebugInfoParseTime() override;
+ lldb_private::ElapsedTime::Duration GetDebugInfoIndexTime() override;
+
protected:
enum { kHaveInitializedOSOs = (1 << 0), kNumFlags };
Index: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
===================================================================
--- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
+++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
@@ -1441,3 +1441,54 @@
}
return num_line_entries_added;
}
+
+uint64_t SymbolFileDWARFDebugMap::GetDebugInfoSize() {
+ uint64_t debug_info_size = 0;
+ ForEachSymbolFile([&](SymbolFileDWARF *oso_dwarf) -> bool {
+ ObjectFile *oso_objfile = oso_dwarf->GetObjectFile();
+ if (!oso_objfile)
+ return false; // Keep iterating
+ ModuleSP module_sp = oso_objfile->GetModule();
+ if (!module_sp)
+ return false; // Keep iterating
+ SectionList *section_list = module_sp->GetSectionList();
+ if (section_list)
+ debug_info_size += section_list->GetDebugInfoSize();
+ return false; // Keep iterating
+ });
+ return debug_info_size;
+}
+
+ElapsedTime::Duration SymbolFileDWARFDebugMap::GetDebugInfoParseTime() {
+ ElapsedTime::Duration elapsed(0.0);
+ ForEachSymbolFile([&](SymbolFileDWARF *oso_dwarf) -> bool {
+ ObjectFile *oso_objfile = oso_dwarf->GetObjectFile();
+ if (oso_objfile) {
+ ModuleSP module_sp = oso_objfile->GetModule();
+ if (module_sp) {
+ SymbolFile *symfile = module_sp->GetSymbolFile();
+ if (symfile)
+ elapsed += symfile->GetDebugInfoParseTime();
+ }
+ }
+ return false; // Keep iterating
+ });
+ return elapsed;
+}
+
+ElapsedTime::Duration SymbolFileDWARFDebugMap::GetDebugInfoIndexTime() {
+ ElapsedTime::Duration elapsed(0.0);
+ ForEachSymbolFile([&](SymbolFileDWARF *oso_dwarf) -> bool {
+ ObjectFile *oso_objfile = oso_dwarf->GetObjectFile();
+ if (oso_objfile) {
+ ModuleSP module_sp = oso_objfile->GetModule();
+ if (module_sp) {
+ SymbolFile *symfile = module_sp->GetSymbolFile();
+ if (symfile)
+ elapsed += symfile->GetDebugInfoIndexTime();
+ }
+ }
+ return false; // Keep iterating
+ });
+ return elapsed;
+}
Index: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
===================================================================
--- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -25,6 +25,7 @@
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Symbol/SymbolFile.h"
#include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/ElapsedTime.h"
#include "lldb/Utility/Flags.h"
#include "lldb/Utility/RangeMap.h"
#include "lldb/lldb-private.h"
@@ -318,6 +319,16 @@
/// Same as GetLanguage() but reports all C++ versions as C++ (no version).
static lldb::LanguageType GetLanguageFamily(DWARFUnit &unit);
+ uint64_t GetDebugInfoSize() override;
+ lldb_private::ElapsedTime::Duration GetDebugInfoParseTime() override {
+ return m_parse_time;
+ }
+ lldb_private::ElapsedTime::Duration GetDebugInfoIndexTime() override;
+
+ lldb_private::ElapsedTime::Duration &GetDebugInfoParseTimeRef() {
+ return m_parse_time;
+ }
+
protected:
typedef llvm::DenseMap<const DWARFDebugInfoEntry *, lldb_private::Type *>
DIEToTypePtr;
@@ -519,6 +530,7 @@
llvm::DenseMap<dw_offset_t, lldb_private::FileSpecList>
m_type_unit_support_files;
std::vector<uint32_t> m_lldb_cu_to_dwarf_unit;
+ lldb_private::ElapsedTime::Duration m_parse_time{0.0};
};
#endif // LLDB_SOURCE_PLUGINS_SYMBOLFILE_DWARF_SYMBOLFILEDWARF_H
Index: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
===================================================================
--- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -965,6 +965,7 @@
if (offset == DW_INVALID_OFFSET)
return false;
+ ElapsedTime elapsed(m_parse_time);
llvm::DWARFDebugLine::Prologue prologue;
if (!ParseLLVMLineTablePrologue(m_context, prologue, offset,
dwarf_cu.GetOffset()))
@@ -1013,6 +1014,7 @@
"SymbolFileDWARF::GetTypeUnitSupportFiles failed to parse "
"the line table prologue");
};
+ ElapsedTime elapsed(m_parse_time);
llvm::Error error = prologue.parse(data, &line_table_offset, report, ctx);
if (error) {
report(std::move(error));
@@ -1099,6 +1101,7 @@
if (offset == DW_INVALID_OFFSET)
return false;
+ ElapsedTime elapsed(m_parse_time);
llvm::DWARFDebugLine line;
const llvm::DWARFDebugLine::LineTable *line_table =
ParseLLVMLineTable(m_context, line, offset, dwarf_cu->GetOffset());
@@ -1147,6 +1150,7 @@
if (iter != m_debug_macros_map.end())
return iter->second;
+ ElapsedTime elapsed(m_parse_time);
const DWARFDataExtractor &debug_macro_data = m_context.getOrLoadMacroData();
if (debug_macro_data.GetByteSize() == 0)
return DebugMacrosSP();
@@ -3937,3 +3941,19 @@
lang = DW_LANG_C_plus_plus;
return LanguageTypeFromDWARF(lang);
}
+
+uint64_t SymbolFileDWARF::GetDebugInfoSize() {
+ ModuleSP module_sp(m_objfile_sp->GetModule());
+ if (!module_sp)
+ return 0;
+ const SectionList *section_list = module_sp->GetSectionList();
+ if (section_list)
+ return section_list->GetDebugInfoSize();
+ return 0;
+}
+
+ElapsedTime::Duration SymbolFileDWARF::GetDebugInfoIndexTime() {
+ if (m_index)
+ return m_index->GetIndexTime();
+ return ElapsedTime::Duration();
+}
Index: lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp
===================================================================
--- lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp
+++ lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp
@@ -30,6 +30,7 @@
SymbolFileDWARF &main_dwarf = *m_dwarf;
m_dwarf = nullptr;
+ ElapsedTime elapsed(m_index_time);
LLDB_SCOPED_TIMERF("%p", static_cast<void *>(&main_dwarf));
DWARFDebugInfo &main_info = main_dwarf.DebugInfo();
Index: lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp
===================================================================
--- lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp
+++ lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp
@@ -49,6 +49,7 @@
if (m_first_die)
return; // Already parsed
+ ElapsedTime elapsed(m_dwarf.GetDebugInfoParseTimeRef());
LLDB_SCOPED_TIMERF("%8.8x: DWARFUnit::ExtractUnitDIENoDwoIfNeeded()",
GetOffset());
@@ -195,7 +196,7 @@
// held R/W and m_die_array must be empty.
void DWARFUnit::ExtractDIEsRWLocked() {
llvm::sys::ScopedWriter first_die_lock(m_first_die_mutex);
-
+ ElapsedTime elapsed(m_dwarf.GetDebugInfoParseTimeRef());
LLDB_SCOPED_TIMERF("%8.8x: DWARFUnit::ExtractDIEsIfNeeded()", GetOffset());
// Set the offset to that of the first DIE and calculate the start of the
Index: lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h
===================================================================
--- lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h
+++ lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h
@@ -12,6 +12,7 @@
#include "Plugins/SymbolFile/DWARF/DIERef.h"
#include "Plugins/SymbolFile/DWARF/DWARFDIE.h"
#include "Plugins/SymbolFile/DWARF/DWARFFormValue.h"
+#include "lldb/Utility/ElapsedTime.h"
class DWARFDeclContext;
class DWARFDIE;
@@ -62,8 +63,11 @@
virtual void Dump(Stream &s) = 0;
+ ElapsedTime::Duration GetIndexTime() { return m_index_time; }
+
protected:
Module &m_module;
+ ElapsedTime::Duration m_index_time{0.0};
/// Helper function implementing common logic for processing function dies. If
/// the function given by "ref" matches search criteria given by
Index: lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h
===================================================================
--- lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h
+++ lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h
@@ -55,6 +55,8 @@
uint32_t CalculateAbilities() override;
+ uint64_t GetDebugInfoSize() override;
+
void InitializeObject() override {}
// Compile Unit function calls
Index: lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp
===================================================================
--- lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp
+++ lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp
@@ -188,6 +188,11 @@
return CompileUnits | Functions | LineTables;
}
+uint64_t SymbolFileBreakpad::GetDebugInfoSize() {
+ // Breakpad files are all debug info.
+ return m_objfile_sp->GetByteSize();
+}
+
uint32_t SymbolFileBreakpad::CalculateNumCompileUnits() {
ParseCUData();
return m_cu_data->GetSize();
Index: lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
===================================================================
--- lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
+++ lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
@@ -600,6 +600,7 @@
if (module_sp) {
std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex());
if (m_symtab_up == nullptr) {
+ ElapsedTime elapsed(module_sp->GetSymtabParseTime());
SectionList *sect_list = GetSectionList();
m_symtab_up = std::make_unique<Symtab>(this);
std::lock_guard<std::recursive_mutex> guard(m_symtab_up->GetMutex());
Index: lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
===================================================================
--- lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -1307,6 +1307,10 @@
if (module_sp) {
std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex());
if (m_symtab_up == nullptr) {
+ // Aggregate the symbol table parse time into the module in case we have
+ // one object file for the module and one for the debug information, both
+ // of which can have symbol tables that might get parsed.
+ ElapsedTime elapsed(module_sp->GetSymtabParseTime());
m_symtab_up = std::make_unique<Symtab>(this);
std::lock_guard<std::recursive_mutex> symtab_guard(
m_symtab_up->GetMutex());
@@ -2229,7 +2233,6 @@
ModuleSP module_sp(GetModule());
if (!module_sp)
return 0;
-
Progress progress(llvm::formatv("Parsing symbol table for {0}",
m_file.GetFilename().AsCString("<Unknown>")));
@@ -2479,8 +2482,8 @@
// We shouldn't have exports data from both the LC_DYLD_INFO command
// AND the LC_DYLD_EXPORTS_TRIE command in the same binary:
- lldbassert(!((dyld_info.export_size > 0)
- && (exports_trie_load_command.datasize > 0)));
+ lldbassert(!((dyld_info.export_size > 0) &&
+ (exports_trie_load_command.datasize > 0)));
if (dyld_info.export_size > 0) {
dyld_trie_data.SetData(m_data, dyld_info.export_off,
dyld_info.export_size);
Index: lldb/source/Plugins/ObjectFile/JIT/ObjectFileJIT.cpp
===================================================================
--- lldb/source/Plugins/ObjectFile/JIT/ObjectFileJIT.cpp
+++ lldb/source/Plugins/ObjectFile/JIT/ObjectFileJIT.cpp
@@ -120,6 +120,7 @@
if (module_sp) {
std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex());
if (m_symtab_up == nullptr) {
+ ElapsedTime elapsed(module_sp->GetSymtabParseTime());
m_symtab_up = std::make_unique<Symtab>(this);
std::lock_guard<std::recursive_mutex> symtab_guard(
m_symtab_up->GetMutex());
Index: lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
===================================================================
--- lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
+++ lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
@@ -2707,9 +2707,6 @@
if (!module_sp)
return nullptr;
- Progress progress(llvm::formatv("Parsing symbol table for {0}",
- m_file.GetFilename().AsCString("<Unknown>")));
-
// We always want to use the main object file so we (hopefully) only have one
// cached copy of our symtab, dynamic sections, etc.
ObjectFile *module_obj_file = module_sp->GetObjectFile();
@@ -2717,6 +2714,10 @@
return module_obj_file->GetSymtab();
if (m_symtab_up == nullptr) {
+ Progress progress(
+ llvm::formatv("Parsing symbol table for {0}",
+ m_file.GetFilename().AsCString("<Unknown>")));
+ ElapsedTime elapsed(module_sp->GetSymtabParseTime());
SectionList *section_list = module_sp->GetSectionList();
if (!section_list)
return nullptr;
Index: lldb/source/Core/Section.cpp
===================================================================
--- lldb/source/Core/Section.cpp
+++ lldb/source/Core/Section.cpp
@@ -151,6 +151,75 @@
return "unknown";
}
+bool Section::ContainsOnlyDebugInfo() const {
+ switch (m_type) {
+ case eSectionTypeInvalid:
+ case eSectionTypeCode:
+ case eSectionTypeContainer:
+ case eSectionTypeData:
+ case eSectionTypeDataCString:
+ case eSectionTypeDataCStringPointers:
+ case eSectionTypeDataSymbolAddress:
+ case eSectionTypeData4:
+ case eSectionTypeData8:
+ case eSectionTypeData16:
+ case eSectionTypeDataPointers:
+ case eSectionTypeZeroFill:
+ case eSectionTypeDataObjCMessageRefs:
+ case eSectionTypeDataObjCCFStrings:
+ case eSectionTypeELFSymbolTable:
+ case eSectionTypeELFDynamicSymbols:
+ case eSectionTypeELFRelocationEntries:
+ case eSectionTypeELFDynamicLinkInfo:
+ case eSectionTypeEHFrame:
+ case eSectionTypeARMexidx:
+ case eSectionTypeARMextab:
+ case eSectionTypeCompactUnwind:
+ case eSectionTypeGoSymtab:
+ case eSectionTypeAbsoluteAddress:
+ case eSectionTypeOther:
+ return false;
+
+ case eSectionTypeDebug:
+ case eSectionTypeDWARFDebugAbbrev:
+ case eSectionTypeDWARFDebugAbbrevDwo:
+ case eSectionTypeDWARFDebugAddr:
+ case eSectionTypeDWARFDebugAranges:
+ case eSectionTypeDWARFDebugCuIndex:
+ case eSectionTypeDWARFDebugTuIndex:
+ case eSectionTypeDWARFDebugFrame:
+ case eSectionTypeDWARFDebugInfo:
+ case eSectionTypeDWARFDebugInfoDwo:
+ case eSectionTypeDWARFDebugLine:
+ case eSectionTypeDWARFDebugLineStr:
+ case eSectionTypeDWARFDebugLoc:
+ case eSectionTypeDWARFDebugLocDwo:
+ case eSectionTypeDWARFDebugLocLists:
+ case eSectionTypeDWARFDebugLocListsDwo:
+ case eSectionTypeDWARFDebugMacInfo:
+ case eSectionTypeDWARFDebugMacro:
+ case eSectionTypeDWARFDebugPubNames:
+ case eSectionTypeDWARFDebugPubTypes:
+ case eSectionTypeDWARFDebugRanges:
+ case eSectionTypeDWARFDebugRngLists:
+ case eSectionTypeDWARFDebugRngListsDwo:
+ case eSectionTypeDWARFDebugStr:
+ case eSectionTypeDWARFDebugStrDwo:
+ case eSectionTypeDWARFDebugStrOffsets:
+ case eSectionTypeDWARFDebugStrOffsetsDwo:
+ case eSectionTypeDWARFDebugTypes:
+ case eSectionTypeDWARFDebugTypesDwo:
+ case eSectionTypeDWARFDebugNames:
+ case eSectionTypeDWARFAppleNames:
+ case eSectionTypeDWARFAppleTypes:
+ case eSectionTypeDWARFAppleNamespaces:
+ case eSectionTypeDWARFAppleObjC:
+ case eSectionTypeDWARFGNUDebugAltLink:
+ return true;
+ }
+ return false;
+}
+
Section::Section(const ModuleSP &module_sp, ObjectFile *obj_file,
user_id_t sect_id, ConstString name,
SectionType sect_type, addr_t file_addr, addr_t byte_size,
@@ -599,3 +668,15 @@
}
return count;
}
+
+uint64_t SectionList::GetDebugInfoSize() const {
+ uint64_t debug_info_size = 0;
+ for (const auto §ion : m_sections) {
+ const SectionList &sub_sections = section->GetChildren();
+ if (sub_sections.GetSize() > 0)
+ debug_info_size += sub_sections.GetDebugInfoSize();
+ else if (section->ContainsOnlyDebugInfo())
+ debug_info_size += section->GetFileSize();
+ }
+ return debug_info_size;
+}
Index: lldb/source/Core/Module.cpp
===================================================================
--- lldb/source/Core/Module.cpp
+++ lldb/source/Core/Module.cpp
@@ -1659,3 +1659,10 @@
return false;
}
+
+uint64_t Module::GetDebugInfoSize() {
+ SymbolFile *symbols = GetSymbolFile();
+ if (symbols)
+ return symbols->GetDebugInfoSize();
+ return 0;
+}
Index: lldb/source/Core/FileSpecList.cpp
===================================================================
--- lldb/source/Core/FileSpecList.cpp
+++ lldb/source/Core/FileSpecList.cpp
@@ -119,3 +119,10 @@
FileSpecList &matches) {
return 0;
}
+
+llvm::json::Value FileSpecList::ToJSON() const {
+ llvm::json::Array files;
+ for (const auto &file : m_files)
+ files.emplace_back(file.GetPath());
+ return llvm::json::Value(std::move(files));
+}
Index: lldb/source/Commands/Options.td
===================================================================
--- lldb/source/Commands/Options.td
+++ lldb/source/Commands/Options.td
@@ -1290,3 +1290,16 @@
def trace_schema_verbose : Option<"verbose", "v">, Group<1>,
Desc<"Show verbose trace schema logging for debugging the plug-in.">;
}
+
+let Command = "statistics dump" in {
+ def statistics_dump_modules: Option<"modules", "m">, Group<1>,
+ Desc<"Include metrics for each module in the target.">;
+ def statistics_dump_breakpoints: Option<"breakpoints", "b">, Group<1>,
+ Desc<"Include metrics for each breakpoint in the target.">;
+ def statistics_dump_locations: Option<"locations", "l">, Group<1>,
+ Desc<"Include metrics for each breakpoint location in each breakpoint.">;
+ def statistics_dump_settings: Option<"settings", "s">, Group<1>,
+ Desc<"Include important target settings in metrics.">;
+ def statistics_dump_all: Option<"all", "a">, Group<1>,
+ Desc<"Include all metrics in the target.">;
+}
Index: lldb/source/Commands/CommandObjectStats.cpp
===================================================================
--- lldb/source/Commands/CommandObjectStats.cpp
+++ lldb/source/Commands/CommandObjectStats.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "CommandObjectStats.h"
+#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Target/Target.h"
@@ -26,12 +27,12 @@
bool DoExecute(Args &command, CommandReturnObject &result) override {
Target &target = GetSelectedOrDummyTarget();
- if (target.GetCollectingStats()) {
+ if (target.GetStatistics().GetCollectingStats()) {
result.AppendError("statistics already enabled");
return false;
}
- target.SetCollectingStats(true);
+ target.GetStatistics().SetCollectingStats(true);
result.SetStatus(eReturnStatusSuccessFinishResult);
return true;
}
@@ -50,42 +51,84 @@
bool DoExecute(Args &command, CommandReturnObject &result) override {
Target &target = GetSelectedOrDummyTarget();
- if (!target.GetCollectingStats()) {
+ if (!target.GetStatistics().GetCollectingStats()) {
result.AppendError("need to enable statistics before disabling them");
return false;
}
- target.SetCollectingStats(false);
+ target.GetStatistics().SetCollectingStats(false);
result.SetStatus(eReturnStatusSuccessFinishResult);
return true;
}
};
+#define LLDB_OPTIONS_statistics_dump
+#include "CommandOptions.inc"
+
class CommandObjectStatsDump : public CommandObjectParsed {
public:
+ class CommandOptions : public Options {
+ public:
+ CommandOptions() : Options() { OptionParsingStarting(nullptr); }
+
+ Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+ ExecutionContext *execution_context) override {
+ Status error;
+ const int short_option = m_getopt_table[option_idx].val;
+
+ switch (short_option) {
+ case 'a':
+ m_metric_options.SetAll();
+ break;
+ case 'b':
+ m_metric_options.dump_breakpoints = true;
+ break;
+ case 'l':
+ m_metric_options.dump_breakpoint_locations = true;
+ break;
+ case 'm':
+ m_metric_options.dump_modules = true;
+ break;
+ case 's':
+ m_metric_options.dump_settings = true;
+ break;
+ default:
+ llvm_unreachable("Unimplemented option");
+ }
+ return error;
+ }
+
+ void OptionParsingStarting(ExecutionContext *execution_context) override {
+ m_metric_options = StatsDumpOptions();
+ }
+
+ llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+ return llvm::makeArrayRef(g_statistics_dump_options);
+ }
+
+ StatsDumpOptions m_metric_options;
+ };
+
CommandObjectStatsDump(CommandInterpreter &interpreter)
- : CommandObjectParsed(interpreter, "dump", "Dump statistics results",
- nullptr, eCommandProcessMustBePaused) {}
+ : CommandObjectParsed(
+ interpreter, "statistics dump", "Dump metrics in JSON format",
+ "statistics dump [<options>]", eCommandRequiresTarget),
+ m_options() {}
~CommandObjectStatsDump() override = default;
+ Options *GetOptions() override { return &m_options; }
+
protected:
bool DoExecute(Args &command, CommandReturnObject &result) override {
- Target &target = GetSelectedOrDummyTarget();
-
- uint32_t i = 0;
- for (auto &stat : target.GetStatistics()) {
- result.AppendMessageWithFormat(
- "%s : %u\n",
- lldb_private::GetStatDescription(
- static_cast<lldb_private::StatisticKind>(i))
- .c_str(),
- stat);
- i += 1;
- }
+ Target &target = m_exe_ctx.GetTargetRef();
+ result.AppendMessageWithFormatv(
+ "{0:2}", target.ReportStatistics(m_options.m_metric_options));
result.SetStatus(eReturnStatusSuccessFinishResult);
return true;
}
+
+ CommandOptions m_options;
};
CommandObjectStats::CommandObjectStats(CommandInterpreter &interpreter)
Index: lldb/source/Commands/CommandObjectFrame.cpp
===================================================================
--- lldb/source/Commands/CommandObjectFrame.cpp
+++ lldb/source/Commands/CommandObjectFrame.cpp
@@ -708,11 +708,7 @@
// Increment statistics.
bool res = result.Succeeded();
- Target &target = GetSelectedOrDummyTarget();
- if (res)
- target.IncrementStats(StatisticKind::FrameVarSuccess);
- else
- target.IncrementStats(StatisticKind::FrameVarFailure);
+ GetSelectedOrDummyTarget().GetStatistics().NotifyFrameVar(res);
return res;
}
Index: lldb/source/Commands/CommandObjectExpression.cpp
===================================================================
--- lldb/source/Commands/CommandObjectExpression.cpp
+++ lldb/source/Commands/CommandObjectExpression.cpp
@@ -660,12 +660,12 @@
history.AppendString(fixed_command);
}
// Increment statistics to record this expression evaluation success.
- target.IncrementStats(StatisticKind::ExpressionSuccessful);
+ target.GetStatistics().NotifyExprEval(true);
return true;
}
// Increment statistics to record this expression evaluation failure.
- target.IncrementStats(StatisticKind::ExpressionFailure);
+ target.GetStatistics().NotifyExprEval(false);
result.SetStatus(eReturnStatusFailed);
return false;
}
Index: lldb/source/Breakpoint/BreakpointLocation.cpp
===================================================================
--- lldb/source/Breakpoint/BreakpointLocation.cpp
+++ lldb/source/Breakpoint/BreakpointLocation.cpp
@@ -667,3 +667,33 @@
m_is_indirect = swap_from->m_is_indirect;
m_user_expression_sp.reset();
}
+
+llvm::json::Value
+BreakpointLocation::GetMetrics(const StatsDumpOptions &options) {
+
+ llvm::json::Object loc_json;
+
+ const bool is_resolved = IsResolved();
+ const bool is_hardware = is_resolved && m_bp_site_sp->IsHardware();
+ loc_json.try_emplace("id", GetID());
+ loc_json.try_emplace("resolved", is_resolved);
+ loc_json.try_emplace("hardward", is_hardware);
+ loc_json.try_emplace("reexported", IsReExported());
+ loc_json.try_emplace("indirect", IsIndirect());
+ loc_json.try_emplace("enabled", IsEnabled());
+
+ if (m_address.IsSectionOffset()) {
+ SymbolContext sc;
+ m_address.CalculateSymbolContext(&sc);
+ loc_json.try_emplace("symbolContext", sc.ToJSON());
+ }
+
+ const addr_t load_addr = GetLoadAddress();
+ if (load_addr != LLDB_INVALID_ADDRESS)
+ loc_json.try_emplace("loadAddress", (int64_t)load_addr);
+ const addr_t file_addr = m_address.GetFileAddress();
+ if (file_addr != LLDB_INVALID_ADDRESS)
+ loc_json.try_emplace("fileAddress", (int64_t)file_addr);
+ loc_json.try_emplace("hitCount", (int64_t)GetHitCount());
+ return llvm::json::Value(std::move(loc_json));
+}
Index: lldb/source/Breakpoint/Breakpoint.cpp
===================================================================
--- lldb/source/Breakpoint/Breakpoint.cpp
+++ lldb/source/Breakpoint/Breakpoint.cpp
@@ -19,16 +19,17 @@
#include "lldb/Core/ModuleList.h"
#include "lldb/Core/SearchFilter.h"
#include "lldb/Core/Section.h"
-#include "lldb/Target/SectionLoadList.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Symbol/SymbolContext.h"
+#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/ThreadSpec.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/StreamString.h"
+#include "lldb/Utility/Timer.h"
#include <memory>
@@ -77,6 +78,44 @@
return bp;
}
+json::Value Breakpoint::GetStatistics(const StatsDumpOptions &options) {
+ json::Object bp;
+ bp.try_emplace("id", GetID());
+ if (!m_name_list.empty()) {
+ json::Array names;
+ for (const auto &name : m_name_list)
+ names.emplace_back(name);
+ bp.try_emplace("names", std::move(names));
+ }
+ if (options.dump_breakpoint_locations) {
+ json::Array locations;
+ const size_t num_locs = m_locations.GetSize();
+ for (size_t i = 0; i < num_locs; ++i) {
+ BreakpointLocationSP loc_sp = GetLocationAtIndex(i);
+ if (!loc_sp)
+ continue;
+ locations.emplace_back(loc_sp->GetMetrics(options));
+ }
+ bp.try_emplace("locations", std::move(locations));
+ }
+
+ if (m_resolve_time.count() > 0.0)
+ bp.try_emplace("resolveTime", m_resolve_time.count());
+ StructuredData::ObjectSP bp_data_sp = SerializeToStructuredData();
+ if (bp_data_sp) {
+ std::string buffer;
+ llvm::raw_string_ostream ss(buffer);
+ json::OStream json_os(ss);
+ bp_data_sp->Serialize(json_os);
+ if (llvm::Expected<llvm::json::Value> expected_value =
+ llvm::json::parse(ss.str()))
+ bp.try_emplace("details", std::move(*expected_value));
+ else
+ llvm::consumeError(expected_value.takeError());
+ }
+ return json::Value(std::move(bp));
+}
+
// Serialization
StructuredData::ObjectSP Breakpoint::SerializeToStructuredData() {
// Serialize the resolver:
@@ -439,22 +478,24 @@
const BreakpointOptions &Breakpoint::GetOptions() const { return m_options; }
void Breakpoint::ResolveBreakpoint() {
- if (m_resolver_sp)
+ if (m_resolver_sp) {
+ ElapsedTime elapsed(m_resolve_time);
m_resolver_sp->ResolveBreakpoint(*m_filter_sp);
+ }
}
void Breakpoint::ResolveBreakpointInModules(
ModuleList &module_list, BreakpointLocationCollection &new_locations) {
+ ElapsedTime elapsed(m_resolve_time);
m_locations.StartRecordingNewLocations(new_locations);
-
m_resolver_sp->ResolveBreakpointInModules(*m_filter_sp, module_list);
-
m_locations.StopRecordingNewLocations();
}
void Breakpoint::ResolveBreakpointInModules(ModuleList &module_list,
bool send_event) {
if (m_resolver_sp) {
+ ElapsedTime elapsed(m_resolve_time);
// If this is not an internal breakpoint, set up to record the new
// locations, then dispatch an event with the new locations.
if (!IsInternal() && send_event) {
@@ -522,12 +563,12 @@
locations_with_no_section.Add(break_loc_sp);
continue;
}
-
+
if (!break_loc_sp->IsEnabled())
continue;
-
+
SectionSP section_sp(section_addr.GetSection());
-
+
// If we don't have a Section, that means this location is a raw
// address that we haven't resolved to a section yet. So we'll have to
// look in all the new modules to resolve this location. Otherwise, if
@@ -544,9 +585,9 @@
}
}
}
-
+
size_t num_to_delete = locations_with_no_section.GetSize();
-
+
for (size_t i = 0; i < num_to_delete; i++)
m_locations.RemoveLocation(locations_with_no_section.GetByIndex(i));
Index: lldb/source/API/SBTarget.cpp
===================================================================
--- lldb/source/API/SBTarget.cpp
+++ lldb/source/API/SBTarget.cpp
@@ -217,17 +217,11 @@
TargetSP target_sp(GetSP());
if (!target_sp)
return LLDB_RECORD_RESULT(data);
-
- auto stats_up = std::make_unique<StructuredData::Dictionary>();
- int i = 0;
- for (auto &Entry : target_sp->GetStatistics()) {
- std::string Desc = lldb_private::GetStatDescription(
- static_cast<lldb_private::StatisticKind>(i));
- stats_up->AddIntegerItem(Desc, Entry);
- i += 1;
- }
-
- data.m_impl_up->SetObjectSP(std::move(stats_up));
+ std::string json_text;
+ llvm::raw_string_ostream stream(json_text);
+ llvm::json::Value json = target_sp->ReportStatistics(StatsDumpOptions());
+ stream << json;
+ data.m_impl_up->SetObjectSP(StructuredData::ParseJSON(stream.str()));
return LLDB_RECORD_RESULT(data);
}
@@ -237,7 +231,7 @@
TargetSP target_sp(GetSP());
if (!target_sp)
return;
- return target_sp->SetCollectingStats(v);
+ return target_sp->GetStatistics().SetCollectingStats(v);
}
bool SBTarget::GetCollectingStats() {
@@ -246,7 +240,7 @@
TargetSP target_sp(GetSP());
if (!target_sp)
return false;
- return target_sp->GetCollectingStats();
+ return target_sp->GetStatistics().GetCollectingStats();
}
SBProcess SBTarget::LoadCore(const char *core_file) {
Index: lldb/packages/Python/lldbsuite/test/lldbtest.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -766,6 +766,20 @@
"""Return absolute path to an artifact in the test's build directory."""
return os.path.join(self.getBuildDir(), name)
+ def getShlibBuildArtifact(self, DYLIB_NAME):
+ """Return absolute path to a shared library artifact given the library
+ name as it is known from the Makefile in DYLIB_NAME.
+
+ Each platform has different prefixes and extensions for shared
+ librairies when they get built by the Makefile. This function
+ allows tests to specify the DYLIB_NAME from the Makefile and it
+ will return the correct path for the built shared library in the
+ build artifacts directory
+ """
+ ctx = self.platformContext
+ dylibName = ctx.shlib_prefix + DYLIB_NAME + '.' + ctx.shlib_extension
+ return self.getBuildArtifact(dylibName)
+
def getSourcePath(self, name):
"""Return absolute path to a file in the test's source directory."""
return os.path.join(self.getSourceDir(), name)
Index: lldb/include/lldb/lldb-forward.h
===================================================================
--- lldb/include/lldb/lldb-forward.h
+++ lldb/include/lldb/lldb-forward.h
@@ -118,6 +118,7 @@
class MemoryHistory;
class MemoryRegionInfo;
class MemoryRegionInfos;
+struct StatsDumpOptions;
class Module;
class ModuleList;
class ModuleSpec;
Index: lldb/include/lldb/Utility/ElapsedTime.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/Utility/ElapsedTime.h
@@ -0,0 +1,56 @@
+//===-- ElapsedTime.h -------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UTILITY_ELAPSEDTIME_H
+#define LLDB_UTILITY_ELAPSEDTIME_H
+
+#include <chrono>
+namespace lldb_private {
+
+/// A class that measures elapsed time.
+///
+/// This class is designed to help gather timing metrics within LLDB where
+/// objects have optional Duration variables that get updated with elapsed
+/// times. This helps LLDB measure metrics for many things that are then
+/// reported in LLDB commands.
+///
+/// Objects that need to measure elapsed times should have a variable with of
+/// type "ElapsedTime::Duration m_time_xxx;" which can then be used in the
+/// constructor of this class inside a scope that wants to measure something:
+///
+/// ElapsedTime elapsed(m_time_xxx);
+/// // Do some work
+///
+/// This class will update the m_time_xxx variable with the elapsed time when
+/// the object goes out of scope. The "m_time_xxx" variable will be incremented
+/// when the class goes out of scope. This allows a variable to measure
+/// something that might happen in stages at different times, like resolving a
+/// breakpoint each time a new shared library is loaded.
+class ElapsedTime {
+public:
+ using Clock = std::chrono::high_resolution_clock;
+ using Duration = std::chrono::duration<double>;
+ using Timepoint = std::chrono::time_point<Clock>;
+ /// Set to the start time when the object is created.
+ Timepoint m_start_time;
+ /// The elapsed time in seconds to update when this object goes out of scope.
+ ElapsedTime::Duration &m_elapsed_time;
+
+public:
+ ElapsedTime(ElapsedTime::Duration &opt_time) : m_elapsed_time(opt_time) {
+ m_start_time = Clock::now();
+ }
+ ~ElapsedTime() {
+ Duration elapsed = Clock::now() - m_start_time;
+ m_elapsed_time += elapsed;
+ }
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_UTILITY_ELAPSEDTIME_H
Index: lldb/include/lldb/Target/Target.h
===================================================================
--- lldb/include/lldb/Target/Target.h
+++ lldb/include/lldb/Target/Target.h
@@ -28,6 +28,7 @@
#include "lldb/Target/ExecutionContextScope.h"
#include "lldb/Target/PathMappingList.h"
#include "lldb/Target/SectionLoadHistory.h"
+#include "lldb/Target/Statistics.h"
#include "lldb/Target/ThreadSpec.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/Broadcaster.h"
@@ -1451,23 +1452,26 @@
// Utilities for `statistics` command.
private:
- std::vector<uint32_t> m_stats_storage;
- bool m_collecting_stats = false;
+ // Target metrics storage.
+ TargetStats m_stats;
public:
- void SetCollectingStats(bool v) { m_collecting_stats = v; }
-
- bool GetCollectingStats() { return m_collecting_stats; }
-
- void IncrementStats(lldb_private::StatisticKind key) {
- if (!GetCollectingStats())
- return;
- lldbassert(key < lldb_private::StatisticKind::StatisticMax &&
- "invalid statistics!");
- m_stats_storage[key] += 1;
- }
+ /// Get metrics associated with this target in JSON format.
+ ///
+ /// Target metrics help measure timings and information that is contained in
+ /// a target. These are designed to help measure performance of a debug
+ /// session as well as represent the current state of the target, like
+ /// information on the currently modules, currently set breakpoints and more.
+ ///
+ /// \param [in] modules
+ /// If true, include an array of metrics for each module loaded in the
+ /// target.
+ //
+ /// \return
+ /// Returns a JSON value that contains all target metrics.
+ llvm::json::Value ReportStatistics(const StatsDumpOptions &options);
- std::vector<uint32_t> GetStatistics() { return m_stats_storage; }
+ TargetStats &GetStatistics() { return m_stats; }
private:
/// Construct with optional file and arch.
Index: lldb/include/lldb/Target/Statistics.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/Target/Statistics.h
@@ -0,0 +1,95 @@
+//===-- Statistics.h --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TARGET_STATISTICS_H
+#define LLDB_TARGET_STATISTICS_H
+
+#include <chrono>
+#include <vector>
+
+#include "lldb/Utility/ElapsedTime.h"
+#include "lldb/Utility/Stream.h"
+#include "lldb/lldb-forward.h"
+#include "llvm/Support/JSON.h"
+
+namespace lldb_private {
+
+struct SuccessFailStats {
+ void Notify(bool success);
+
+ llvm::json::Value ToJSON() const;
+
+ uint32_t successes = 0;
+ uint32_t failures = 0;
+};
+
+struct StatsDumpOptions {
+ bool dump_modules = false;
+ bool dump_breakpoints = false;
+ bool dump_breakpoint_locations = false;
+ bool dump_settings = false;
+
+ /// Set all dump options to true.
+ void SetAll() {
+ dump_modules = true;
+ dump_breakpoints = true;
+ dump_breakpoint_locations = true;
+ dump_settings = true;
+ }
+};
+
+struct ModuleStats {
+ llvm::json::Value ToJSON() const;
+
+ std::string path;
+ std::string uuid;
+ std::string triple;
+ uint64_t debug_info_size = 0;
+ double debug_parse_time = 0.0;
+ double debug_index_time = 0.0;
+ double symtab_parse_time = 0.0;
+ double symtab_names_time = 0.0;
+};
+
+class TargetStats {
+public:
+ llvm::json::Value ToJSON(Target &target, const StatsDumpOptions &options);
+
+ void CollectModuleStats(Target &target);
+
+ void SetLaunchOrAttachTime();
+
+ void SetFirstPrivateStopTime();
+
+ void SetFirstPublicStopTime();
+
+ ElapsedTime::Duration &GetCreateTime() { return create_time; }
+
+ void NotifyExprEval(bool success) { return m_expr_eval.Notify(success); }
+ void NotifyFrameVar(bool success) { return m_frame_var.Notify(success); }
+ void SetCollectingStats(bool v) { m_collecting_stats = v; }
+ bool GetCollectingStats() { return m_collecting_stats; }
+
+protected:
+ // Collecting stats can be set to true to collect stats that are expensive
+ // to collect. By default all stats that are cheap to collect are enabled.
+ // This settings is here to maintain compatibility with "statistics enable"
+ // and "statistics disable".
+ bool m_collecting_stats = false;
+ std::vector<ModuleStats> m_modules;
+ ElapsedTime::Duration create_time{0.0};
+ llvm::Optional<std::chrono::steady_clock::time_point> launch_or_attach_time;
+ llvm::Optional<std::chrono::steady_clock::time_point> first_private_stop_time;
+ llvm::Optional<std::chrono::steady_clock::time_point> first_public_stop_time;
+ SuccessFailStats m_expr_eval;
+ SuccessFailStats m_frame_var;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_STATISTICS_H
Index: lldb/include/lldb/Target/Process.h
===================================================================
--- lldb/include/lldb/Target/Process.h
+++ lldb/include/lldb/Target/Process.h
@@ -240,10 +240,11 @@
~ProcessModID() = default;
- void BumpStopID() {
- m_stop_id++;
+ uint32_t BumpStopID() {
+ const uint32_t prev_stop_id = m_stop_id++;
if (!IsLastResumeForUserExpression())
m_last_natural_stop_id++;
+ return prev_stop_id;
}
void BumpMemoryID() { m_memory_id++; }
Index: lldb/include/lldb/Target/PathMappingList.h
===================================================================
--- lldb/include/lldb/Target/PathMappingList.h
+++ lldb/include/lldb/Target/PathMappingList.h
@@ -9,10 +9,11 @@
#ifndef LLDB_TARGET_PATHMAPPINGLIST_H
#define LLDB_TARGET_PATHMAPPINGLIST_H
-#include <map>
-#include <vector>
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/Status.h"
+#include "llvm/Support/JSON.h"
+#include <map>
+#include <vector>
namespace lldb_private {
@@ -108,6 +109,11 @@
uint32_t GetModificationID() const { return m_mod_id; }
+ /// Return the path mappings as a JSON array.
+ ///
+ /// The array contains arrays of path mapping paths as strings.
+ llvm::json::Value ToJSON() const;
+
protected:
typedef std::pair<ConstString, ConstString> pair;
typedef std::vector<pair> collection;
Index: lldb/include/lldb/Symbol/SymbolFile.h
===================================================================
--- lldb/include/lldb/Symbol/SymbolFile.h
+++ lldb/include/lldb/Symbol/SymbolFile.h
@@ -19,6 +19,7 @@
#include "lldb/Symbol/Type.h"
#include "lldb/Symbol/TypeList.h"
#include "lldb/Symbol/TypeSystem.h"
+#include "lldb/Utility/ElapsedTime.h"
#include "lldb/Utility/XcodeSDK.h"
#include "lldb/lldb-private.h"
#include "llvm/ADT/DenseSet.h"
@@ -299,6 +300,35 @@
virtual void Dump(Stream &s);
+ /// Metrics gathering functions
+
+ /// Return the size in bytes of all debug information in the symbol file.
+ ///
+ /// If the debug information is contained in sections of an ObjectFile, then
+ /// this call should add the size of all sections that contain debug
+ /// information. Symbols the symbol tables are not considered debug
+ /// information for this call to make it easy and quick for this number to be
+ /// calculated. If the symbol file is all debug information, the size of the
+ /// entire file should be returned.
+ virtual uint64_t GetDebugInfoSize() = 0;
+
+ /// Return the time taken to parse the debug information.
+ ///
+ /// \returns llvm::None if no information has been parsed or if there is
+ /// no computational cost to parsing the debug information.
+ virtual ElapsedTime::Duration GetDebugInfoParseTime() {
+ return ElapsedTime::Duration(0.0);
+ }
+
+ /// Return the time it took to index the debug information in the object
+ /// file.
+ ///
+ /// \returns llvm::None if the file doesn't need to be indexed or if it
+ /// hasn't been indexed yet, or a valid duration if it has.
+ virtual ElapsedTime::Duration GetDebugInfoIndexTime() {
+ return ElapsedTime::Duration(0.0);
+ }
+
protected:
void AssertModuleLock();
virtual uint32_t CalculateNumCompileUnits() = 0;
Index: lldb/include/lldb/Symbol/SymbolContext.h
===================================================================
--- lldb/include/lldb/Symbol/SymbolContext.h
+++ lldb/include/lldb/Symbol/SymbolContext.h
@@ -18,6 +18,7 @@
#include "lldb/Symbol/LineEntry.h"
#include "lldb/Utility/Iterable.h"
#include "lldb/lldb-private.h"
+#include "llvm/Support/JSON.h"
namespace lldb_private {
@@ -313,6 +314,8 @@
SymbolContext &next_frame_sc,
Address &inlined_frame_addr) const;
+ llvm::json::Value ToJSON() const;
+
// Member variables
lldb::TargetSP target_sp; ///< The Target for a given query
lldb::ModuleSP module_sp; ///< The Module for a given query
Index: lldb/include/lldb/Core/Section.h
===================================================================
--- lldb/include/lldb/Core/Section.h
+++ lldb/include/lldb/Core/Section.h
@@ -89,6 +89,12 @@
void Clear() { m_sections.clear(); }
+ /// Get the debug information size from all sections that contain debug
+ /// information. Symbol tables are not considered part of the debug
+ /// information for this call, just known sections that contain debug
+ /// information.
+ uint64_t GetDebugInfoSize() const;
+
protected:
collection m_sections;
};
@@ -181,6 +187,13 @@
void SetIsThreadSpecific(bool b) { m_thread_specific = b; }
+ /// Returns true if this section contains debug information. Symbol tables
+ /// are not considered debug information since some symbols might contain
+ /// debug information (STABS, COFF) but not all symbols do, so to keep this
+ /// fast and simple only sections that contains only debug information should
+ /// return true.
+ bool ContainsOnlyDebugInfo() const;
+
/// Get the permissions as OR'ed bits from lldb::Permissions
uint32_t GetPermissions() const;
Index: lldb/include/lldb/Core/Module.h
===================================================================
--- lldb/include/lldb/Core/Module.h
+++ lldb/include/lldb/Core/Module.h
@@ -18,10 +18,11 @@
#include "lldb/Target/PathMappingList.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/ElapsedTime.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Status.h"
-#include "lldb/Utility/XcodeSDK.h"
#include "lldb/Utility/UUID.h"
+#include "lldb/Utility/XcodeSDK.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-forward.h"
@@ -933,6 +934,15 @@
bool m_match_name_after_lookup = false;
};
+ /// Get the symbol table parse time metric.
+ ///
+ /// The value is returned as a reference to allow it to be updated by the
+ /// ElapsedTime
+ ElapsedTime::Duration &GetSymtabParseTime() { return m_symtab_parse_time; }
+ ElapsedTime::Duration &GetSymtabNamesTime() { return m_symtab_names_time; }
+
+ uint64_t GetDebugInfoSize();
+
protected:
// Member Variables
mutable std::recursive_mutex m_mutex; ///< A mutex to keep this object happy
@@ -959,6 +969,14 @@
///by \a m_file.
uint64_t m_object_offset = 0;
llvm::sys::TimePoint<> m_object_mod_time;
+ /// We store a symbol table parse time duration here because we might have
+ /// an object file and a symbol file which both have symbol tables. The parse
+ /// time for the symbol tables can be aggregated here.
+ ElapsedTime::Duration m_symtab_parse_time{0.0};
+ /// We store a symbol named index time duration here because we might have
+ /// an object file and a symbol file which both have symbol tables. The parse
+ /// time for the symbol tables can be aggregated here.
+ ElapsedTime::Duration m_symtab_names_time{0.0};
/// DataBuffer containing the module image, if it was provided at
/// construction time. Otherwise the data will be retrieved by mapping
Index: lldb/include/lldb/Core/FileSpecList.h
===================================================================
--- lldb/include/lldb/Core/FileSpecList.h
+++ lldb/include/lldb/Core/FileSpecList.h
@@ -11,7 +11,7 @@
#if defined(__cplusplus)
#include "lldb/Utility/FileSpec.h"
-
+#include "llvm/Support/JSON.h"
#include <vector>
#include <cstddef>
@@ -197,6 +197,8 @@
const_iterator begin() const { return m_files.begin(); }
const_iterator end() const { return m_files.end(); }
+ llvm::json::Value ToJSON() const;
+
protected:
collection m_files; ///< A collection of FileSpec objects.
};
Index: lldb/include/lldb/Breakpoint/BreakpointLocation.h
===================================================================
--- lldb/include/lldb/Breakpoint/BreakpointLocation.h
+++ lldb/include/lldb/Breakpoint/BreakpointLocation.h
@@ -18,6 +18,8 @@
#include "lldb/Utility/UserID.h"
#include "lldb/lldb-private.h"
+#include "llvm/Support/JSON.h"
+
namespace lldb_private {
/// \class BreakpointLocation BreakpointLocation.h
@@ -230,7 +232,7 @@
/// \b true if the target should stop at this breakpoint and \b
/// false not.
bool InvokeCallback(StoppointCallbackContext *context);
-
+
/// Report whether the callback for this location is synchronous or not.
///
/// \return
@@ -279,6 +281,9 @@
/// Returns the breakpoint location ID.
lldb::break_id_t GetID() const { return m_loc_id; }
+ /// Returns the breakpoint location metrics as JSON.
+ llvm::json::Value GetMetrics(const StatsDumpOptions &options);
+
protected:
friend class BreakpointSite;
friend class BreakpointLocationList;
Index: lldb/include/lldb/Breakpoint/Breakpoint.h
===================================================================
--- lldb/include/lldb/Breakpoint/Breakpoint.h
+++ lldb/include/lldb/Breakpoint/Breakpoint.h
@@ -22,6 +22,7 @@
#include "lldb/Breakpoint/Stoppoint.h"
#include "lldb/Breakpoint/StoppointHitCounter.h"
#include "lldb/Core/SearchFilter.h"
+#include "lldb/Utility/ElapsedTime.h"
#include "lldb/Utility/Event.h"
#include "lldb/Utility/StringList.h"
#include "lldb/Utility/StructuredData.h"
@@ -576,6 +577,12 @@
static lldb::BreakpointSP CopyFromBreakpoint(lldb::TargetSP new_target,
const Breakpoint &bp_to_copy_from);
+ /// Get statistics associated with this breakpoint in JSON format.
+ llvm::json::Value GetStatistics(const StatsDumpOptions &options);
+
+ /// Get the time it took to resolve all locations in this breakpoint.
+ ElapsedTime::Duration GetResolveTime() const { return m_resolve_time; }
+
protected:
friend class Target;
// Protected Methods
@@ -653,6 +660,8 @@
BreakpointName::Permissions m_permissions;
+ ElapsedTime::Duration m_resolve_time{0.0};
+
void SendBreakpointChangedEvent(lldb::BreakpointEventType eventKind);
void SendBreakpointChangedEvent(BreakpointEventData *data);
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits