Skip to content

Commit

Permalink
[trace][intel pt] Add a cgroup filter
Browse files Browse the repository at this point in the history
It turns out that cgroup filtering is relatively trivial and works
really nicely. Thid diffs adds automatic cgroup filtering when in
per-cpu mode, unless a new --disable-cgroup-filtering flag is passed in
the start command. At least on Meta machines, all processes are spawned
inside a cgroup by default, which comes super handy, because per cpu
tracing is now much more precise.

A manual test gave me this result

- Without filtering:
    Total number of trace items: 36083
    Total number of continuous executions found: 229
    Number of continuous executions for this thread: 2
    Total number of PSB blocks found: 98
    Number of PSB blocks for this thread 2
    Total number of unattributed PSB blocks found: 38

- With filtering:
    Total number of trace items: 87756
    Total number of continuous executions found: 123
    Number of continuous executions for this thread: 2
    Total number of PSB blocks found: 10
    Number of PSB blocks for this thread 3
    Total number of unattributed PSB blocks found: 2

Filtering gives us great results. The number of instructions collected
more than double (probalby because we have less noise in the trace), and
we have much less unattributed PSBs blocks and unrelated PSBs in
general. The ones that are unrelated probably belong to other processes
in the same cgroup.

Differential Revision: https://reviews.llvm.org/D129257
  • Loading branch information
Gaurav Gaur authored and walter-erquinigo committed Jul 13, 2022
1 parent 4a843d9 commit d30fd5c
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 28 deletions.
5 changes: 5 additions & 0 deletions lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h
Expand Up @@ -50,6 +50,10 @@ struct TraceIntelPTStartRequest : TraceStartRequest {
/// Whether to have a trace buffer per thread or per cpu cpu.
llvm::Optional<bool> per_cpu_tracing;

/// Disable the cgroup filtering that is automatically applied in per cpu
/// mode.
llvm::Optional<bool> disable_cgroup_filtering;

bool IsPerCpuTracing() const;
};

Expand Down Expand Up @@ -107,6 +111,7 @@ struct LinuxPerfZeroTscConversion {
struct TraceIntelPTGetStateResponse : TraceGetStateResponse {
/// The TSC to wall time conversion if it exists, otherwise \b nullptr.
llvm::Optional<LinuxPerfZeroTscConversion> tsc_perf_zero_conversion;
bool using_cgroup_filtering = false;
};

bool fromJSON(const llvm::json::Value &value,
Expand Down
43 changes: 41 additions & 2 deletions lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp
Expand Up @@ -21,6 +21,7 @@

#include <algorithm>
#include <cstddef>
#include <fcntl.h>
#include <fstream>
#include <linux/perf_event.h>
#include <sstream>
Expand Down Expand Up @@ -65,6 +66,39 @@ Error IntelPTCollector::TraceStop(const TraceStopRequest &request) {
}
}

/// \return
/// some file descriptor in /sys/fs/ associated with the cgroup of the given
/// pid, or \a llvm::None if the pid is not part of a cgroup.
static Optional<int> GetCGroupFileDescriptor(lldb::pid_t pid) {
static Optional<int> fd;
if (fd)
return fd;

std::ifstream ifile;
ifile.open(formatv("/proc/{0}/cgroup", pid));
if (!ifile)
return None;

std::string line;
while (std::getline(ifile, line)) {
if (line.find("0:") != 0)
continue;

std::string slice = line.substr(line.find_first_of("/"));
if (slice.empty())
return None;
std::string cgroup_file = formatv("/sys/fs/cgroup/{0}", slice);
// This cgroup should for the duration of the target, so we don't need to
// invoke close ourselves.
int maybe_fd = open(cgroup_file.c_str(), O_RDONLY);
if (maybe_fd != -1) {
fd = maybe_fd;
return fd;
}
}
return None;
}

Error IntelPTCollector::TraceStart(const TraceIntelPTStartRequest &request) {
if (request.IsProcessTracing()) {
if (m_process_trace_up) {
Expand All @@ -83,14 +117,19 @@ Error IntelPTCollector::TraceStart(const TraceIntelPTStartRequest &request) {
if (!tsc_conversion)
return tsc_conversion.takeError();

// We force the enabledment of TSCs, which is needed for correlating the
// We force the enablement of TSCs, which is needed for correlating the
// cpu traces.
TraceIntelPTStartRequest effective_request = request;
effective_request.enable_tsc = true;

// We try to use cgroup filtering whenever possible
Optional<int> cgroup_fd;
if (!request.disable_cgroup_filtering.getValueOr(false))
cgroup_fd = GetCGroupFileDescriptor(m_process.GetID());

if (Expected<IntelPTProcessTraceUP> trace =
IntelPTMultiCoreTrace::StartOnAllCores(effective_request,
m_process)) {
m_process, cgroup_fd)) {
m_process_trace_up = std::move(*trace);
return Error::success();
} else {
Expand Down
8 changes: 5 additions & 3 deletions lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp
Expand Up @@ -35,7 +35,8 @@ static Error IncludePerfEventParanoidMessageInError(Error &&error) {

Expected<std::unique_ptr<IntelPTMultiCoreTrace>>
IntelPTMultiCoreTrace::StartOnAllCores(const TraceIntelPTStartRequest &request,
NativeProcessProtocol &process) {
NativeProcessProtocol &process,
Optional<int> cgroup_fd) {
Expected<ArrayRef<cpu_id_t>> cpu_ids = GetAvailableLogicalCoreIDs();
if (!cpu_ids)
return cpu_ids.takeError();
Expand All @@ -52,7 +53,7 @@ IntelPTMultiCoreTrace::StartOnAllCores(const TraceIntelPTStartRequest &request,
for (cpu_id_t cpu_id : *cpu_ids) {
Expected<IntelPTSingleBufferTrace> core_trace =
IntelPTSingleBufferTrace::Start(request, /*tid=*/None, cpu_id,
/*disabled=*/true);
/*disabled=*/true, cgroup_fd);
if (!core_trace)
return IncludePerfEventParanoidMessageInError(core_trace.takeError());

Expand All @@ -68,7 +69,7 @@ IntelPTMultiCoreTrace::StartOnAllCores(const TraceIntelPTStartRequest &request,
}

return std::unique_ptr<IntelPTMultiCoreTrace>(
new IntelPTMultiCoreTrace(std::move(traces), process));
new IntelPTMultiCoreTrace(std::move(traces), process, (bool)cgroup_fd));
}

void IntelPTMultiCoreTrace::ForEachCore(
Expand Down Expand Up @@ -106,6 +107,7 @@ void IntelPTMultiCoreTrace::ProcessWillResume() {

TraceIntelPTGetStateResponse IntelPTMultiCoreTrace::GetState() {
TraceIntelPTGetStateResponse state;
state.using_cgroup_filtering = m_using_cgroup_filtering;

for (NativeThreadProtocol &thread : m_process.Threads())
state.traced_threads.push_back(
Expand Down
14 changes: 11 additions & 3 deletions lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h
Expand Up @@ -35,12 +35,18 @@ class IntelPTMultiCoreTrace : public IntelPTProcessTrace {
/// \param[in] process
/// The process being debugged.
///
/// \param[in] cgroup_fd
/// A file descriptor in /sys/fs associated with the cgroup of the process to
/// trace. If not \a llvm::None, then the trace sesion will use cgroup
/// filtering.
///
/// \return
/// An \a IntelPTMultiCoreTrace instance if tracing was successful, or
/// an \a llvm::Error otherwise.
static llvm::Expected<std::unique_ptr<IntelPTMultiCoreTrace>>
StartOnAllCores(const TraceIntelPTStartRequest &request,
NativeProcessProtocol &process);
NativeProcessProtocol &process,
llvm::Optional<int> cgroup_fd = llvm::None);

/// Execute the provided callback on each core that is being traced.
///
Expand Down Expand Up @@ -90,15 +96,17 @@ class IntelPTMultiCoreTrace : public IntelPTProcessTrace {
llvm::DenseMap<lldb::cpu_id_t,
std::pair<IntelPTSingleBufferTrace, ContextSwitchTrace>>
&&traces_per_core,
NativeProcessProtocol &process)
: m_traces_per_core(std::move(traces_per_core)), m_process(process) {}
NativeProcessProtocol &process, bool using_cgroup_filtering)
: m_traces_per_core(std::move(traces_per_core)), m_process(process),
m_using_cgroup_filtering(using_cgroup_filtering) {}

llvm::DenseMap<lldb::cpu_id_t,
std::pair<IntelPTSingleBufferTrace, ContextSwitchTrace>>
m_traces_per_core;

/// The target process.
NativeProcessProtocol &m_process;
bool m_using_cgroup_filtering;
};

} // namespace process_linux
Expand Down
15 changes: 10 additions & 5 deletions lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp
Expand Up @@ -231,10 +231,9 @@ Expected<std::vector<uint8_t>> IntelPTSingleBufferTrace::GetIptTrace() {
return m_perf_event.GetReadOnlyAuxBuffer();
}

Expected<IntelPTSingleBufferTrace>
IntelPTSingleBufferTrace::Start(const TraceIntelPTStartRequest &request,
Optional<lldb::tid_t> tid,
Optional<cpu_id_t> cpu_id, bool disabled) {
Expected<IntelPTSingleBufferTrace> IntelPTSingleBufferTrace::Start(
const TraceIntelPTStartRequest &request, Optional<lldb::tid_t> tid,
Optional<cpu_id_t> cpu_id, bool disabled, Optional<int> cgroup_fd) {
#ifndef PERF_ATTR_SIZE_VER5
return createStringError(inconvertibleErrorCode(),
"Intel PT Linux perf event not supported");
Expand Down Expand Up @@ -265,8 +264,14 @@ IntelPTSingleBufferTrace::Start(const TraceIntelPTStartRequest &request,

LLDB_LOG(log, "Will create intel pt trace buffer of size {0}",
request.ipt_trace_size);
unsigned long flags = 0;
if (cgroup_fd) {
tid = *cgroup_fd;
flags |= PERF_FLAG_PID_CGROUP;
}

if (Expected<PerfEvent> perf_event = PerfEvent::Init(*attr, tid, cpu_id)) {
if (Expected<PerfEvent> perf_event =
PerfEvent::Init(*attr, tid, cpu_id, -1, flags)) {
if (Error mmap_err = perf_event->MmapMetadataAndBuffers(
/*num_data_pages=*/0, aux_buffer_numpages,
/*data_buffer_write=*/true)) {
Expand Down
7 changes: 6 additions & 1 deletion lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h
Expand Up @@ -44,14 +44,19 @@ class IntelPTSingleBufferTrace {
/// Similarly, if \b false, data is collected right away until \a Pause is
/// invoked.
///
/// \param[in] cgroup_fd
/// A file descriptor in /sys/fs associated with the cgroup of the process
/// to trace. If not \a llvm::None, then the trace sesion will use cgroup
/// filtering.
///
/// \return
/// A \a IntelPTSingleBufferTrace instance if tracing was successful, or
/// an \a llvm::Error otherwise.
static llvm::Expected<IntelPTSingleBufferTrace>
Start(const TraceIntelPTStartRequest &request,
llvm::Optional<lldb::tid_t> tid,
llvm::Optional<lldb::cpu_id_t> cpu_id = llvm::None,
bool disabled = false);
bool disabled = false, llvm::Optional<int> cgroup_fd = llvm::None);

/// \return
/// The bytes requested by a jLLDBTraceGetBinaryData packet that was routed
Expand Down
Expand Up @@ -122,6 +122,10 @@ Status CommandObjectProcessTraceStartIntelPT::CommandOptions::SetOptionValue(
m_per_cpu_tracing = true;
break;
}
case 'd': {
m_disable_cgroup_filtering = true;
break;
}
case 'p': {
int64_t psb_period;
if (option_arg.empty() || option_arg.getAsInteger(0, psb_period) ||
Expand All @@ -145,6 +149,7 @@ void CommandObjectProcessTraceStartIntelPT::CommandOptions::
m_enable_tsc = kDefaultEnableTscValue;
m_psb_period = kDefaultPsbPeriod;
m_per_cpu_tracing = kDefaultPerCpuTracing;
m_disable_cgroup_filtering = kDefaultDisableCgroupFiltering;
}

llvm::ArrayRef<OptionDefinition>
Expand All @@ -154,10 +159,10 @@ CommandObjectProcessTraceStartIntelPT::CommandOptions::GetDefinitions() {

bool CommandObjectProcessTraceStartIntelPT::DoExecute(
Args &command, CommandReturnObject &result) {
if (Error err = m_trace.Start(m_options.m_ipt_trace_size,
m_options.m_process_buffer_size_limit,
m_options.m_enable_tsc, m_options.m_psb_period,
m_options.m_per_cpu_tracing))
if (Error err = m_trace.Start(
m_options.m_ipt_trace_size, m_options.m_process_buffer_size_limit,
m_options.m_enable_tsc, m_options.m_psb_period,
m_options.m_per_cpu_tracing, m_options.m_disable_cgroup_filtering))
result.SetError(Status(std::move(err)));
else
result.SetStatus(eReturnStatusSuccessFinishResult);
Expand Down
Expand Up @@ -79,6 +79,7 @@ class CommandObjectProcessTraceStartIntelPT : public CommandObjectParsed {
bool m_enable_tsc;
llvm::Optional<uint64_t> m_psb_period;
bool m_per_cpu_tracing;
bool m_disable_cgroup_filtering;
};

CommandObjectProcessTraceStartIntelPT(TraceIntelPT &trace,
Expand Down
21 changes: 18 additions & 3 deletions lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
Expand Up @@ -226,6 +226,12 @@ void TraceIntelPT::DumpTraceInfo(Thread &thread, Stream &s, bool verbose) {
s.Format(
" Number of continuous executions for this thread: {0}\n",
storage.multicpu_decoder->GetNumContinuousExecutionsForThread(tid));
s.Format(" Total number of PSB blocks found: {0}\n",
storage.multicpu_decoder->GetTotalPSBBlocksCount());
s.Format(" Number of PSB blocks for this thread {0}\n",
storage.multicpu_decoder->GePSBBlocksCountForThread(tid));
s.Format(" Total number of unattributed PSB blocks found: {0}\n",
storage.multicpu_decoder->GetUnattributedPSBBlocksCount());
}

// Errors
Expand Down Expand Up @@ -408,24 +414,30 @@ const char *TraceIntelPT::GetStartConfigurationHelp() {
[process tracing only]
- int processBufferSizeLimit (defaults to {4} MiB):
[process tracing only]
- boolean disableCgroupFiltering (default to {5}):
[process tracing only])",
kDefaultIptTraceSize, kDefaultEnableTscValue,
kDefaultPsbPeriod, kDefaultPerCpuTracing,
kDefaultProcessBufferSizeLimit / 1024 / 1024));
kDefaultProcessBufferSizeLimit / 1024 / 1024,
kDefaultDisableCgroupFiltering));
}
return message->c_str();
}

Error TraceIntelPT::Start(uint64_t ipt_trace_size,
uint64_t total_buffer_size_limit, bool enable_tsc,
Optional<uint64_t> psb_period, bool per_cpu_tracing) {
Optional<uint64_t> psb_period, bool per_cpu_tracing,
bool disable_cgroup_filtering) {
TraceIntelPTStartRequest request;
request.ipt_trace_size = ipt_trace_size;
request.process_buffer_size_limit = total_buffer_size_limit;
request.enable_tsc = enable_tsc;
request.psb_period = psb_period;
request.type = GetPluginName().str();
request.per_cpu_tracing = per_cpu_tracing;
request.disable_cgroup_filtering = disable_cgroup_filtering;
return Trace::Start(toJSON(request));
}

Expand All @@ -435,6 +447,7 @@ Error TraceIntelPT::Start(StructuredData::ObjectSP configuration) {
bool enable_tsc = kDefaultEnableTscValue;
Optional<uint64_t> psb_period = kDefaultPsbPeriod;
bool per_cpu_tracing = kDefaultPerCpuTracing;
bool disable_cgroup_filtering = kDefaultDisableCgroupFiltering;

if (configuration) {
if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) {
Expand All @@ -444,14 +457,16 @@ Error TraceIntelPT::Start(StructuredData::ObjectSP configuration) {
dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc);
dict->GetValueForKeyAsInteger("psbPeriod", psb_period);
dict->GetValueForKeyAsBoolean("perCpuTracing", per_cpu_tracing);
dict->GetValueForKeyAsBoolean("disableCgroupFiltering",
disable_cgroup_filtering);
} else {
return createStringError(inconvertibleErrorCode(),
"configuration object is not a dictionary");
}
}

return Start(ipt_trace_size, process_buffer_size_limit, enable_tsc,
psb_period, per_cpu_tracing);
psb_period, per_cpu_tracing, disable_cgroup_filtering);
}

llvm::Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids,
Expand Down
6 changes: 5 additions & 1 deletion lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h
Expand Up @@ -105,12 +105,16 @@ class TraceIntelPT : public Trace {
/// This value defines whether to have an intel pt trace buffer per thread
/// or per cpu core.
///
/// \param[in] disable_cgroup_filtering
/// Disable the cgroup filtering that is automatically applied when doing
/// per cpu tracing.
///
/// \return
/// \a llvm::Error::success if the operation was successful, or
/// \a llvm::Error otherwise.
llvm::Error Start(uint64_t ipt_trace_size, uint64_t total_buffer_size_limit,
bool enable_tsc, llvm::Optional<uint64_t> psb_period,
bool m_per_cpu_tracing);
bool m_per_cpu_tracing, bool disable_cgroup_filtering);

/// \copydoc Trace::Start
llvm::Error Start(StructuredData::ObjectSP configuration =
Expand Down
1 change: 1 addition & 0 deletions lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h
Expand Up @@ -21,6 +21,7 @@ const size_t kDefaultProcessBufferSizeLimit = 5 * 1024 * 1024; // 500MB
const bool kDefaultEnableTscValue = false;
const llvm::Optional<size_t> kDefaultPsbPeriod = llvm::None;
const bool kDefaultPerCpuTracing = false;
const bool kDefaultDisableCgroupFiltering = false;

} // namespace trace_intel_pt
} // namespace lldb_private
Expand Down

0 comments on commit d30fd5c

Please sign in to comment.