clayborg updated this revision to Diff 376358.
clayborg added a comment.

Changes:

- Swithed from using "double" to using ElapsedTime::Duration
- Stop using assertTrue in tests, and use better assertXXX methods


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/Metrics.h
  lldb/include/lldb/Target/PathMappingList.h
  lldb/include/lldb/Target/Process.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/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,3 @@
+extern int a_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,359 @@
+"""
+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
+        }
+
+        """
+        target = self.createTestTarget()
+        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.assertGreater(metrics['targetCreateTime'], 0.0)
+        self.assertEqual(metrics['totalBreakpointResolveTime'], 0.0)
+        self.assertGreater(metrics['totalDebugInfoSize'], 0)
+        self.assertGreater(metrics['totalSymbolTableIndexTime'], 0.0)
+        self.assertGreater(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")
+        target = self.createTestTarget(file_path=exe)
+        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.assertNotEqual(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.assertNotEqual(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
+              },
+            ]
+        }
+
+        """
+        target = self.createTestTarget()
+        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.assertGreater(metrics['totalBreakpointResolveTime'], 0.0)
+        self.assertGreater(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
+              },
+            ]
+          }
+
+        """
+        target = self.createTestTarget()
+        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.assertGreater(metrics['totalBreakpointResolveTime'], 0.0)
+        self.assertGreater(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": []
+            },
+          }
+
+        """
+        target = self.createTestTarget()
+        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,189 @@
+//===-- 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().count();
+      module_metrics.debug_parse_time =
+          sym_file->GetDebugInfoParseTime().count();
+    }
+    module_metrics.symtab_parse_time = module_sp->GetSymtabParseTime().count();
+    module_metrics.symtab_names_time = module_sp->GetSymtabNamesTime().count();
+    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().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},
+  };
+  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 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
@@ -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 = "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.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/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/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
@@ -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/ElapsedTime.h"
+#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();
+
+  ElapsedTime::Duration &GetCreateTime() { return create_time; }
+
+protected:
+  std::vector<ModuleMetrics> m_module_metrics;
+  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;
+};
+
+} // 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
@@ -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 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
@@ -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 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.
+  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