diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -238,10 +238,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++; } diff --git a/lldb/include/lldb/Target/Statistics.h b/lldb/include/lldb/Target/Statistics.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/Statistics.h @@ -0,0 +1,62 @@ +//===-- Statistics.h --------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TARGET_STATISTICS_H +#define LLDB_TARGET_STATISTICS_H + +#include +#include + +#include "lldb/Utility/ElapsedTime.h" +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-forward.h" +#include "llvm/Support/JSON.h" + +namespace lldb_private { + +struct SuccessFailStats { + void Notify(bool success); + + llvm::json::Value ToJSON() const; + + uint32_t successes = 0; + uint32_t failures = 0; +}; + +class TargetStats { +public: + llvm::json::Value ToJSON(); + + void SetLaunchOrAttachTime(); + void SetFirstPrivateStopTime(); + void SetFirstPublicStopTime(); + + ElapsedTime::Duration &GetCreateTime() { return create_time; } + + void NotifyExprEval(bool success) { return m_expr_eval.Notify(success); } + void NotifyFrameVar(bool success) { return m_frame_var.Notify(success); } + void SetCollectingStats(bool v) { m_collecting_stats = v; } + bool GetCollectingStats() { return m_collecting_stats; } + +protected: + // Collecting stats can be set to true to collect stats that are expensive + // to collect. By default all stats that are cheap to collect are enabled. + // This settings is here to maintain compatibility with "statistics enable" + // and "statistics disable". + bool m_collecting_stats = false; + ElapsedTime::Duration create_time{0.0}; + llvm::Optional launch_or_attach_time; + llvm::Optional first_private_stop_time; + llvm::Optional first_public_stop_time; + SuccessFailStats m_expr_eval; + SuccessFailStats m_frame_var; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_STATISTICS_H diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -28,6 +28,7 @@ #include "lldb/Target/ExecutionContextScope.h" #include "lldb/Target/PathMappingList.h" #include "lldb/Target/SectionLoadHistory.h" +#include "lldb/Target/Statistics.h" #include "lldb/Target/ThreadSpec.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Broadcaster.h" @@ -1451,23 +1452,26 @@ // Utilities for `statistics` command. private: - std::vector m_stats_storage; - bool m_collecting_stats = false; + // Target metrics storage. + TargetStats m_stats; public: - void SetCollectingStats(bool v) { m_collecting_stats = v; } - - bool GetCollectingStats() { return m_collecting_stats; } - - void IncrementStats(lldb_private::StatisticKind key) { - if (!GetCollectingStats()) - return; - lldbassert(key < lldb_private::StatisticKind::StatisticMax && - "invalid statistics!"); - m_stats_storage[key] += 1; - } + /// Get metrics associated with this target in JSON format. + /// + /// Target metrics help measure timings and information that is contained in + /// a target. These are designed to help measure performance of a debug + /// session as well as represent the current state of the target, like + /// information on the currently modules, currently set breakpoints and more. + /// + /// \param [in] modules + /// If true, include an array of metrics for each module loaded in the + /// target. + // + /// \return + /// Returns a JSON value that contains all target metrics. + llvm::json::Value ReportStatistics(); - std::vector GetStatistics() { return m_stats_storage; } + TargetStats &GetStatistics() { return m_stats; } private: /// Construct with optional file and arch. diff --git a/lldb/include/lldb/Utility/ElapsedTime.h b/lldb/include/lldb/Utility/ElapsedTime.h new file mode 100644 --- /dev/null +++ b/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 +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; + using Timepoint = std::chrono::time_point; + /// 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 diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -118,6 +118,7 @@ class MemoryHistory; class MemoryRegionInfo; class MemoryRegionInfos; +struct StatsDumpOptions; class Module; class ModuleList; class ModuleSpec; diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -758,6 +758,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) diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp --- a/lldb/source/API/SBTarget.cpp +++ b/lldb/source/API/SBTarget.cpp @@ -213,17 +213,11 @@ TargetSP target_sp(GetSP()); if (!target_sp) return LLDB_RECORD_RESULT(data); - - auto stats_up = std::make_unique(); - int i = 0; - for (auto &Entry : target_sp->GetStatistics()) { - std::string Desc = lldb_private::GetStatDescription( - static_cast(i)); - stats_up->AddIntegerItem(Desc, Entry); - i += 1; - } - - data.m_impl_up->SetObjectSP(std::move(stats_up)); + std::string json_text; + llvm::raw_string_ostream stream(json_text); + llvm::json::Value json = target_sp->ReportStatistics(); + stream << json; + data.m_impl_up->SetObjectSP(StructuredData::ParseJSON(stream.str())); return LLDB_RECORD_RESULT(data); } @@ -233,7 +227,7 @@ TargetSP target_sp(GetSP()); if (!target_sp) return; - return target_sp->SetCollectingStats(v); + return target_sp->GetStatistics().SetCollectingStats(v); } bool SBTarget::GetCollectingStats() { @@ -242,7 +236,7 @@ TargetSP target_sp(GetSP()); if (!target_sp) return false; - return target_sp->GetCollectingStats(); + return target_sp->GetStatistics().GetCollectingStats(); } SBProcess SBTarget::LoadCore(const char *core_file) { diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp --- a/lldb/source/Commands/CommandObjectExpression.cpp +++ b/lldb/source/Commands/CommandObjectExpression.cpp @@ -660,12 +660,12 @@ history.AppendString(fixed_command); } // Increment statistics to record this expression evaluation success. - target.IncrementStats(StatisticKind::ExpressionSuccessful); + target.GetStatistics().NotifyExprEval(true); return true; } // Increment statistics to record this expression evaluation failure. - target.IncrementStats(StatisticKind::ExpressionFailure); + target.GetStatistics().NotifyExprEval(false); result.SetStatus(eReturnStatusFailed); return false; } diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp --- a/lldb/source/Commands/CommandObjectFrame.cpp +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -708,11 +708,7 @@ // Increment statistics. bool res = result.Succeeded(); - Target &target = GetSelectedOrDummyTarget(); - if (res) - target.IncrementStats(StatisticKind::FrameVarSuccess); - else - target.IncrementStats(StatisticKind::FrameVarFailure); + GetSelectedOrDummyTarget().GetStatistics().NotifyFrameVar(res); return res; } diff --git a/lldb/source/Commands/CommandObjectStats.cpp b/lldb/source/Commands/CommandObjectStats.cpp --- a/lldb/source/Commands/CommandObjectStats.cpp +++ b/lldb/source/Commands/CommandObjectStats.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "CommandObjectStats.h" +#include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Target/Target.h" @@ -26,12 +27,12 @@ bool DoExecute(Args &command, CommandReturnObject &result) override { Target &target = GetSelectedOrDummyTarget(); - if (target.GetCollectingStats()) { + if (target.GetStatistics().GetCollectingStats()) { result.AppendError("statistics already enabled"); return false; } - target.SetCollectingStats(true); + target.GetStatistics().SetCollectingStats(true); result.SetStatus(eReturnStatusSuccessFinishResult); return true; } @@ -50,39 +51,33 @@ bool DoExecute(Args &command, CommandReturnObject &result) override { Target &target = GetSelectedOrDummyTarget(); - if (!target.GetCollectingStats()) { + if (!target.GetStatistics().GetCollectingStats()) { result.AppendError("need to enable statistics before disabling them"); return false; } - target.SetCollectingStats(false); + target.GetStatistics().SetCollectingStats(false); result.SetStatus(eReturnStatusSuccessFinishResult); return true; } }; +#define LLDB_OPTIONS_statistics_dump +#include "CommandOptions.inc" + class CommandObjectStatsDump : public CommandObjectParsed { public: CommandObjectStatsDump(CommandInterpreter &interpreter) - : CommandObjectParsed(interpreter, "dump", "Dump statistics results", - nullptr, eCommandProcessMustBePaused) {} + : CommandObjectParsed( + interpreter, "statistics dump", "Dump metrics in JSON format", + "statistics dump []", eCommandRequiresTarget) {} ~CommandObjectStatsDump() override = default; protected: bool DoExecute(Args &command, CommandReturnObject &result) override { - Target &target = GetSelectedOrDummyTarget(); - - uint32_t i = 0; - for (auto &stat : target.GetStatistics()) { - result.AppendMessageWithFormat( - "%s : %u\n", - lldb_private::GetStatDescription( - static_cast(i)) - .c_str(), - stat); - i += 1; - } + Target &target = m_exe_ctx.GetTargetRef(); + result.AppendMessageWithFormatv("{0:2}", target.ReportStatistics()); result.SetStatus(eReturnStatusSuccessFinishResult); return true; } diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -39,6 +39,7 @@ StackFrameList.cpp StackFrameRecognizer.cpp StackID.cpp + Statistics.cpp StopInfo.cpp StructuredDataPlugin.cpp SystemRuntime.cpp diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -1297,6 +1297,17 @@ } void Process::SetPublicState(StateType new_state, bool restarted) { + const bool new_state_is_stopped = StateIsStoppedState(new_state, false); + if (new_state_is_stopped) { + // This will only set the time if the public stop time has no value, so + // it is ok to call this multiple times. With a public stop we can't look + // at the stop ID because many private stops might have happened, so we + // can't check for a stop ID of zero. This allows the "statistics" command + // to dump the time it takes to reach somewhere in your code, like a + // breakpoint you set. + GetTarget().GetStatistics().SetFirstPublicStopTime(); + } + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STATE | LIBLLDB_LOG_PROCESS)); LLDB_LOGF(log, "Process::SetPublicState (state = %s, restarted = %i)", @@ -1315,7 +1326,6 @@ m_public_run_lock.SetStopped(); } else { const bool old_state_is_stopped = StateIsStoppedState(old_state, false); - const bool new_state_is_stopped = StateIsStoppedState(new_state, false); if ((old_state_is_stopped != new_state_is_stopped)) { if (new_state_is_stopped && !restarted) { LLDB_LOGF(log, "Process::SetPublicState (%s) -- unlocking run lock", @@ -1446,7 +1456,9 @@ // before we get here. m_thread_list.DidStop(); - m_mod_id.BumpStopID(); + if (m_mod_id.BumpStopID() == 0) + GetTarget().GetStatistics().SetFirstPrivateStopTime(); + if (!m_mod_id.IsLastResumeForUserExpression()) m_mod_id.SetStopEventForLastNaturalStopID(event_sp); m_memory_cache.Clear(); diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Target/Statistics.cpp @@ -0,0 +1,80 @@ +//===-- Statistics.cpp ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Target/Statistics.h" + +#include "lldb/Core/Module.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Target/Target.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +void SuccessFailStats::Notify(bool success) { + if (success) + ++successes; + else + ++failures; +} + +json::Value SuccessFailStats::ToJSON() const { + return json::Value(json::Object{ + {"successes", successes}, + {"failures", failures}, + }); +} + +static double elapsed(const std::chrono::steady_clock::time_point &start, + const std::chrono::steady_clock::time_point &end) { + std::chrono::duration elapsed = + end.time_since_epoch() - start.time_since_epoch(); + return elapsed.count(); +} + +json::Value TargetStats::ToJSON() { + + json::Object target_metrics_json{ + {"expressionEvaluation", m_expr_eval.ToJSON()}, + {"frameVariable", m_frame_var.ToJSON()}, + }; + if (launch_or_attach_time && first_private_stop_time) { + 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 && first_public_stop_time) { + double elapsed_time = + elapsed(*launch_or_attach_time, *first_public_stop_time); + target_metrics_json.try_emplace("firstStopTime", elapsed_time); + } + target_metrics_json.try_emplace("targetCreateTime", create_time.count()); + + return target_metrics_json; +} + +void TargetStats::SetLaunchOrAttachTime() { + launch_or_attach_time = std::chrono::steady_clock::now(); + first_private_stop_time = llvm::None; +} + +void TargetStats::SetFirstPrivateStopTime() { + // Launching and attaching has many paths depending on if synchronous mode + // was used or if we are stopping at the entry point or not. Only set the + // first stop time if it hasn't already been set. + if (!first_private_stop_time) + first_private_stop_time = std::chrono::steady_clock::now(); +} + +void TargetStats::SetFirstPublicStopTime() { + // Launching and attaching has many paths depending on if synchronous mode + // was used or if we are stopping at the entry point or not. Only set the + // first stop time if it hasn't already been set. + if (!first_public_stop_time) + first_public_stop_time = std::chrono::steady_clock::now(); +} diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -95,14 +95,10 @@ m_watchpoint_list(), m_process_sp(), m_search_filter_sp(), m_image_search_paths(ImageSearchPathsChanged, this), m_source_manager_up(), m_stop_hooks(), m_stop_hook_next_id(0), - m_latest_stop_hook_id(0), - m_valid(true), m_suppress_stop_hooks(false), + m_latest_stop_hook_id(0), m_valid(true), m_suppress_stop_hooks(false), m_is_dummy_target(is_dummy_target), m_frame_recognizer_manager_up( - std::make_unique()), - m_stats_storage(static_cast(StatisticKind::StatisticMax)) - -{ + std::make_unique()) { SetEventName(eBroadcastBitBreakpointChanged, "breakpoint-changed"); SetEventName(eBroadcastBitModulesLoaded, "modules-loaded"); SetEventName(eBroadcastBitModulesUnloaded, "modules-unloaded"); @@ -1400,6 +1396,7 @@ ClearModules(false); if (executable_sp) { + ElapsedTime elapsed(m_stats.GetCreateTime()); LLDB_SCOPED_TIMERF("Target::SetExecutableModule (executable = '%s')", executable_sp->GetFileSpec().GetPath().c_str()); @@ -2895,6 +2892,7 @@ void Target::ClearAllLoadedSections() { m_section_load_history.Clear(); } Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) { + m_stats.SetLaunchOrAttachTime(); Status error; Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_TARGET)); @@ -3098,6 +3096,7 @@ } Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) { + m_stats.SetLaunchOrAttachTime(); auto state = eStateInvalid; auto process_sp = GetProcessSP(); if (process_sp) { @@ -4443,3 +4442,6 @@ else return m_mutex; } + +/// Get metrics associated with this target in JSON format. +llvm::json::Value Target::ReportStatistics() { return m_stats.ToJSON(); } diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py --- a/lldb/test/API/commands/statistics/basic/TestStats.py +++ b/lldb/test/API/commands/statistics/basic/TestStats.py @@ -1,4 +1,5 @@ import lldb +import json from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil @@ -7,22 +8,87 @@ mydir = TestBase.compute_mydir(__file__) - def test(self): + def setUp(self): + TestBase.setUp(self) self.build() - lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.c")) - self.expect("statistics disable", substrs=['need to enable statistics before disabling'], error=True) + NO_DEBUG_INFO_TESTCASE = True + + def test_enable_disable(self): + """ + Test "statistics disable" and "statistics enable". These don't do + anything anymore for cheap to gather statistics. In the future if + statistics are expensive to gather, we can enable the feature inside + of LLDB and test that enabling and disabling stops expesive information + from being gathered. + """ + target = self.createTestTarget() - # 'expression' should change the statistics. + self.expect("statistics disable", substrs=['need to enable statistics before disabling'], error=True) self.expect("statistics enable") self.expect("statistics enable", substrs=['already enabled'], error=True) - self.expect("expr patatino", substrs=['27']) self.expect("statistics disable") - self.expect("statistics dump", substrs=['expr evaluation successes : 1\n', - 'expr evaluation failures : 0\n']) + self.expect("statistics disable", substrs=['need to enable statistics before disabling'], error=True) - self.expect("statistics enable") - # Doesn't parse. + def verify_key_in_dict(self, key, d, description): + self.assertEqual(key in d, True, + 'make sure key "%s" is in dictionary %s' % (key, description)) + + def verify_key_not_in_dict(self, key, d, description): + self.assertEqual(key in d, False, + 'make sure key "%s" is in dictionary %s' % (key, description)) + + def verify_keys(self, dict, description, keys_exist, keys_missing=None): + """ + Verify that all keys in "keys_exist" list are top level items in + "dict", and that all keys in "keys_missing" do not exist as top + level items in "dict". + """ + if keys_exist: + for key in keys_exist: + self.verify_key_in_dict(key, dict, description) + if keys_missing: + for key in keys_missing: + self.verify_key_not_in_dict(key, dict, description) + + def verify_success_fail_count(self, stats, key, num_successes, num_fails): + self.verify_key_in_dict(key, stats, 'stats["%s"]' % (key)) + success_fail_dict = stats[key] + self.assertEqual(success_fail_dict['successes'], num_successes, + 'make sure success count') + self.assertEqual(success_fail_dict['failures'], num_fails, + 'make sure success count') + + def get_stats(self, options=None, log_path=None): + """ + Get the output of the "statistics dump" with optional extra options + and return the JSON as a python dictionary. + """ + # If log_path is set, open the path and emit the output of the command + # for debugging purposes. + if log_path is not None: + f = open(log_path, 'w') + else: + f = None + return_obj = lldb.SBCommandReturnObject() + command = "statistics dump " + if options is not None: + command += options + if f: + f.write('(lldb) %s\n' % (command)) + self.ci.HandleCommand(command, return_obj, False) + metrics_json = return_obj.GetOutput() + if f: + f.write(metrics_json) + return json.loads(metrics_json) + + def test_expressions_frame_var_counts(self): + lldbutil.run_to_source_breakpoint(self, "// break here", + lldb.SBFileSpec("main.c")) + + self.expect("expr patatino", substrs=['27']) + stats = self.get_stats() + self.verify_success_fail_count(stats, 'expressionEvaluation', 1, 0) self.expect("expr doesnt_exist", error=True, substrs=["undeclared identifier 'doesnt_exist'"]) # Doesn't successfully execute. @@ -30,17 +96,88 @@ # Interpret an integer as an array with 3 elements is also a failure. self.expect("expr -Z 3 -- 1", error=True, substrs=["expression cannot be used with --element-count"]) - self.expect("statistics disable") # We should have gotten 3 new failures and the previous success. - self.expect("statistics dump", substrs=['expr evaluation successes : 1\n', - 'expr evaluation failures : 3\n']) - - # 'frame var' with disabled statistics shouldn't change stats. - self.expect("frame var", substrs=['27']) + stats = self.get_stats() + self.verify_success_fail_count(stats, 'expressionEvaluation', 1, 3) self.expect("statistics enable") # 'frame var' with enabled statistics will change stats. self.expect("frame var", substrs=['27']) - self.expect("statistics disable") - self.expect("statistics dump", substrs=['frame var successes : 1\n', - 'frame var failures : 0\n']) + stats = self.get_stats() + self.verify_success_fail_count(stats, 'frameVariable', 1, 0) + + def test_default_no_run(self): + """Test "statistics dump" without running the target. + + When we don't run the target, we expect to not see any 'firstStopTime' + or 'launchOrAttachTime' top level keys that measure the launch or + attach of the target. + + Output expected to be something like: + + (lldb) statistics dump + { + "targetCreateTime": 0.26566899599999999, + "expressionEvaluation": { + "failures": 0, + "successes": 0 + }, + "frameVariable": { + "failures": 0, + "successes": 0 + }, + } + """ + target = self.createTestTarget() + stats = self.get_stats() + keys_exist = [ + 'expressionEvaluation', + 'frameVariable', + 'targetCreateTime', + ] + keys_missing = [ + 'firstStopTime', + 'launchOrAttachTime' + ] + self.verify_keys(stats, '"stats"', keys_exist, keys_missing) + self.assertGreater(stats['targetCreateTime'], 0.0) + + def test_default_with_run(self): + """Test "statistics dump" when running the target to a breakpoint. + + When we run the target, we expect to see 'launchOrAttachTime' and + 'firstStopTime' top level keys. + + Output expected to be something like: + + (lldb) statistics dump + { + "firstStopTime": 0.34164492800000001, + "launchOrAttachTime": 0.31969605400000001, + "targetCreateTime": 0.0040863039999999998 + "expressionEvaluation": { + "failures": 0, + "successes": 0 + }, + "frameVariable": { + "failures": 0, + "successes": 0 + }, + } + + """ + target = self.createTestTarget() + lldbutil.run_to_source_breakpoint(self, "// break here", + lldb.SBFileSpec("main.c")) + stats = self.get_stats() + keys_exist = [ + 'expressionEvaluation', + 'firstStopTime', + 'frameVariable', + 'launchOrAttachTime', + 'targetCreateTime', + ] + self.verify_keys(stats, '"stats"', keys_exist, None) + self.assertGreater(stats['firstStopTime'], 0.0) + self.assertGreater(stats['launchOrAttachTime'], 0.0) + self.assertGreater(stats['targetCreateTime'], 0.0) diff --git a/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py b/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py --- a/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py +++ b/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py @@ -10,6 +10,8 @@ class TestStatsAPI(TestBase): mydir = TestBase.compute_mydir(__file__) + NO_DEBUG_INFO_TESTCASE = True + def test_stats_api(self): self.build() exe = self.getBuildArtifact("a.out") @@ -26,9 +28,18 @@ stats = target.GetStatistics() stream = lldb.SBStream() res = stats.GetAsJSON(stream) - stats_json = sorted(json.loads(stream.GetData())) - self.assertEqual(len(stats_json), 4) - self.assertIn("Number of expr evaluation failures", stats_json) - self.assertIn("Number of expr evaluation successes", stats_json) - self.assertIn("Number of frame var failures", stats_json) - self.assertIn("Number of frame var successes", stats_json) + stats_json = json.loads(stream.GetData()) + self.assertEqual('expressionEvaluation' in stats_json, True, + 'Make sure the "expressionEvaluation" key in in target.GetStatistics()') + self.assertEqual('frameVariable' in stats_json, True, + 'Make sure the "frameVariable" key in in target.GetStatistics()') + expressionEvaluation = stats_json['expressionEvaluation'] + self.assertEqual('successes' in expressionEvaluation, True, + 'Make sure the "successes" key in in "expressionEvaluation" dictionary"') + self.assertEqual('failures' in expressionEvaluation, True, + 'Make sure the "failures" key in in "expressionEvaluation" dictionary"') + frameVariable = stats_json['frameVariable'] + self.assertEqual('successes' in frameVariable, True, + 'Make sure the "successes" key in in "frameVariable" dictionary"') + self.assertEqual('failures' in frameVariable, True, + 'Make sure the "failures" key in in "frameVariable" dictionary"')