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 &section : 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
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to