Skip to content

Commit db73a52

Browse files
rnofenkoWalter Erquinigo
authored andcommitted
[trace][intel pt] Add a nice parser for the trace size
Thanks to [email protected] for coming up with these changes. This diff adds support for passing units in the trace size inputs. For example, it's now possible to specify 64KB as the trace size, instead of the problematic 65536. This makes the user experience a bit friendlier. Differential Revision: https://reviews.llvm.org/D129613
1 parent aba0d3c commit db73a52

File tree

5 files changed

+154
-24
lines changed

5 files changed

+154
-24
lines changed

lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,12 @@ Status CommandObjectThreadTraceStartIntelPT::CommandOptions::SetOptionValue(
3232

3333
switch (short_option) {
3434
case 's': {
35-
int64_t ipt_trace_size;
36-
if (option_arg.empty() || option_arg.getAsInteger(0, ipt_trace_size) ||
37-
ipt_trace_size < 0)
38-
error.SetErrorStringWithFormat("invalid integer value for option '%s'",
39-
option_arg.str().c_str());
35+
if (Optional<uint64_t> bytes =
36+
ParsingUtils::ParseUserFriendlySizeExpression(option_arg))
37+
m_ipt_trace_size = *bytes;
4038
else
41-
m_ipt_trace_size = ipt_trace_size;
39+
error.SetErrorStringWithFormat("invalid bytes expression for '%s'",
40+
option_arg.str().c_str());
4241
break;
4342
}
4443
case 't': {
@@ -98,24 +97,21 @@ Status CommandObjectProcessTraceStartIntelPT::CommandOptions::SetOptionValue(
9897

9998
switch (short_option) {
10099
case 's': {
101-
int64_t ipt_trace_size;
102-
if (option_arg.empty() || option_arg.getAsInteger(0, ipt_trace_size) ||
103-
ipt_trace_size < 0)
104-
error.SetErrorStringWithFormat("invalid integer value for option '%s'",
105-
option_arg.str().c_str());
100+
if (Optional<uint64_t> bytes =
101+
ParsingUtils::ParseUserFriendlySizeExpression(option_arg))
102+
m_ipt_trace_size = *bytes;
106103
else
107-
m_ipt_trace_size = ipt_trace_size;
104+
error.SetErrorStringWithFormat("invalid bytes expression for '%s'",
105+
option_arg.str().c_str());
108106
break;
109107
}
110108
case 'l': {
111-
int64_t process_buffer_size_limit;
112-
if (option_arg.empty() ||
113-
option_arg.getAsInteger(0, process_buffer_size_limit) ||
114-
process_buffer_size_limit < 0)
115-
error.SetErrorStringWithFormat("invalid integer value for option '%s'",
116-
option_arg.str().c_str());
109+
if (Optional<uint64_t> bytes =
110+
ParsingUtils::ParseUserFriendlySizeExpression(option_arg))
111+
m_process_buffer_size_limit = *bytes;
117112
else
118-
m_process_buffer_size_limit = process_buffer_size_limit;
113+
error.SetErrorStringWithFormat("invalid bytes expression for '%s'",
114+
option_arg.str().c_str());
119115
break;
120116
}
121117
case 't': {
@@ -168,3 +164,45 @@ bool CommandObjectProcessTraceStartIntelPT::DoExecute(
168164

169165
return result.Succeeded();
170166
}
167+
168+
Optional<uint64_t>
169+
ParsingUtils::ParseUserFriendlySizeExpression(llvm::StringRef size_expression) {
170+
if (size_expression.empty()) {
171+
return llvm::None;
172+
}
173+
const uint64_t kBytesMultiplier = 1;
174+
const uint64_t kKibiBytesMultiplier = 1024;
175+
const uint64_t kMebiBytesMultiplier = 1024 * 1024;
176+
177+
DenseMap<StringRef, uint64_t> multipliers = {
178+
{"mib", kMebiBytesMultiplier}, {"mb", kMebiBytesMultiplier},
179+
{"m", kMebiBytesMultiplier}, {"kib", kKibiBytesMultiplier},
180+
{"kb", kKibiBytesMultiplier}, {"k", kKibiBytesMultiplier},
181+
{"b", kBytesMultiplier}, {"", kBytesMultiplier}};
182+
183+
const auto non_digit_index = size_expression.find_first_not_of("0123456789");
184+
if (non_digit_index == 0) { // expression starts from from non-digit char.
185+
return llvm::None;
186+
}
187+
188+
const llvm::StringRef number_part =
189+
non_digit_index == llvm::StringRef::npos
190+
? size_expression
191+
: size_expression.substr(0, non_digit_index);
192+
uint64_t parsed_number;
193+
if (number_part.getAsInteger(10, parsed_number)) {
194+
return llvm::None;
195+
}
196+
197+
if (non_digit_index != llvm::StringRef::npos) { // if expression has units.
198+
const auto multiplier = size_expression.substr(non_digit_index).lower();
199+
200+
auto it = multipliers.find(multiplier);
201+
if (it == multipliers.end())
202+
return llvm::None;
203+
204+
return parsed_number * it->second;
205+
} else {
206+
return parsed_number;
207+
}
208+
}

lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@ class CommandObjectProcessTraceStartIntelPT : public CommandObjectParsed {
109109
CommandOptions m_options;
110110
};
111111

112+
namespace ParsingUtils {
113+
/// Convert an integral size expression like 12KiB or 4MB into bytes. The units
114+
/// are taken loosely to help users input sizes into LLDB, e.g. KiB and KB are
115+
/// considered the same (2^20 bytes) for simplicity.
116+
///
117+
/// \param[in] size_expression
118+
/// String expression which is an integral number plus a unit that can be
119+
/// lower or upper case. Supported units: K, KB and KiB for 2^10 bytes; M,
120+
/// MB and MiB for 2^20 bytes; and B for bytes. A single integral number is
121+
/// considered bytes.
122+
/// \return
123+
/// The converted number of bytes or \a llvm::None if the expression is
124+
/// invalid.
125+
llvm::Optional<uint64_t>
126+
ParseUserFriendlySizeExpression(llvm::StringRef size_expression);
127+
} // namespace ParsingUtils
128+
112129
} // namespace trace_intel_pt
113130
} // namespace lldb_private
114131

lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,20 @@ Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids,
476476

477477
if (configuration) {
478478
if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) {
479-
dict->GetValueForKeyAsInteger("iptTraceSize", ipt_trace_size);
479+
llvm::StringRef ipt_trace_size_not_parsed;
480+
if (dict->GetValueForKeyAsString("iptTraceSize",
481+
ipt_trace_size_not_parsed)) {
482+
if (Optional<uint64_t> bytes =
483+
ParsingUtils::ParseUserFriendlySizeExpression(
484+
ipt_trace_size_not_parsed))
485+
ipt_trace_size = *bytes;
486+
else
487+
return createStringError(inconvertibleErrorCode(),
488+
"iptTraceSize is wrong bytes expression");
489+
} else {
490+
dict->GetValueForKeyAsInteger("iptTraceSize", ipt_trace_size);
491+
}
492+
480493
dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc);
481494
dict->GetValueForKeyAsInteger("psbPeriod", psb_period);
482495
} else {

lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ let Command = "thread trace start intel pt" in {
1111
Arg<"Value">,
1212
Desc<"Trace size in bytes per thread. It must be a power of 2 greater "
1313
"than or equal to 4096 (2^12). The trace is circular keeping "
14-
"the most recent data. Defaults to 4096 bytes.">;
14+
"the most recent data. Defaults to 4096 bytes. It's possible to "
15+
"specify size using multiples of unit bytes, e.g., 4KB, 1MB, 1MiB, "
16+
"where 1K is 1024 bytes and 1M is 1048576 bytes.">;
1517
def thread_trace_start_intel_pt_tsc: Option<"tsc", "t">,
1618
Group<1>,
1719
Desc<"Enable the use of TSC timestamps. This is supported on all devices "
@@ -40,7 +42,8 @@ let Command = "process trace start intel pt" in {
4042
Arg<"Value">,
4143
Desc<"Size in bytes used by each individual per-thread or per-cpu trace "
4244
"buffer. It must be a power of 2 greater than or equal to 4096 (2^12) "
43-
"bytes.">;
45+
"bytes. It's possible to specify a unit for these bytes, like 4KB, "
46+
"16KiB or 1MB. Lower case units are allowed for convenience.">;
4447
def process_trace_start_intel_pt_per_cpu_tracing:
4548
Option<"per-cpu-tracing", "c">,
4649
Group<1>,
@@ -53,7 +56,8 @@ let Command = "process trace start intel pt" in {
5356
"option forces the capture of TSC timestamps (see --tsc). Also, this "
5457
"option can't be used simulatenously with any other trace sessions "
5558
"because of its system-wide nature.">;
56-
def process_trace_start_intel_pt_process_size_limit: Option<"total-size-limit", "l">,
59+
def process_trace_start_intel_pt_process_size_limit:
60+
Option<"total-size-limit", "l">,
5761
Group<1>,
5862
Arg<"Value">,
5963
Desc<"Maximum total trace size per process in bytes. This limit applies to "
@@ -62,7 +66,9 @@ let Command = "process trace start intel pt" in {
6266
"Whenever a thread is attempted to be traced due to this command and "
6367
"the limit would be reached, the process is stopped with a "
6468
"\"processor trace\" reason, so that the user can retrace the process "
65-
"if needed. Defaults to 500MB.">;
69+
"if needed. Defaults to 500MB. It's possible to specify a unit for "
70+
"these bytes, like 4KB, 16KiB or 1MB. Lower case units are allowed "
71+
"for convenience.">;
6672
def process_trace_start_intel_pt_tsc: Option<"tsc", "t">,
6773
Group<1>,
6874
Desc<"Enable the use of TSC timestamps. This is supported on all devices "

lldb/test/API/commands/trace/TestTraceStartStop.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,62 @@ def testStartSessionWithWrongSize(self):
4646

4747
self.traceStartThread(iptTraceSize=1048576)
4848

49+
@testSBAPIAndCommands
50+
def testStartSessionWithSizeDeclarationInUnits(self):
51+
self.expect("file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
52+
self.expect("b main")
53+
self.expect("r")
54+
55+
self.traceStartThread(
56+
error=True, iptTraceSize="abc",
57+
substrs=["invalid bytes expression for 'abc'"])
58+
59+
self.traceStartThread(
60+
error=True, iptTraceSize="123.12",
61+
substrs=["invalid bytes expression for '123.12'"])
62+
63+
self.traceStartThread(
64+
error=True, iptTraceSize="\"\"",
65+
substrs=["invalid bytes expression for ''"])
66+
67+
self.traceStartThread(
68+
error=True, iptTraceSize="2000B",
69+
substrs=["The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 2000"])
70+
71+
self.traceStartThread(
72+
error=True, iptTraceSize="3MB",
73+
substrs=["The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3145728"])
74+
75+
self.traceStartThread(
76+
error=True, iptTraceSize="3MiB",
77+
substrs=["The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3145728"])
78+
79+
self.traceStartThread(
80+
error=True, iptTraceSize="3mib",
81+
substrs=["The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3145728"])
82+
83+
self.traceStartThread(
84+
error=True, iptTraceSize="3M",
85+
substrs=["The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3145728"])
86+
87+
self.traceStartThread(
88+
error=True, iptTraceSize="3KB",
89+
substrs=["The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3072"])
90+
91+
self.traceStartThread(
92+
error=True, iptTraceSize="3KiB",
93+
substrs=["The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3072"])
94+
95+
self.traceStartThread(
96+
error=True, iptTraceSize="3K",
97+
substrs=["The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3072"])
98+
99+
self.traceStartThread(
100+
error=True, iptTraceSize="3MS",
101+
substrs=["invalid bytes expression for '3MS'"])
102+
103+
self.traceStartThread(iptTraceSize="1048576")
104+
49105
@skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64']))
50106
def testSBAPIHelp(self):
51107
self.expect("file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))

0 commit comments

Comments
 (0)