clayborg created this revision.
clayborg added reviewers: jingham, labath, aprantl, JDevlieghere, aadsm, 
wallace.
Herald added subscribers: dang, pengfei, arphaman, mgorny, emaste.
clayborg requested review of this revision.
Herald added subscribers: lldb-commits, sstefan1, MaskRay.
Herald added a reviewer: jdoerfert.
Herald added a project: LLDB.

The new target metrics command will report internal timings and information 
that can be used to track performance in LLDB. The information can also be used 
to help reproduce the debug session and exact breakpoint state if the 
appropriate options are used. This allows users to report performance issues or 
bugs where breakpoints were not resolved and LLDB engineers can use this 
information to track the issue.

The command emits JSON so that the structured data can be used to report 
timings to databases for performance tracking.

(lldb) target metrics
{

  "firstStopTime": 0.33988644499999998,
  "launchOrAttachTime": 0.31808012899999999,
  "targetCreateTime": 31.168737819,
  "totalBreakpointResolveTime": 6.6650758780000006,
  "totalDebugInfoIndexTime": 0,
  "totalDebugInfoParseTime": 4.2728297900000118,
  "totalDebugInfoSize": 6581323413,
  "totalSymbolTableIndexTime": 17.815137097000008,
  "totalSymbolTableParseTime": 12.261250563000004

}

With no options we report some timings at the top level:

"launchOrAttachTime" is the time it takes from the time launch or attach is 
started, to when we hit our first private eStateStopped state
"firstStopTime" is the time it takes to stop at our first public stop. This 
time only has meaning if you run your program to a known breakpoint and want to 
compare timings with new versions of LLDB. This is because your program might 
not stop at a breakpoint right away unless a targetted breakpoint has been set 
for measuring.
"targetCreateTime" is the time it takes to set the main executable in the 
target and all dependent shared libraries. This time will include the symbol 
preloading if symbol preloading is enabled. This time can help is measure how 
long it takes to parse the symbol tables, index the symbol tables, and index 
the debug info along with any debug info parsing that might be required.
"totalBreakpointResolveTime" is the time it took to resolve any breakpoints 
that were set. If you specify the "--breakpoints" or "-b" option, you can get a 
breakdown of resolve time on a per breakpoint level.
"totalDebugInfoIndexTime" is the time it took to index the debug information 
for all current modules in the target.
"totalDebugInfoParseTime" is the time it took to parse the debug information 
for all current modules in the target.
"totalDebugInfoSize" is the sum of all debug information sections from all 
object files in the module.
"totalSymbolTableIndexTime" is the time it took to parse the symbol tables for 
each object file in all modules.
"totalSymbolTableParseTime" is the time it took to index the symbol tables for 
each object file in all modules.

Adding the "--modules" options will include a top level "modules" array that 
contains full details on each module, breaking down the debug info parse/index 
times and size, symbol table parse/index times along with enough info to 
identify the module (path, triple, uuid).

(lldb) target metrics --modules
...

  "modules": [
    {
      "debugInfoIndexTime": 0,
      "debugInfoParseTime": 0.054413763000000004,
      "debugInfoSize": 18118626,
      "path": "/Users/gclayton/Documents/src/lldb/main/Debug/bin/lldb",
      "symbolTableIndexTime": 0.22065103899999999,
      "symbolTableParseTime": 0.138976874,
      "triple": "x86_64-apple-macosx11.0.0",
      "uuid": "F59B8D3F-67B1-35EC-A1AA-E0ABB5CB3601"
    },
    {
      "debugInfoIndexTime": 0,
      "debugInfoParseTime": 0,
      "debugInfoSize": 0,
      "path": "/usr/lib/dyld",
      "symbolTableIndexTime": 0.01367178,
      "symbolTableParseTime": 0.0093677970000000006,
      "triple": "x86_64-apple-macosx11.6.0",
      "uuid": "0CC19410-FD43-39AE-A32A-50273F8303A4"
    },
    {
      "debugInfoIndexTime": 0,
      "debugInfoParseTime": 4.2184160270000115,
      "debugInfoSize": 6563204787,
      "path": 
"/Users/gclayton/Documents/src/lldb/main/Debug/bin/LLDB.framework/Versions/A/LLDB",
      "symbolTableIndexTime": 17.112896844000002,
      "symbolTableParseTime": 11.240680348,
      "triple": "x86_64-apple-macosx11.0.0",
      "uuid": "CB1763B7-BB3D-38A1-AB32-237E59244745"
    },

Adding the "--breakpoints option will include details on all breakpoints 
including the breakpoint ID, individual resolve time, and "details" which 
contains the serialized breakpoint settings that were already built into each 
breakpoint, so we can know the exact details of how a breakpoint was set.

(lldb) target metrics --breakpoints
{

  "breakpoints": [
    {
      "details": {...},
      "id": 1,
      "resolveTime": 2.2936575110000001
    }

Adding the "--locations" options will dump the details of all resolved 
breakpoint locations withing each breakpoint:

(lldb) target metrics --locations
{

  "breakpoints": [
    {
      ...,
      "locations": [
        {
          "enabled": true,
          "fileAddress": 15783486,
          "hardward": false,
          "hitCount": 0,
          "id": 1,
          "indirect": false,
          "loadAddress": 4319532606,
          "reexported": false,
          "resolved": true,
          "symbolContext": {
            "compUnit": 
"/Users/gclayton/Documents/src/lldb/main/llvm-project/lldb/source/Target/Target.cpp",
            "function": "lldb_private::Target::Target(lldb_private::Debugger&, 
lldb_private::ArchSpec const&, std::__1::shared_ptr<lldb_private::Platform> 
const&, bool)",
            "module": "CB1763B7-BB3D-38A1-AB32-237E59244745",
            "sourceColumn": 1,
            "sourceFile": 
"/Users/gclayton/Documents/src/lldb/main/llvm-project/lldb/source/Target/Target.cpp",
            "sourceLine": 123,
            "symbol": "lldb_private::Target::Target(lldb_private::Debugger&, 
lldb_private::ArchSpec const&, std::__1::shared_ptr<lldb_private::Platform> 
const&, bool)"
          }
        },
        {
          "enabled": true,
          "fileAddress": 15787038,
          "hardward": false,
          "hitCount": 0,
          "id": 2,
          "indirect": false,
          "loadAddress": 4319536158,
          "reexported": false,
          "resolved": true,
          "symbolContext": {
            "compUnit": 
"/Users/gclayton/Documents/src/lldb/main/llvm-project/lldb/source/Target/Target.cpp",
            "function": "lldb_private::Target::Target(lldb_private::Debugger&, 
lldb_private::ArchSpec const&, std::__1::shared_ptr<lldb_private::Platform> 
const&, bool)",
            "module": "CB1763B7-BB3D-38A1-AB32-237E59244745",
            "sourceColumn": 1,
            "sourceFile": 
"/Users/gclayton/Documents/src/lldb/main/llvm-project/lldb/source/Target/Target.cpp",
            "sourceLine": 123,
            "symbol": "lldb_private::Target::Target(lldb_private::Debugger&, 
lldb_private::ArchSpec const&, std::__1::shared_ptr<lldb_private::Platform> 
const&, bool)"
          }
        }
      ],
      "resolveTime": 4.3714183670000004


Repository:
  rG LLVM Github Monorepo

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/Metrics.h
  lldb/include/lldb/Target/PathMappingList.h
  lldb/include/lldb/Target/Process.h
  lldb/include/lldb/Target/Target.h
  lldb/include/lldb/Utility/Timer.h
  lldb/include/lldb/lldb-forward.h
  lldb/packages/Python/lldbsuite/test/lldbtest.py
  lldb/source/Breakpoint/Breakpoint.cpp
  lldb/source/Breakpoint/BreakpointLocation.cpp
  lldb/source/Commands/CommandObjectTarget.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/Metrics.cpp
  lldb/source/Target/PathMappingList.cpp
  lldb/source/Target/Process.cpp
  lldb/source/Target/Target.cpp
  lldb/test/API/commands/target/metrics/Makefile
  lldb/test/API/commands/target/metrics/TestTargetMetrics.py
  lldb/test/API/commands/target/metrics/a.cpp
  lldb/test/API/commands/target/metrics/main.cpp

Index: lldb/test/API/commands/target/metrics/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/commands/target/metrics/main.cpp
@@ -0,0 +1,4 @@
+extern int a_function();
+extern int b_function();
+
+int main(int argc, char const *argv[]) { return a_function(); }
Index: lldb/test/API/commands/target/metrics/a.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/commands/target/metrics/a.cpp
@@ -0,0 +1 @@
+int a_function() { return 500; }
Index: lldb/test/API/commands/target/metrics/TestTargetMetrics.py
===================================================================
--- /dev/null
+++ lldb/test/API/commands/target/metrics/TestTargetMetrics.py
@@ -0,0 +1,363 @@
+"""
+Test that loading of dependents works correctly for all the potential
+combinations.
+"""
+
+
+import lldb
+import json
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+@skipIfWindows # Windows deals differently with shared libs.
+class TargetDependentsTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def setUp(self):
+        TestBase.setUp(self)
+        self.build()
+
+    def has_exactly_one_image(self, matching, msg=""):
+        self.expect(
+            "image list",
+            "image list should contain at least one image",
+            substrs=['[  0]'])
+        should_match = not matching
+        self.expect(
+            "image list", msg, matching=should_match, substrs=['[  1]'])
+
+
+    def verify_key_presence(self, dict, description, keys_exist, keys_missing=None):
+        if keys_exist:
+            for key in keys_exist:
+                self.assertTrue(key in dict,
+                        'Make sure "%s" key exists in %s dictionary' % (key, description))
+        if keys_missing:
+            for key in keys_missing:
+                self.assertTrue(key not in dict,
+                        'Make sure "%s" key does not exists in %s dictionary' % (key, description))
+
+    def find_module_in_metrics(self, path, metrics):
+        modules = metrics['modules']
+        for module in modules:
+            if module['path'] == path:
+                return module
+        return None
+
+    def test_metrics_default(self):
+        """Test default behavior of "target metrics" command.
+
+        Output expected to be something like:
+
+        (lldb) target metrics
+        {
+          "targetCreateTime": 0.26566899599999999,
+          "totalBreakpointResolveTime": 0.0031409419999999999,
+          "totalDebugInfoIndexTime": 0,
+          "totalDebugInfoParseTime": 0,
+          "totalDebugInfoSize": 2193,
+          "totalSymbolTableIndexTime": 0.056834488000000009,
+          "totalSymbolTableParseTime": 0.093979421999999979
+        }
+
+        """
+        exe = self.getBuildArtifact("a.out")
+        self.runCmd("target create  " + exe, CURRENT_EXECUTABLE_SET)
+        return_obj = lldb.SBCommandReturnObject()
+        self.ci.HandleCommand("target metrics", return_obj, False)
+        metrics_json = return_obj.GetOutput()
+        metrics = json.loads(metrics_json)
+        keys_exist = [
+            'targetCreateTime',
+            'totalBreakpointResolveTime',
+            'totalDebugInfoIndexTime',
+            'totalDebugInfoParseTime',
+            'totalDebugInfoSize',
+            'totalSymbolTableIndexTime',
+            'totalSymbolTableParseTime',
+        ]
+        keys_missing = [
+            'breakpoints',
+            'modules',
+            'settings'
+        ]
+        self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing)
+        self.assertTrue(metrics['targetCreateTime'] >  0.0)
+        self.assertTrue(metrics['totalBreakpointResolveTime'] == 0.0)
+        self.assertTrue(metrics['totalDebugInfoSize'] > 0)
+        self.assertTrue(metrics['totalSymbolTableIndexTime'] > 0.0)
+        self.assertTrue(metrics['totalSymbolTableParseTime'] > 0.0)
+
+    def test_metrics_modules(self):
+        """Test behavior of "target metrics --modules" command.
+
+        Output expected to be something like:
+
+        (lldb) target metrics --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": "/Users/gclayton/Documents/src/lldb/main/Debug/lldb-test-build.noindex/commands/target/metrics/TestTargetMetrics.test_metrics_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": "/Users/gclayton/Documents/src/lldb/main/Debug/lldb-test-build.noindex/commands/target/metrics/TestTargetMetrics.test_metrics_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")
+        self.runCmd("target create  " + exe, CURRENT_EXECUTABLE_SET)
+        return_obj = lldb.SBCommandReturnObject()
+        self.ci.HandleCommand("target metrics --modules", return_obj, False)
+        output = return_obj.GetOutput()
+        metrics = json.loads(output)
+        keys_exist = [
+            'targetCreateTime',
+            'totalBreakpointResolveTime',
+            'totalDebugInfoIndexTime',
+            'totalDebugInfoParseTime',
+            'totalDebugInfoSize',
+            'totalSymbolTableIndexTime',
+            'totalSymbolTableParseTime',
+            'modules',
+        ]
+        keys_missing = [
+            'breakpoints',
+            'settings'
+        ]
+        self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing)
+        exe_module = self.find_module_in_metrics(exe, metrics)
+        module_keys = [
+            'debugInfoIndexTime',
+            'debugInfoParseTime',
+            'debugInfoSize',
+            'path',
+            'symbolTableIndexTime',
+            'symbolTableParseTime',
+            'triple',
+            'uuid',
+        ]
+        self.assertTrue(exe_module != None)
+        self.verify_key_presence(exe_module, 'module dict for "%s"' % (exe),
+                                 module_keys)
+        shlib_path = self.getShlibBuildArtifact('load_a')
+        shlib_module = self.find_module_in_metrics(shlib_path, metrics)
+        self.assertTrue(shlib_module != None)
+        self.verify_key_presence(shlib_module, 'module dict for "%s"' % (exe),
+                                 module_keys)
+
+    def test_metrics_breakpoints(self):
+        """Test behavior of "target metrics --breakpoints" command.
+
+        Output expected to be something like:
+
+        (lldb) target metrics --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
+              },
+            ]
+        }
+
+        """
+        exe = self.getBuildArtifact("a.out")
+        self.runCmd("target create  " + exe, CURRENT_EXECUTABLE_SET)
+        self.runCmd("b main.cpp:7")
+        self.runCmd("b a_function")
+        return_obj = lldb.SBCommandReturnObject()
+        self.ci.HandleCommand("target metrics --breakpoints", return_obj, False)
+        metrics_json = return_obj.GetOutput()
+        metrics = json.loads(metrics_json)
+        keys_exist = [
+            'targetCreateTime',
+            'totalBreakpointResolveTime',
+            'totalDebugInfoIndexTime',
+            'totalDebugInfoParseTime',
+            'totalDebugInfoSize',
+            'totalSymbolTableIndexTime',
+            'totalSymbolTableParseTime',
+            'breakpoints',
+        ]
+        keys_missing = [
+            'modules',
+            'settings'
+        ]
+        self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing)
+        self.assertTrue(metrics['totalBreakpointResolveTime'] > 0.0)
+        self.assertTrue(metrics['totalDebugInfoParseTime'] > 0.0)
+        breakpoints = metrics['breakpoints']
+        bp_keys_exist = [
+            'details',
+            'id',
+            'resolveTime'
+        ]
+        bk_keys_missing = [
+            'locations'
+        ]
+        for breakpoint in breakpoints:
+            self.verify_key_presence(breakpoint, 'metrics["breakpoints"]',
+                                     bp_keys_exist, bk_keys_missing)
+
+    def test_metrics_locations(self):
+        """Test behavior of "target metrics --locations" command.
+
+        Output expected to be something like:
+
+        (lldb) target metrics --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
+              },
+            ]
+          }
+
+        """
+        exe = self.getBuildArtifact("a.out")
+        self.runCmd("target create  " + exe, CURRENT_EXECUTABLE_SET)
+        self.runCmd("b main.cpp:7")
+        self.runCmd("b a_function")
+        return_obj = lldb.SBCommandReturnObject()
+        self.ci.HandleCommand("target metrics --locations", return_obj, False)
+        metrics_json = return_obj.GetOutput()
+        metrics = json.loads(metrics_json)
+        keys_exist = [
+            'targetCreateTime',
+            'totalBreakpointResolveTime',
+            'totalDebugInfoIndexTime',
+            'totalDebugInfoParseTime',
+            'totalDebugInfoSize',
+            'totalSymbolTableIndexTime',
+            'totalSymbolTableParseTime',
+            'breakpoints',
+        ]
+        keys_missing = [
+            'modules',
+            'settings'
+        ]
+        self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing)
+        self.assertTrue(metrics['totalBreakpointResolveTime'] > 0.0)
+        self.assertTrue(metrics['totalDebugInfoParseTime'] > 0.0)
+        breakpoints = metrics['breakpoints']
+        bp_keys_exist = [
+            'details',
+            'id',
+            'resolveTime',
+            'locations'
+        ]
+        for breakpoint in breakpoints:
+            self.verify_key_presence(breakpoint, 'metrics["breakpoints"]',
+                                     bp_keys_exist, None)
+
+    def test_metrics_settings(self):
+        """Test behavior of "target metrics --settings" command.
+
+        Output expected to be something like:
+
+        (lldb) target metrics --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/TestTargetMetrics.test_metrics_settings/a.out",
+            "target.clang-module-search-paths": [],
+            "target.debug-file-search-paths": [],
+            "target.exec-search-paths": [
+              "/.../commands/target/metrics/TestTargetMetrics.test_metrics_settings"
+            ],
+            "target.inline-breakpoint-strategy": "always",
+            "target.preload-symbols": true,
+            "target.run-args": [],
+            "target.skip-prologue": true,
+            "target.source-map": []
+            },
+          }
+
+        """
+        exe = self.getBuildArtifact("a.out")
+        self.runCmd("target create " + exe, CURRENT_EXECUTABLE_SET)
+        return_obj = lldb.SBCommandReturnObject()
+        self.ci.HandleCommand("target metrics --settings", return_obj, False)
+        metrics_json = return_obj.GetOutput()
+        f = open('/tmp/a', 'w')
+        f.write(metrics_json)
+        metrics = json.loads(metrics_json)
+        keys_exist = [
+            'targetCreateTime',
+            'totalBreakpointResolveTime',
+            'totalDebugInfoIndexTime',
+            'totalDebugInfoParseTime',
+            'totalDebugInfoSize',
+            'totalSymbolTableIndexTime',
+            'totalSymbolTableParseTime',
+            'settings'
+        ]
+        keys_missing = [
+            'breakpoints',
+            'modules',
+        ]
+        self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing)
+        settings = metrics['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_key_presence(settings, 'metrics["settings"]', settings_keys_exist)
Index: lldb/test/API/commands/target/metrics/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/commands/target/metrics/Makefile
@@ -0,0 +1,10 @@
+LD_EXTRAS := -L. -lload_a
+CXX_SOURCES := main.cpp
+
+a.out: libload_a
+
+include Makefile.rules
+
+libload_a:
+	$(MAKE) -f $(MAKEFILE_RULES) \
+		DYLIB_ONLY=YES DYLIB_NAME=load_a DYLIB_CXX_SOURCES=a.cpp
Index: lldb/source/Target/Target.cpp
===================================================================
--- lldb/source/Target/Target.cpp
+++ lldb/source/Target/Target.cpp
@@ -1400,6 +1400,7 @@
   ClearModules(false);
 
   if (executable_sp) {
+    ElapsedTime elapsed(m_metrics.GetCreateTime());
     LLDB_SCOPED_TIMERF("Target::SetExecutableModule (executable = '%s')",
                        executable_sp->GetFileSpec().GetPath().c_str());
 
@@ -2908,6 +2909,7 @@
 void Target::ClearAllLoadedSections() { m_section_load_history.Clear(); }
 
 Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
+  m_metrics.SetLaunchOrAttachTime();
   Status error;
   Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_TARGET));
 
@@ -3119,6 +3121,7 @@
 }
 
 Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) {
+  m_metrics.SetLaunchOrAttachTime();
   auto state = eStateInvalid;
   auto process_sp = GetProcessSP();
   if (process_sp) {
@@ -4464,3 +4467,9 @@
   else
     return m_mutex;
 }
+
+/// Get metrics associated with this target in JSON format.
+llvm::json::Value Target::GatherMetrics(const MetricDumpOptions &options) {
+  m_metrics.CollectModuleMetrics(*this);
+  return m_metrics.ToJSON(*this, options);
+}
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().GetMetrics().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().GetMetrics().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/Metrics.cpp
===================================================================
--- /dev/null
+++ lldb/source/Target/Metrics.cpp
@@ -0,0 +1,187 @@
+//===-- Metrics.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/Metrics.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 TargetMetrics::CollectModuleMetrics(Target &target) {
+  m_module_metrics.clear();
+  for (ModuleSP module_sp : target.GetImages().Modules()) {
+    ModuleMetrics module_metrics;
+    module_metrics.path = module_sp->GetFileSpec().GetPath();
+    module_metrics.uuid = module_sp->GetUUID().GetAsString();
+    module_metrics.triple = module_sp->GetArchitecture().GetTriple().str();
+    SymbolFile *sym_file = module_sp->GetSymbolFile();
+    if (sym_file) {
+      module_metrics.debug_index_time = sym_file->GetDebugInfoIndexTime();
+      module_metrics.debug_parse_time = sym_file->GetDebugInfoParseTime();
+    }
+    module_metrics.symtab_parse_time = module_sp->GetSymtabParseTime();
+    module_metrics.symtab_names_time = module_sp->GetSymtabNamesTime();
+    module_metrics.debug_info_size = module_sp->GetDebugInfoSize();
+    m_module_metrics.push_back(std::move(module_metrics));
+  }
+}
+
+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 TargetMetrics::ToJSON(Target &target,
+                                  const MetricDumpOptions &options) {
+  CollectModuleMetrics(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_metrics : m_module_metrics) {
+    modules.push_back(module_metrics.ToJSON());
+    debug_info_size += module_metrics.debug_info_size;
+    debug_parse_time += module_metrics.debug_parse_time;
+    debug_index_time += module_metrics.debug_index_time;
+    symtab_parse_time += module_metrics.symtab_parse_time;
+    symtab_names_time += module_metrics.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->GetMetrics(options));
+    totalBreakpointResolveTime += bp->GetResolveTime();
+  }
+
+  // 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},
+  };
+  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);
+
+  return json::Value(std::move(target_metrics_json));
+}
+
+void TargetMetrics::SetLaunchOrAttachTime() {
+  launch_or_attach_time = std::chrono::steady_clock::now();
+  first_private_stop_time = llvm::None;
+}
+
+void TargetMetrics::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 TargetMetrics::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 ModuleMetrics::ToJSON() const {
+  json::Object module_metrics;
+  EmplaceSafeString(module_metrics, "path", path);
+  EmplaceSafeString(module_metrics, "uuid", uuid);
+  EmplaceSafeString(module_metrics, "triple", triple);
+  module_metrics.try_emplace("debugInfoParseTime", debug_parse_time);
+  module_metrics.try_emplace("debugInfoIndexTime", debug_index_time);
+  module_metrics.try_emplace("symbolTableParseTime", symtab_parse_time);
+  module_metrics.try_emplace("symbolTableIndexTime", symtab_names_time);
+  module_metrics.try_emplace("debugInfoSize", (int64_t)debug_info_size);
+  return module_metrics;
+}
Index: lldb/source/Target/CMakeLists.txt
===================================================================
--- lldb/source/Target/CMakeLists.txt
+++ lldb/source/Target/CMakeLists.txt
@@ -19,6 +19,7 @@
   Memory.cpp
   MemoryHistory.cpp
   MemoryRegionInfo.cpp
+  Metrics.cpp
   ModuleCache.cpp
   OperatingSystem.cpp
   PathMappingList.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
@@ -142,6 +142,10 @@
   // PluginInterface protocol
   lldb_private::ConstString GetPluginName() override;
 
+  uint64_t GetDebugInfoSize() override;
+  double GetDebugInfoParseTime() override;
+  double 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;
+}
+
+double SymbolFileDWARFDebugMap::GetDebugInfoParseTime() {
+  double time = 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)
+          time += symfile->GetDebugInfoParseTime();
+      }
+    }
+    return false; // Keep iterating
+  });
+  return time;
+}
+
+double SymbolFileDWARFDebugMap::GetDebugInfoIndexTime() {
+  double index_time = 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)
+          index_time += symfile->GetDebugInfoIndexTime();
+      }
+    }
+    return false; // Keep iterating
+  });
+  return index_time;
+}
Index: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
===================================================================
--- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -318,6 +318,12 @@
   /// Same as GetLanguage() but reports all C++ versions as C++ (no version).
   static lldb::LanguageType GetLanguageFamily(DWARFUnit &unit);
 
+  uint64_t GetDebugInfoSize() override;
+  double GetDebugInfoParseTime() override;
+  double GetDebugInfoIndexTime() override;
+
+  double &GetDebugInfoParseTimeRef() { return m_parse_time; }
+
 protected:
   typedef llvm::DenseMap<const DWARFDebugInfoEntry *, lldb_private::Type *>
       DIEToTypePtr;
@@ -519,6 +525,7 @@
   llvm::DenseMap<dw_offset_t, lldb_private::FileSpecList>
       m_type_unit_support_files;
   std::vector<uint32_t> m_lldb_cu_to_dwarf_unit;
+  double 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,21 @@
     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;
+}
+
+double SymbolFileDWARF::GetDebugInfoParseTime() { return m_parse_time; }
+
+double SymbolFileDWARF::GetDebugInfoIndexTime() {
+  if (m_index)
+    return m_index->GetIndexTime();
+  return 0.0;
+}
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
@@ -62,8 +62,11 @@
 
   virtual void Dump(Stream &s) = 0;
 
+  double GetIndexTime() { return m_index_time; }
+
 protected:
   Module &m_module;
+  double 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 = "target metrics" in {
+  def target_metrics_modules: Option<"modules", "m">, Group<1>,
+    Desc<"Include metrics for each module in the target.">;
+  def target_metrics_breakpoints: Option<"breakpoints", "b">, Group<1>,
+    Desc<"Include metrics for each breakpoint in the target.">;
+  def target_metrics_locations: Option<"locations", "l">, Group<1>,
+    Desc<"Include metrics for each breakpoint location in each breakpoint.">;
+  def target_metrics_settings: Option<"settings", "s">, Group<1>,
+    Desc<"Include important target settings in metrics.">;
+  def target_metrics_all: Option<"all", "a">, Group<1>,
+    Desc<"Include all metrics in the target.">;
+}
Index: lldb/source/Commands/CommandObjectTarget.cpp
===================================================================
--- lldb/source/Commands/CommandObjectTarget.cpp
+++ lldb/source/Commands/CommandObjectTarget.cpp
@@ -4990,6 +4990,77 @@
   ~CommandObjectMultiwordTargetStopHooks() override = default;
 };
 
+#pragma mark CommandObjectTargetMetrics
+
+#define LLDB_OPTIONS_target_metrics
+#include "CommandOptions.inc"
+
+class CommandObjectTargetMetrics : 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 = MetricDumpOptions();
+    }
+
+    llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+      return llvm::makeArrayRef(g_target_metrics_options);
+    }
+
+    MetricDumpOptions m_metric_options;
+  };
+
+  CommandObjectTargetMetrics(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "target metrics",
+                            "Dump target metrics in JSON format", nullptr,
+                            eCommandRequiresTarget),
+        m_options() {}
+
+  ~CommandObjectTargetMetrics() override = default;
+
+  Options *GetOptions() override { return &m_options; }
+
+protected:
+  bool DoExecute(Args &command, CommandReturnObject &result) override {
+    Target &target = m_exe_ctx.GetTargetRef();
+    result.AppendMessageWithFormatv(
+        "{0:2}", target.GatherMetrics(m_options.m_metric_options));
+    result.SetStatus(eReturnStatusSuccessFinishResult);
+    return true;
+  }
+
+  CommandOptions m_options;
+};
+
 #pragma mark CommandObjectMultiwordTarget
 
 // CommandObjectMultiwordTarget
@@ -5005,6 +5076,8 @@
                  CommandObjectSP(new CommandObjectTargetDelete(interpreter)));
   LoadSubCommand("list",
                  CommandObjectSP(new CommandObjectTargetList(interpreter)));
+  LoadSubCommand("metrics",
+                 CommandObjectSP(new CommandObjectTargetMetrics(interpreter)));
   LoadSubCommand("select",
                  CommandObjectSP(new CommandObjectTargetSelect(interpreter)));
   LoadSubCommand("show-launch-environment",
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 MetricDumpOptions &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::GetMetrics(const MetricDumpOptions &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 > 0.0)
+    bp.try_emplace("resolveTime", m_resolve_time);
+  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/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 MetricDumpOptions;
 class Module;
 class ModuleList;
 class ModuleSpec;
Index: lldb/include/lldb/Utility/Timer.h
===================================================================
--- lldb/include/lldb/Utility/Timer.h
+++ lldb/include/lldb/Utility/Timer.h
@@ -83,6 +83,45 @@
 
 llvm::SignpostEmitter &GetSignposts();
 
+/// 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 "double 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.
+  double &m_elapsed_time;
+
+public:
+  ElapsedTime(double &opt_time) : m_elapsed_time(opt_time) {
+    m_start_time = Clock::now();
+  }
+  ~ElapsedTime() {
+    Duration elapsed = Clock::now() - m_start_time;
+    m_elapsed_time += elapsed.count();
+  }
+};
+
 } // namespace lldb_private
 
 // Use a format string because LLVM_PRETTY_FUNCTION might not be a string
Index: lldb/include/lldb/Target/Target.h
===================================================================
--- lldb/include/lldb/Target/Target.h
+++ lldb/include/lldb/Target/Target.h
@@ -26,6 +26,7 @@
 #include "lldb/Host/ProcessLaunchInfo.h"
 #include "lldb/Symbol/TypeSystem.h"
 #include "lldb/Target/ExecutionContextScope.h"
+#include "lldb/Target/Metrics.h"
 #include "lldb/Target/PathMappingList.h"
 #include "lldb/Target/SectionLoadHistory.h"
 #include "lldb/Target/ThreadSpec.h"
@@ -1178,6 +1179,30 @@
   ///     if none can be found.
   llvm::Expected<lldb_private::Address> GetEntryPointAddress();
 
+  // Utilities for the `target metrics` command
+private:
+  // Target metrics storage.
+  TargetMetrics m_metrics;
+
+public:
+  /// 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 GatherMetrics(const MetricDumpOptions &options);
+
+  /// Accessor for the target metrics object.
+  TargetMetrics &GetMetrics() { return m_metrics; }
+
   // Target Stop Hooks
   class StopHook : public UserID {
   public:
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/Target/Metrics.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/Target/Metrics.h
@@ -0,0 +1,74 @@
+//===-- Metrics.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_METRICS_H
+#define LLDB_TARGET_METRICS_H
+
+#include <chrono>
+#include <vector>
+
+#include "lldb/Utility/Stream.h"
+#include "lldb/lldb-forward.h"
+
+#include "llvm/Support/JSON.h"
+
+namespace lldb_private {
+
+struct MetricDumpOptions {
+  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 ModuleMetrics {
+  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 TargetMetrics {
+public:
+  llvm::json::Value ToJSON(Target &target, const MetricDumpOptions &options);
+
+  void CollectModuleMetrics(Target &target);
+
+  void SetLaunchOrAttachTime();
+
+  void SetFirstPrivateStopTime();
+
+  void SetFirstPublicStopTime();
+
+  double &GetCreateTime() { return create_time; }
+
+protected:
+  std::vector<ModuleMetrics> m_module_metrics;
+  double create_time;
+  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;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_METRICS_H
Index: lldb/include/lldb/Symbol/SymbolFile.h
===================================================================
--- lldb/include/lldb/Symbol/SymbolFile.h
+++ lldb/include/lldb/Symbol/SymbolFile.h
@@ -299,6 +299,31 @@
 
   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 double GetDebugInfoParseTime() { return 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 double GetDebugInfoIndexTime() { return 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
@@ -933,6 +933,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
+  double &GetSymtabParseTime() { return m_symtab_parse_time; }
+  double &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 +968,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.
+  double 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.
+  double 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 MetricDumpOptions &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
@@ -9,6 +9,7 @@
 #ifndef LLDB_BREAKPOINT_BREAKPOINT_H
 #define LLDB_BREAKPOINT_BREAKPOINT_H
 
+#include <atomic>
 #include <memory>
 #include <string>
 #include <unordered_set>
@@ -576,6 +577,12 @@
   static lldb::BreakpointSP CopyFromBreakpoint(lldb::TargetSP new_target,
       const Breakpoint &bp_to_copy_from);
 
+  /// Get metrics associated with this breakpoint in JSON format.
+  llvm::json::Value GetMetrics(const MetricDumpOptions &options);
+
+  /// Get the time it took to resolve all locations in this breakpoint.
+  double GetResolveTime() const { return m_resolve_time; }
+
 protected:
   friend class Target;
   // Protected Methods
@@ -653,6 +660,8 @@
 
   BreakpointName::Permissions m_permissions;
 
+  double 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