diff --git a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h --- a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h +++ b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h @@ -50,6 +50,10 @@ /// Whether to have a trace buffer per thread or per cpu cpu. llvm::Optional per_cpu_tracing; + /// Disable the cgroup filtering that is automatically applied in per cpu + /// mode. + llvm::Optional disable_cgroup_filtering; + bool IsPerCpuTracing() const; }; @@ -107,6 +111,7 @@ struct TraceIntelPTGetStateResponse : TraceGetStateResponse { /// The TSC to wall time conversion if it exists, otherwise \b nullptr. llvm::Optional tsc_perf_zero_conversion; + bool using_cgroup_filtering = false; }; bool fromJSON(const llvm::json::Value &value, diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -65,6 +66,39 @@ } } +/// \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 GetCGroupFileDescriptor(lldb::pid_t pid) { + static Optional 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) { @@ -83,14 +117,19 @@ 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 cgroup_fd; + if (!request.disable_cgroup_filtering.getValueOr(false)) + cgroup_fd = GetCGroupFileDescriptor(m_process.GetID()); + if (Expected trace = IntelPTMultiCoreTrace::StartOnAllCores(effective_request, - m_process)) { + m_process, cgroup_fd)) { m_process_trace_up = std::move(*trace); return Error::success(); } else { diff --git a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h --- a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h @@ -35,12 +35,18 @@ /// \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> StartOnAllCores(const TraceIntelPTStartRequest &request, - NativeProcessProtocol &process); + NativeProcessProtocol &process, + llvm::Optional cgroup_fd = llvm::None); /// Execute the provided callback on each core that is being traced. /// @@ -90,8 +96,9 @@ llvm::DenseMap> &&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> @@ -99,6 +106,7 @@ /// The target process. NativeProcessProtocol &m_process; + bool m_using_cgroup_filtering; }; } // namespace process_linux diff --git a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp @@ -35,7 +35,8 @@ Expected> IntelPTMultiCoreTrace::StartOnAllCores(const TraceIntelPTStartRequest &request, - NativeProcessProtocol &process) { + NativeProcessProtocol &process, + Optional cgroup_fd) { Expected> cpu_ids = GetAvailableLogicalCoreIDs(); if (!cpu_ids) return cpu_ids.takeError(); @@ -52,7 +53,7 @@ for (cpu_id_t cpu_id : *cpu_ids) { Expected core_trace = IntelPTSingleBufferTrace::Start(request, /*tid=*/None, cpu_id, - /*disabled=*/true); + /*disabled=*/true, cgroup_fd); if (!core_trace) return IncludePerfEventParanoidMessageInError(core_trace.takeError()); @@ -68,7 +69,7 @@ } return std::unique_ptr( - new IntelPTMultiCoreTrace(std::move(traces), process)); + new IntelPTMultiCoreTrace(std::move(traces), process, (bool)cgroup_fd)); } void IntelPTMultiCoreTrace::ForEachCore( @@ -106,6 +107,7 @@ TraceIntelPTGetStateResponse IntelPTMultiCoreTrace::GetState() { TraceIntelPTGetStateResponse state; + state.using_cgroup_filtering = m_using_cgroup_filtering; for (NativeThreadProtocol &thread : m_process.Threads()) state.traced_threads.push_back( diff --git a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h --- a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h @@ -44,6 +44,11 @@ /// 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. @@ -51,7 +56,7 @@ Start(const TraceIntelPTStartRequest &request, llvm::Optional tid, llvm::Optional cpu_id = llvm::None, - bool disabled = false); + bool disabled = false, llvm::Optional cgroup_fd = llvm::None); /// \return /// The bytes requested by a jLLDBTraceGetBinaryData packet that was routed diff --git a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp @@ -231,10 +231,9 @@ return m_perf_event.GetReadOnlyAuxBuffer(); } -Expected -IntelPTSingleBufferTrace::Start(const TraceIntelPTStartRequest &request, - Optional tid, - Optional cpu_id, bool disabled) { +Expected IntelPTSingleBufferTrace::Start( + const TraceIntelPTStartRequest &request, Optional tid, + Optional cpu_id, bool disabled, Optional cgroup_fd) { #ifndef PERF_ATTR_SIZE_VER5 return createStringError(inconvertibleErrorCode(), "Intel PT Linux perf event not supported"); @@ -265,8 +264,14 @@ 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 perf_event = PerfEvent::Init(*attr, tid, cpu_id)) { + if (Expected 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)) { diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h @@ -79,6 +79,7 @@ bool m_enable_tsc; llvm::Optional m_psb_period; bool m_per_cpu_tracing; + bool m_disable_cgroup_filtering; }; CommandObjectProcessTraceStartIntelPT(TraceIntelPT &trace, diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp @@ -126,6 +126,10 @@ 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) || @@ -149,6 +153,7 @@ m_enable_tsc = kDefaultEnableTscValue; m_psb_period = kDefaultPsbPeriod; m_per_cpu_tracing = kDefaultPerCpuTracing; + m_disable_cgroup_filtering = kDefaultDisableCgroupFiltering; } llvm::ArrayRef @@ -158,10 +163,10 @@ 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); diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -104,12 +104,16 @@ /// 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 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 = diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -226,6 +226,12 @@ 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 @@ -408,17 +414,22 @@ [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 psb_period, bool per_cpu_tracing) { + Optional 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; @@ -426,6 +437,7 @@ 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)); } @@ -435,6 +447,7 @@ bool enable_tsc = kDefaultEnableTscValue; Optional psb_period = kDefaultPsbPeriod; bool per_cpu_tracing = kDefaultPerCpuTracing; + bool disable_cgroup_filtering = kDefaultDisableCgroupFiltering; if (configuration) { if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { @@ -444,6 +457,8 @@ 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"); @@ -451,7 +466,7 @@ } 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 tids, diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h @@ -21,6 +21,7 @@ const bool kDefaultEnableTscValue = false; const llvm::Optional kDefaultPsbPeriod = llvm::None; const bool kDefaultPerCpuTracing = false; +const bool kDefaultDisableCgroupFiltering = false; } // namespace trace_intel_pt } // namespace lldb_private diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.h @@ -49,10 +49,23 @@ /// The number of continuous executions found for the given \p tid. size_t GetNumContinuousExecutionsForThread(lldb::tid_t tid) const; + /// \return + /// The number of PSB blocks for a given thread in all cores. + size_t GePSBBlocksCountForThread(lldb::tid_t tid) const; + /// \return /// The total number of continuous executions found across CPUs. size_t GetTotalContinuousExecutionsCount() const; + /// \return + /// The number of psb blocks in all cores that couldn't be matched with a + /// thread execution coming from context switch traces. + size_t GetUnattributedPSBBlocksCount() const; + + /// \return + /// The total number of PSB blocks in all cores. + size_t GetTotalPSBBlocksCount() const; + private: /// Traverse the context switch traces and the basic intel pt continuous /// subtraces and produce a list of continuous executions for each process and @@ -80,7 +93,8 @@ /// This variable will be non-None if a severe error happened during the setup /// of the decoder and we don't want decoding to be reattempted. llvm::Optional m_setup_error; - uint64_t m_unattributed_intelpt_subtraces; + uint64_t m_unattributed_psb_blocks = 0; + uint64_t m_total_psb_blocks = 0; }; } // namespace trace_intel_pt diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.cpp @@ -105,6 +105,7 @@ if (!intel_pt_subtraces) return intel_pt_subtraces.takeError(); + m_total_psb_blocks += intel_pt_subtraces->size(); // We'll be iterating through the thread continuous executions and the intel // pt subtraces sorted by time. auto it = intel_pt_subtraces->begin(); @@ -118,7 +119,7 @@ if (it->tsc > thread_execution.GetStartTSC()) { execution.intelpt_subtraces.push_back(*it); } else { - m_unattributed_intelpt_subtraces++; + m_unattributed_psb_blocks++; } } continuous_executions_per_thread[thread_execution.tid].push_back( @@ -137,6 +138,8 @@ }); if (err) return std::move(err); + + m_unattributed_psb_blocks += intel_pt_subtraces->end() - it; } // We now sort the executions of each thread to have them ready for // instruction decoding @@ -187,3 +190,22 @@ count += kv.second.size(); return count; } + +size_t +TraceIntelPTMultiCpuDecoder::GePSBBlocksCountForThread(lldb::tid_t tid) const { + if (!m_continuous_executions_per_thread) + return 0; + size_t count = 0; + auto it = m_continuous_executions_per_thread->find(tid); + for (const IntelPTThreadContinousExecution &execution : it->second) + count += execution.intelpt_subtraces.size(); + return count; +} + +size_t TraceIntelPTMultiCpuDecoder::GetUnattributedPSBBlocksCount() const { + return m_unattributed_psb_blocks; +} + +size_t TraceIntelPTMultiCpuDecoder::GetTotalPSBBlocksCount() const { + return m_total_psb_blocks; +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td @@ -83,6 +83,11 @@ "converted to the approximate number of raw trace bytes between PSB " "packets as: 2 ^ (value + 11), e.g. value 3 means 16KiB between PSB " "packets. Defaults to 0 if supported.">; + def process_trace_start_intel_pt_disable_cgroup_filtering: + Option<"disable-cgroup-filtering", "d">, + Desc<"Disable the automatic cgroup filtering that is applied if --per-cpu " + "is provided. Cgroup filtering allows collecting intel pt data " + "exclusively of processes of the same cgroup as the target.">; } let Command = "process trace save intel pt" in { diff --git a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp --- a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +++ b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp @@ -53,7 +53,8 @@ if (packet.IsProcessTracing()) { if (!o.map("processBufferSizeLimit", packet.process_buffer_size_limit) || - !o.map("perCpuTracing", packet.per_cpu_tracing)) + !o.map("perCpuTracing", packet.per_cpu_tracing) || + !o.map("disableCgroupTracing", packet.disable_cgroup_filtering)) return false; } return true; @@ -67,6 +68,7 @@ obj.try_emplace("psbPeriod", packet.psb_period); obj.try_emplace("enableTsc", packet.enable_tsc); obj.try_emplace("perCpuTracing", packet.per_cpu_tracing); + obj.try_emplace("disableCgroupTracing", packet.disable_cgroup_filtering); return base; } @@ -108,13 +110,15 @@ json::Path path) { ObjectMapper o(value, path); return o && fromJSON(value, (TraceGetStateResponse &)packet, path) && - o.map("tscPerfZeroConversion", packet.tsc_perf_zero_conversion); + o.map("tscPerfZeroConversion", packet.tsc_perf_zero_conversion) && + o.map("usingCgroupFiltering", packet.using_cgroup_filtering); } json::Value toJSON(const TraceIntelPTGetStateResponse &packet) { json::Value base = toJSON((const TraceGetStateResponse &)packet); - base.getAsObject()->insert( - {"tscPerfZeroConversion", packet.tsc_perf_zero_conversion}); + json::Object &obj = *base.getAsObject(); + obj.insert({"tscPerfZeroConversion", packet.tsc_perf_zero_conversion}); + obj.insert({"usingCgroupFiltering", packet.using_cgroup_filtering}); return base; }