Skip to content

Commit 22144e2

Browse files
authored
[LLDB][Data Formatters] Calculate average and total time for summary providers within lldb (#102708)
This PR adds a statistics provider cache, which allows an individual target to keep a rolling tally of it's total time and number of invocations for a given summary provider. This information is then available in statistics dump to help slow summary providers, and gleam more into insight into LLDB's time use.
1 parent c7a7767 commit 22144e2

File tree

14 files changed

+350
-13
lines changed

14 files changed

+350
-13
lines changed

lldb/include/lldb/DataFormatters/TypeSummary.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,14 @@ class TypeSummaryImpl {
258258

259259
virtual std::string GetDescription() = 0;
260260

261+
/// Get the name of the Type Summary Provider, either a C++ class, a summary
262+
/// string, or a script function name.
263+
virtual std::string GetName() = 0;
264+
265+
/// Get the name of the kind of Summary Provider, either c++, summary string,
266+
/// script or python.
267+
virtual std::string GetSummaryKindName();
268+
261269
uint32_t &GetRevision() { return m_my_revision; }
262270

263271
typedef std::shared_ptr<TypeSummaryImpl> SharedPointer;
@@ -293,6 +301,8 @@ struct StringSummaryFormat : public TypeSummaryImpl {
293301

294302
std::string GetDescription() override;
295303

304+
std::string GetName() override;
305+
296306
static bool classof(const TypeSummaryImpl *S) {
297307
return S->GetKind() == Kind::eSummaryString;
298308
}
@@ -340,6 +350,8 @@ struct CXXFunctionSummaryFormat : public TypeSummaryImpl {
340350
return S->GetKind() == Kind::eCallback;
341351
}
342352

353+
std::string GetName() override;
354+
343355
typedef std::shared_ptr<CXXFunctionSummaryFormat> SharedPointer;
344356

345357
private:
@@ -352,6 +364,7 @@ struct CXXFunctionSummaryFormat : public TypeSummaryImpl {
352364
struct ScriptSummaryFormat : public TypeSummaryImpl {
353365
std::string m_function_name;
354366
std::string m_python_script;
367+
std::string m_script_formatter_name;
355368
StructuredData::ObjectSP m_script_function_sp;
356369

357370
ScriptSummaryFormat(const TypeSummaryImpl::Flags &flags,
@@ -384,6 +397,8 @@ struct ScriptSummaryFormat : public TypeSummaryImpl {
384397

385398
std::string GetDescription() override;
386399

400+
std::string GetName() override;
401+
387402
static bool classof(const TypeSummaryImpl *S) {
388403
return S->GetKind() == Kind::eScript;
389404
}

lldb/include/lldb/Target/Statistics.h

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef LLDB_TARGET_STATISTICS_H
1010
#define LLDB_TARGET_STATISTICS_H
1111

12+
#include "lldb/DataFormatters/TypeSummary.h"
1213
#include "lldb/Utility/ConstString.h"
1314
#include "lldb/Utility/RealpathPrefixes.h"
1415
#include "lldb/Utility/Stream.h"
@@ -17,6 +18,7 @@
1718
#include "llvm/Support/JSON.h"
1819
#include <atomic>
1920
#include <chrono>
21+
#include <mutex>
2022
#include <optional>
2123
#include <ratio>
2224
#include <string>
@@ -26,6 +28,9 @@ namespace lldb_private {
2628

2729
using StatsClock = std::chrono::high_resolution_clock;
2830
using StatsTimepoint = std::chrono::time_point<StatsClock>;
31+
class SummaryStatistics;
32+
// Declaring here as there is no private forward
33+
typedef std::shared_ptr<SummaryStatistics> SummaryStatisticsSP;
2934

3035
class StatsDuration {
3136
public:
@@ -175,6 +180,81 @@ struct StatisticsOptions {
175180
std::optional<bool> m_include_transcript;
176181
};
177182

183+
/// A class that represents statistics about a TypeSummaryProviders invocations
184+
/// \note All members of this class need to be accessed in a thread safe manner
185+
class SummaryStatistics {
186+
public:
187+
explicit SummaryStatistics(std::string name, std::string impl_type)
188+
: m_total_time(), m_impl_type(std::move(impl_type)),
189+
m_name(std::move(name)), m_count(0) {}
190+
191+
std::string GetName() const { return m_name; };
192+
double GetTotalTime() const { return m_total_time.get().count(); }
193+
194+
uint64_t GetSummaryCount() const {
195+
return m_count.load(std::memory_order_relaxed);
196+
}
197+
198+
StatsDuration &GetDurationReference() { return m_total_time; };
199+
200+
std::string GetSummaryKindName() const { return m_impl_type; }
201+
202+
llvm::json::Value ToJSON() const;
203+
204+
/// Basic RAII class to increment the summary count when the call is complete.
205+
class SummaryInvocation {
206+
public:
207+
SummaryInvocation(SummaryStatisticsSP summary_stats)
208+
: m_stats(summary_stats),
209+
m_elapsed_time(summary_stats->GetDurationReference()) {}
210+
~SummaryInvocation() { m_stats->OnInvoked(); }
211+
212+
/// Delete the copy constructor and assignment operator to prevent
213+
/// accidental double counting.
214+
/// @{
215+
SummaryInvocation(const SummaryInvocation &) = delete;
216+
SummaryInvocation &operator=(const SummaryInvocation &) = delete;
217+
/// @}
218+
219+
private:
220+
SummaryStatisticsSP m_stats;
221+
ElapsedTime m_elapsed_time;
222+
};
223+
224+
private:
225+
void OnInvoked() noexcept { m_count.fetch_add(1, std::memory_order_relaxed); }
226+
lldb_private::StatsDuration m_total_time;
227+
const std::string m_impl_type;
228+
const std::string m_name;
229+
std::atomic<uint64_t> m_count;
230+
};
231+
232+
/// A class that wraps a std::map of SummaryStatistics objects behind a mutex.
233+
class SummaryStatisticsCache {
234+
public:
235+
/// Get the SummaryStatistics object for a given provider name, or insert
236+
/// if statistics for that provider is not in the map.
237+
SummaryStatisticsSP
238+
GetSummaryStatisticsForProvider(lldb_private::TypeSummaryImpl &provider) {
239+
std::lock_guard<std::mutex> guard(m_map_mutex);
240+
if (auto iterator = m_summary_stats_map.find(provider.GetName());
241+
iterator != m_summary_stats_map.end())
242+
return iterator->second;
243+
244+
auto it = m_summary_stats_map.try_emplace(
245+
provider.GetName(),
246+
std::make_shared<SummaryStatistics>(provider.GetName(),
247+
provider.GetSummaryKindName()));
248+
return it.first->second;
249+
}
250+
251+
llvm::json::Value ToJSON();
252+
253+
private:
254+
llvm::StringMap<SummaryStatisticsSP> m_summary_stats_map;
255+
std::mutex m_map_mutex;
256+
};
257+
178258
/// A class that represents statistics for a since lldb_private::Target.
179259
class TargetStats {
180260
public:

lldb/include/lldb/Target/Target.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,10 @@ class Target : public std::enable_shared_from_this<Target>,
12241224

12251225
void ClearAllLoadedSections();
12261226

1227+
lldb_private::SummaryStatisticsSP GetSummaryStatisticsSPForProviderName(
1228+
lldb_private::TypeSummaryImpl &summary_provider);
1229+
lldb_private::SummaryStatisticsCache &GetSummaryStatisticsCache();
1230+
12271231
/// Set the \a Trace object containing processor trace information of this
12281232
/// target.
12291233
///
@@ -1557,6 +1561,7 @@ class Target : public std::enable_shared_from_this<Target>,
15571561
std::string m_label;
15581562
ModuleList m_images; ///< The list of images for this process (shared
15591563
/// libraries and anything dynamically loaded).
1564+
SummaryStatisticsCache m_summary_statistics_cache;
15601565
SectionLoadHistory m_section_load_history;
15611566
BreakpointList m_breakpoint_list;
15621567
BreakpointList m_internal_breakpoint_list;

lldb/source/Core/ValueObject.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,17 @@ bool ValueObject::GetSummaryAsCString(TypeSummaryImpl *summary_ptr,
615615
m_synthetic_value->UpdateValueIfNeeded(); // the summary might depend on
616616
// the synthetic children being
617617
// up-to-date (e.g. ${svar%#})
618-
summary_ptr->FormatObject(this, destination, actual_options);
618+
619+
if (TargetSP target_sp = GetExecutionContextRef().GetTargetSP()) {
620+
SummaryStatisticsSP stats_sp =
621+
target_sp->GetSummaryStatisticsCache()
622+
.GetSummaryStatisticsForProvider(*summary_ptr);
623+
624+
// Construct RAII types to time and collect data on summary creation.
625+
SummaryStatistics::SummaryInvocation invocation(stats_sp);
626+
summary_ptr->FormatObject(this, destination, actual_options);
627+
} else
628+
summary_ptr->FormatObject(this, destination, actual_options);
619629
}
620630
m_flags.m_is_getting_summary = false;
621631
return !destination.empty();

lldb/source/DataFormatters/TypeSummary.cpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,19 @@ TypeSummaryOptions::SetCapping(lldb::TypeSummaryCapping cap) {
4848
TypeSummaryImpl::TypeSummaryImpl(Kind kind, const TypeSummaryImpl::Flags &flags)
4949
: m_flags(flags), m_kind(kind) {}
5050

51+
std::string TypeSummaryImpl::GetSummaryKindName() {
52+
switch (m_kind) {
53+
case Kind::eSummaryString:
54+
return "string";
55+
case Kind::eCallback:
56+
return "callback";
57+
case Kind::eScript:
58+
return "python";
59+
case Kind::eInternal:
60+
return "c++";
61+
}
62+
}
63+
5164
StringSummaryFormat::StringSummaryFormat(const TypeSummaryImpl::Flags &flags,
5265
const char *format_cstr)
5366
: TypeSummaryImpl(Kind::eSummaryString, flags), m_format_str() {
@@ -116,6 +129,8 @@ std::string StringSummaryFormat::GetDescription() {
116129
return std::string(sstr.GetString());
117130
}
118131

132+
std::string StringSummaryFormat::GetName() { return m_format_str; }
133+
119134
CXXFunctionSummaryFormat::CXXFunctionSummaryFormat(
120135
const TypeSummaryImpl::Flags &flags, Callback impl, const char *description)
121136
: TypeSummaryImpl(Kind::eCallback, flags), m_impl(impl),
@@ -145,15 +160,27 @@ std::string CXXFunctionSummaryFormat::GetDescription() {
145160
return std::string(sstr.GetString());
146161
}
147162

163+
std::string CXXFunctionSummaryFormat::GetName() { return m_description; }
164+
148165
ScriptSummaryFormat::ScriptSummaryFormat(const TypeSummaryImpl::Flags &flags,
149166
const char *function_name,
150167
const char *python_script)
151168
: TypeSummaryImpl(Kind::eScript, flags), m_function_name(),
152169
m_python_script(), m_script_function_sp() {
153-
if (function_name)
170+
// Take preference in the python script name over the function name.
171+
if (function_name) {
154172
m_function_name.assign(function_name);
155-
if (python_script)
173+
m_script_formatter_name = function_name;
174+
}
175+
if (python_script) {
156176
m_python_script.assign(python_script);
177+
m_script_formatter_name = python_script;
178+
}
179+
180+
// Python scripts include the tabbing of the function def so we remove the
181+
// leading spaces.
182+
m_script_formatter_name = m_script_formatter_name.erase(
183+
0, m_script_formatter_name.find_first_not_of(' '));
157184
}
158185

159186
bool ScriptSummaryFormat::FormatObject(ValueObject *valobj, std::string &retval,
@@ -201,3 +228,5 @@ std::string ScriptSummaryFormat::GetDescription() {
201228
}
202229
return std::string(sstr.GetString());
203230
}
231+
232+
std::string ScriptSummaryFormat::GetName() { return m_script_formatter_name; }

lldb/source/Target/Statistics.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ TargetStats::ToJSON(Target &target,
196196
m_source_realpath_attempt_count);
197197
target_metrics_json.try_emplace("sourceRealpathCompatibleCount",
198198
m_source_realpath_compatible_count);
199+
target_metrics_json.try_emplace("summaryProviderStatistics",
200+
target.GetSummaryStatisticsCache().ToJSON());
199201
return target_metrics_json;
200202
}
201203

@@ -420,3 +422,21 @@ llvm::json::Value DebuggerStats::ReportStatistics(
420422

421423
return std::move(global_stats);
422424
}
425+
426+
llvm::json::Value SummaryStatistics::ToJSON() const {
427+
return json::Object{{
428+
{"name", GetName()},
429+
{"type", GetSummaryKindName()},
430+
{"count", GetSummaryCount()},
431+
{"totalTime", GetTotalTime()},
432+
}};
433+
}
434+
435+
json::Value SummaryStatisticsCache::ToJSON() {
436+
std::lock_guard<std::mutex> guard(m_map_mutex);
437+
json::Array json_summary_stats;
438+
for (const auto &summary_stat : m_summary_stats_map)
439+
json_summary_stats.emplace_back(summary_stat.second->ToJSON());
440+
441+
return json_summary_stats;
442+
}

lldb/source/Target/Target.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3204,6 +3204,16 @@ bool Target::SetSectionUnloaded(const lldb::SectionSP &section_sp,
32043204

32053205
void Target::ClearAllLoadedSections() { m_section_load_history.Clear(); }
32063206

3207+
lldb_private::SummaryStatisticsSP Target::GetSummaryStatisticsSPForProviderName(
3208+
lldb_private::TypeSummaryImpl &summary_provider) {
3209+
return m_summary_statistics_cache.GetSummaryStatisticsForProvider(
3210+
summary_provider);
3211+
}
3212+
3213+
SummaryStatisticsCache &Target::GetSummaryStatisticsCache() {
3214+
return m_summary_statistics_cache;
3215+
}
3216+
32073217
void Target::SaveScriptedLaunchInfo(lldb_private::ProcessInfo &process_info) {
32083218
if (process_info.IsScriptedProcess()) {
32093219
// Only copy scripted process launch options.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import lldb
2+
3+
4+
def summary(valobj, dict):
5+
return f"[{valobj.GetChildAtIndex(0).GetValue()}]"
6+
7+
8+
def __lldb_init_module(debugger, dict):
9+
typeName = "Box<.*$"
10+
debugger.HandleCommand(
11+
'type summary add -x "'
12+
+ typeName
13+
+ '" --python-function '
14+
+ f"{__name__}.summary"
15+
)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
C_SOURCES := main.c
1+
CXX_SOURCES := main.cpp
22
include Makefile.rules

0 commit comments

Comments
 (0)