diff --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt index d4ec112f3936cd..3eb3dc51c02800 100644 --- a/lldb/docs/lldb-gdb-remote.txt +++ b/lldb/docs/lldb-gdb-remote.txt @@ -309,6 +309,38 @@ read packet: {"name":, "description":}/E;AAAAAAAA // Trace buffer size per thread in bytes. It must be a power of 2 // greater than or equal to 4096 (2^12) bytes. // +// "enableTsc": , +// Whether to enable TSC timestamps or not. This is supported on +// all devices that support intel-pt. A TSC timestamp is generated along +// with PSB (synchronization) packets, whose frequency can be configured +// with the "psbPeriod" parameter. +// +// "psbPeriod"?: , +// This value defines the period in which PSB packets will be generated. +// A PSB packet is a synchronization packet that contains a TSC +// timestamp and the current absolute instruction pointer. +// +// This parameter can only be used if +// +// /sys/bus/event_source/devices/intel_pt/caps/psb_cyc +// +// is 1. Otherwise, the PSB period will be defined by the processor. +// +// If supported, valid values for this period can be found in +/ +// /sys/bus/event_source/devices/intel_pt/caps/psb_periods +// +// which contains a hexadecimal number, whose bits represent valid +// values e.g. if bit 2 is set, then value 2 is valid. +// +// The psb_period value is 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. +// // /* process tracing only */ // "processBufferSizeLimit": , // Maximum total buffer size per process in bytes. @@ -871,7 +903,7 @@ osminor: optional, specifies the minor version number of the OS (e.g. for macOS ospatch: optional, specifies the patch level number of the OS (e.g. for macOS 10.12.2, it would be 2) vm-page-size: optional, specifies the target system VM page size, base 10. Needed for the "dirty-pages:" list in the qMemoryRegionInfo - packet, where a list of dirty pages is sent from the remote + packet, where a list of dirty pages is sent from the remote stub. This page size tells lldb how large each dirty page is. addressing_bits: optional, specifies how many bits in addresses are significant for addressing, base 10. If bits 38..0 @@ -1185,8 +1217,8 @@ tuples to return are: // // If the stub supports identifying dirty pages within a // memory region, this key should always be present for all - // qMemoryRegionInfo replies. This key with no pages - // listed ("dirty-pages:;") indicates no dirty pages in + // qMemoryRegionInfo replies. This key with no pages + // listed ("dirty-pages:;") indicates no dirty pages in // this memory region. The *absence* of this key means // that this stub cannot determine dirty pages. diff --git a/lldb/include/lldb/Target/TraceCursor.h b/lldb/include/lldb/Target/TraceCursor.h index e15ced82a470f4..14fc00d5f95b12 100644 --- a/lldb/include/lldb/Target/TraceCursor.h +++ b/lldb/include/lldb/Target/TraceCursor.h @@ -180,6 +180,15 @@ class TraceCursor { /// LLDB_INVALID_ADDRESS. virtual lldb::addr_t GetLoadAddress() = 0; + /// Get the timestamp counter associated with the current instruction. + /// Modern Intel, ARM and AMD processors support this counter. However, a + /// trace plugin might decide to use a different time unit instead of an + /// actual TSC. + /// + /// \return + /// The timestamp or \b llvm::None if not available. + virtual llvm::Optional GetTimestampCounter() = 0; + /// \return /// The \a lldb::TraceInstructionControlFlowType categories the /// instruction the cursor is pointing at falls into. If the cursor points diff --git a/lldb/include/lldb/Target/TraceInstructionDumper.h b/lldb/include/lldb/Target/TraceInstructionDumper.h index 388e5063d1725c..c4878bfd3fd59b 100644 --- a/lldb/include/lldb/Target/TraceInstructionDumper.h +++ b/lldb/include/lldb/Target/TraceInstructionDumper.h @@ -30,8 +30,12 @@ class TraceInstructionDumper { /// \param[in] raw /// Dump only instruction addresses without disassembly nor symbol /// information. + /// + /// \param[in] show_tsc + /// For each instruction, print the corresponding timestamp counter if + /// available. TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, int initial_index = 0, - bool raw = false); + bool raw = false, bool show_tsc = false); /// Dump \a count instructions of the thread trace starting at the current /// cursor position. @@ -63,6 +67,7 @@ class TraceInstructionDumper { lldb::TraceCursorUP m_cursor_up; int m_index; bool m_raw; + bool m_show_tsc; /// If \b true, all the instructions have been traversed. bool m_no_more_data = false; }; diff --git a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h index 95f4eeb27d3fa2..8f4947b1f189c4 100644 --- a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h +++ b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h @@ -19,6 +19,13 @@ namespace lldb_private { struct TraceIntelPTStartRequest : TraceStartRequest { /// Size in bytes to use for each thread's trace buffer. int64_t threadBufferSize; + + /// Whether to enable TSC + bool enableTsc; + + /// PSB packet period + llvm::Optional psbPeriod; + /// Required when doing "process tracing". /// /// Limit in bytes on all the thread traces started by this "process trace" diff --git a/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py index 2b7dfb16d34abe..a2f63bc88bd10f 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py @@ -45,22 +45,30 @@ def assertSBError(self, sberror, error=False): else: self.assertSuccess(sberror) - def createConfiguration(self, threadBufferSize=None, processBufferSizeLimit=None): + def createConfiguration(self, threadBufferSize=None, + processBufferSizeLimit=None, enableTsc=False, + psbPeriod=None): obj = {} if processBufferSizeLimit is not None: obj["processBufferSizeLimit"] = processBufferSizeLimit if threadBufferSize is not None: - obj["threadBufferSize"] = threadBufferSize + obj["threadBufferSize"] = threadBufferSize + if psbPeriod is not None: + obj["psbPeriod"] = psbPeriod + obj["enableTsc"] = enableTsc configuration = lldb.SBStructuredData() configuration.SetFromJSON(json.dumps(obj)) return configuration - def traceStartThread(self, thread=None, error=False, substrs=None, threadBufferSize=None): + def traceStartThread(self, thread=None, error=False, substrs=None, + threadBufferSize=None, enableTsc=False, psbPeriod=None): if self.USE_SB_API: trace = self.getTraceOrCreate() thread = thread if thread is not None else self.thread() - configuration = self.createConfiguration(threadBufferSize=threadBufferSize) + configuration = self.createConfiguration( + threadBufferSize=threadBufferSize, enableTsc=enableTsc, + psbPeriod=psbPeriod) self.assertSBError(trace.Start(thread, configuration), error) else: command = "thread trace start" @@ -68,17 +76,28 @@ def traceStartThread(self, thread=None, error=False, substrs=None, threadBufferS command += " " + str(thread.GetIndexID()) if threadBufferSize is not None: command += " -s " + str(threadBufferSize) + if enableTsc: + command += " --tsc" + if psbPeriod is not None: + command += " --psb-period " + str(psbPeriod) self.expect(command, error=error, substrs=substrs) - def traceStartProcess(self, processBufferSizeLimit=None, error=False, substrs=None): + def traceStartProcess(self, processBufferSizeLimit=None, error=False, + substrs=None, enableTsc=False, psbPeriod=None): if self.USE_SB_API: trace = self.getTraceOrCreate() - configuration = self.createConfiguration(processBufferSizeLimit=processBufferSizeLimit) + configuration = self.createConfiguration( + processBufferSizeLimit=processBufferSizeLimit, enableTsc=enableTsc, + psbPeriod=psbPeriod) self.assertSBError(trace.Start(configuration), error=error) else: command = "process trace start" if processBufferSizeLimit != None: command += " -l " + str(processBufferSizeLimit) + if enableTsc: + command += " --tsc" + if psbPeriod is not None: + command += " --psb-period " + str(psbPeriod) self.expect(command, error=error, substrs=substrs) def traceStopProcess(self): diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp index e4cf8a411b22b5..2f8772953af401 100644 --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -2024,6 +2024,10 @@ class CommandObjectTraceDumpInstructions m_forwards = true; break; } + case 't': { + m_show_tsc = true; + break; + } default: llvm_unreachable("Unimplemented option"); } @@ -2035,6 +2039,7 @@ class CommandObjectTraceDumpInstructions m_skip = 0; m_raw = false; m_forwards = false; + m_show_tsc = false; } llvm::ArrayRef GetDefinitions() override { @@ -2048,6 +2053,7 @@ class CommandObjectTraceDumpInstructions size_t m_skip; bool m_raw; bool m_forwards; + bool m_show_tsc; }; CommandObjectTraceDumpInstructions(CommandInterpreter &interpreter) @@ -2109,7 +2115,8 @@ class CommandObjectTraceDumpInstructions int initial_index = setUpCursor(); auto dumper = std::make_unique( - std::move(cursor_up), initial_index, m_options.m_raw); + std::move(cursor_up), initial_index, m_options.m_raw, + m_options.m_show_tsc); // This happens when the seek value was more than the number of available // instructions. diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index 36b5a82a8831b5..d0bc80c74d55d0 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1065,6 +1065,9 @@ let Command = "thread trace dump instructions" in { def thread_trace_dump_instructions_raw : Option<"raw", "r">, Group<1>, Desc<"Dump only instruction address without disassembly nor symbol information.">; + def thread_trace_dump_instructions_show_tsc : Option<"tsc", "t">, + Group<1>, + Desc<"For each instruction, print the corresponding timestamp counter if available.">; } let Command = "type summary add" in { diff --git a/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp b/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp index f3680a190c4116..0bd48933d4d3e0 100644 --- a/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" @@ -29,32 +30,151 @@ using namespace llvm; const char *kOSEventIntelPTTypeFile = "/sys/bus/event_source/devices/intel_pt/type"; -/// Return the Linux perf event type for Intel PT. -static Expected GetOSEventType() { - auto intel_pt_type_text = - llvm::MemoryBuffer::getFileAsStream(kOSEventIntelPTTypeFile); +const char *kPSBPeriodCapFile = + "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc"; + +const char *kPSBPeriodValidValuesFile = + "/sys/bus/event_source/devices/intel_pt/caps/psb_periods"; + +const char *kTSCBitOffsetFile = + "/sys/bus/event_source/devices/intel_pt/format/tsc"; + +const char *kPSBPeriodBitOffsetFile = + "/sys/bus/event_source/devices/intel_pt/format/psb_period"; - if (!intel_pt_type_text) +enum IntelPTConfigFileType { + Hex = 0, + // 0 or 1 + ZeroOne, + Decimal, + // a bit index file always starts with the prefix config: following by an int, + // which represents the offset of the perf_event_attr.config value where to + // store a given configuration. + BitOffset +}; + +static Expected ReadIntelPTConfigFile(const char *file, + IntelPTConfigFileType type) { + ErrorOr> stream = + MemoryBuffer::getFileAsStream(file); + + if (!stream) return createStringError(inconvertibleErrorCode(), - "Can't open the file '%s'", - kOSEventIntelPTTypeFile); + "Can't open the file '%s'", file); + + uint32_t value = 0; + StringRef text_buffer = stream.get()->getBuffer(); + + if (type == BitOffset) { + const char *prefix = "config:"; + if (!text_buffer.startswith(prefix)) + return createStringError(inconvertibleErrorCode(), + "The file '%s' contents doesn't start with '%s'", + file, prefix); + text_buffer = text_buffer.substr(strlen(prefix)); + } + + auto getRadix = [&]() { + switch (type) { + case Hex: + return 16; + case ZeroOne: + case Decimal: + case BitOffset: + return 10; + } + }; - uint32_t intel_pt_type = 0; - StringRef buffer = intel_pt_type_text.get()->getBuffer(); - if (buffer.trim().getAsInteger(10, intel_pt_type)) + auto createError = [&](const char *expected_value_message) { return createStringError( inconvertibleErrorCode(), - "The file '%s' has a invalid value. It should be an unsigned int.", - kOSEventIntelPTTypeFile); - return intel_pt_type; + "The file '%s' has an invalid value. It should be %s.", file, + expected_value_message); + }; + + if (text_buffer.trim().consumeInteger(getRadix(), value) || + (type == ZeroOne && value != 0 && value != 1)) { + switch (type) { + case Hex: + return createError("an unsigned hexadecimal int"); + case ZeroOne: + return createError("0 or 1"); + case Decimal: + case BitOffset: + return createError("an unsigned decimal int"); + } + } + return value; +} +/// Return the Linux perf event type for Intel PT. +static Expected GetOSEventType() { + return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, + IntelPTConfigFileType::Decimal); +} + +static Error CheckPsbPeriod(size_t psb_period) { + Expected cap = + ReadIntelPTConfigFile(kPSBPeriodCapFile, IntelPTConfigFileType::ZeroOne); + if (!cap) + return cap.takeError(); + if (*cap == 0) + return createStringError(inconvertibleErrorCode(), + "psb_period is unsupported in the system."); + + Expected valid_values = ReadIntelPTConfigFile( + kPSBPeriodValidValuesFile, IntelPTConfigFileType::Hex); + if (!valid_values) + return valid_values.takeError(); + + if (valid_values.get() & (1 << psb_period)) + return Error::success(); + + std::ostringstream error; + // 0 is always a valid value + error << "Invalid psb_period. Valid values are: 0"; + uint32_t mask = valid_values.get(); + while (mask) { + int index = __builtin_ctz(mask); + if (index > 0) + error << ", " << index; + // clear the lowest bit + mask &= mask - 1; + } + error << "."; + return createStringError(inconvertibleErrorCode(), error.str().c_str()); } size_t IntelPTThreadTrace::GetTraceBufferSize() const { return m_mmap_meta->aux_size; } +static Expected +GeneratePerfEventConfigValue(bool enable_tsc, Optional psb_period) { + uint64_t config = 0; + // tsc is always supported + if (enable_tsc) { + if (Expected offset = ReadIntelPTConfigFile( + kTSCBitOffsetFile, IntelPTConfigFileType::BitOffset)) + config |= 1 << *offset; + else + return offset.takeError(); + } + if (psb_period) { + if (Error error = CheckPsbPeriod(*psb_period)) + return std::move(error); + + if (Expected offset = ReadIntelPTConfigFile( + kPSBPeriodBitOffsetFile, IntelPTConfigFileType::BitOffset)) + config |= *psb_period << *offset; + else + return offset.takeError(); + } + return config; +} + Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid, - uint64_t buffer_size) { + uint64_t buffer_size, bool enable_tsc, + Optional psb_period) { #ifndef PERF_ATTR_SIZE_VER5 llvm_unreachable("Intel PT Linux perf event not supported"); #else @@ -85,15 +205,21 @@ Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid, attr.exclude_hv = 1; attr.exclude_idle = 1; attr.mmap = 1; - attr.config = 0; - Expected intel_pt_type = GetOSEventType(); + if (Expected config_value = + GeneratePerfEventConfigValue(enable_tsc, psb_period)) { + attr.config = *config_value; + LLDB_LOG(log, "intel pt config {0}", attr.config); + } else { + return config_value.takeError(); + } - if (!intel_pt_type) + if (Expected intel_pt_type = GetOSEventType()) { + attr.type = *intel_pt_type; + LLDB_LOG(log, "intel pt type {0}", attr.type); + } else { return intel_pt_type.takeError(); - - LLDB_LOG(log, "intel pt type {0}", *intel_pt_type); - attr.type = *intel_pt_type; + } LLDB_LOG(log, "buffer size {0} ", buffer_size); @@ -174,11 +300,12 @@ Expected> IntelPTThreadTrace::GetCPUInfo() { } llvm::Expected -IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, - size_t buffer_size) { +IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, + bool enable_tsc, Optional psb_period) { IntelPTThreadTraceUP thread_trace_up(new IntelPTThreadTrace()); - if (llvm::Error err = thread_trace_up->StartTrace(pid, tid, buffer_size)) + if (llvm::Error err = thread_trace_up->StartTrace(pid, tid, buffer_size, + enable_tsc, psb_period)) return std::move(err); return std::move(thread_trace_up); @@ -368,8 +495,9 @@ Error IntelPTThreadTraceCollection::TraceStart( return createStringError(inconvertibleErrorCode(), "Thread %" PRIu64 " already traced", tid); - Expected trace_up = - IntelPTThreadTrace::Create(m_pid, tid, request.threadBufferSize); + Expected trace_up = IntelPTThreadTrace::Create( + m_pid, tid, request.threadBufferSize, request.enableTsc, + request.psbPeriod.map([](int64_t period) { return (size_t)period; })); if (!trace_up) return trace_up.takeError(); diff --git a/lldb/source/Plugins/Process/Linux/IntelPTManager.h b/lldb/source/Plugins/Process/Linux/IntelPTManager.h index 807798a811db62..38566a221077a5 100644 --- a/lldb/source/Plugins/Process/Linux/IntelPTManager.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTManager.h @@ -70,11 +70,19 @@ class IntelPTThreadTrace { /// \param[in] buffer_size /// Size of the thread buffer in bytes. /// + /// \param[in] enable_tsc + /// Whether to use enable TSC timestamps or not. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// + /// \param[in] psb_period + /// This value defines the period in which PSB packets will be generated. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// /// \return /// \a llvm::Error::success if tracing was successful, or an /// \a llvm::Error otherwise. - llvm::Error StartTrace(lldb::pid_t pid, lldb::tid_t tid, - uint64_t buffer_size); + llvm::Error StartTrace(lldb::pid_t pid, lldb::tid_t tid, uint64_t buffer_size, + bool enable_tsc, llvm::Optional psb_period); llvm::MutableArrayRef GetAuxBuffer() const; llvm::MutableArrayRef GetDataBuffer() const; @@ -95,7 +103,8 @@ class IntelPTThreadTrace { /// A \a IntelPTThreadTrace instance if tracing was successful, or /// an \a llvm::Error otherwise. static llvm::Expected - Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size); + Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, bool enable_tsc, + llvm::Optional psb_period); /// Read the trace buffer of the currently traced thread. /// diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp index e615b2051f977f..5650af657c5e40 100644 --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp @@ -41,6 +41,20 @@ Status CommandObjectThreadTraceStartIntelPT::CommandOptions::SetOptionValue( m_thread_buffer_size = thread_buffer_size; break; } + case 't': { + m_enable_tsc = true; + break; + } + case 'p': { + int64_t psb_period; + if (option_arg.empty() || option_arg.getAsInteger(0, psb_period) || + psb_period < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_psb_period = psb_period; + break; + } default: llvm_unreachable("Unimplemented option"); } @@ -49,7 +63,9 @@ Status CommandObjectThreadTraceStartIntelPT::CommandOptions::SetOptionValue( void CommandObjectThreadTraceStartIntelPT::CommandOptions:: OptionParsingStarting(ExecutionContext *execution_context) { - m_thread_buffer_size = kThreadBufferSize; + m_thread_buffer_size = kDefaultThreadBufferSize; + m_enable_tsc = kDefaultEnableTscValue; + m_psb_period = kDefaultPsbPeriod; } llvm::ArrayRef @@ -60,7 +76,8 @@ CommandObjectThreadTraceStartIntelPT::CommandOptions::GetDefinitions() { bool CommandObjectThreadTraceStartIntelPT::DoExecuteOnThreads( Args &command, CommandReturnObject &result, llvm::ArrayRef tids) { - if (Error err = m_trace.Start(tids, m_options.m_thread_buffer_size)) + if (Error err = m_trace.Start(tids, m_options.m_thread_buffer_size, + m_options.m_enable_tsc, m_options.m_psb_period)) result.SetError(Status(std::move(err))); else result.SetStatus(eReturnStatusSuccessFinishResult); @@ -101,6 +118,20 @@ Status CommandObjectProcessTraceStartIntelPT::CommandOptions::SetOptionValue( m_process_buffer_size_limit = process_buffer_size_limit; break; } + case 't': { + m_enable_tsc = true; + break; + } + case 'p': { + int64_t psb_period; + if (option_arg.empty() || option_arg.getAsInteger(0, psb_period) || + psb_period < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_psb_period = psb_period; + break; + } default: llvm_unreachable("Unimplemented option"); } @@ -109,8 +140,10 @@ Status CommandObjectProcessTraceStartIntelPT::CommandOptions::SetOptionValue( void CommandObjectProcessTraceStartIntelPT::CommandOptions:: OptionParsingStarting(ExecutionContext *execution_context) { - m_thread_buffer_size = kThreadBufferSize; - m_process_buffer_size_limit = kProcessBufferSizeLimit; + m_thread_buffer_size = kDefaultThreadBufferSize; + m_process_buffer_size_limit = kDefaultProcessBufferSizeLimit; + m_enable_tsc = kDefaultEnableTscValue; + m_psb_period = kDefaultPsbPeriod; } llvm::ArrayRef @@ -121,7 +154,8 @@ CommandObjectProcessTraceStartIntelPT::CommandOptions::GetDefinitions() { bool CommandObjectProcessTraceStartIntelPT::DoExecute( Args &command, CommandReturnObject &result) { if (Error err = m_trace.Start(m_options.m_thread_buffer_size, - m_options.m_process_buffer_size_limit)) + m_options.m_process_buffer_size_limit, + m_options.m_enable_tsc, m_options.m_psb_period)) result.SetError(Status(std::move(err))); else result.SetStatus(eReturnStatusSuccessFinishResult); diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h index 34edbd2995f298..2f3d53a8640671 100644 --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h @@ -32,6 +32,8 @@ class CommandObjectThreadTraceStartIntelPT llvm::ArrayRef GetDefinitions() override; size_t m_thread_buffer_size; + bool m_enable_tsc; + llvm::Optional m_psb_period; }; CommandObjectThreadTraceStartIntelPT(TraceIntelPT &trace, @@ -74,6 +76,8 @@ class CommandObjectProcessTraceStartIntelPT : public CommandObjectParsed { size_t m_thread_buffer_size; size_t m_process_buffer_size_limit; + bool m_enable_tsc; + llvm::Optional m_psb_period; }; CommandObjectProcessTraceStartIntelPT(TraceIntelPT &trace, diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp index 5c15e663fd6381..970a7970c0fb34 100644 --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -48,6 +48,10 @@ bool IntelPTInstruction::IsError() const { return (bool)m_error; } lldb::addr_t IntelPTInstruction::GetLoadAddress() const { return m_pt_insn.ip; } +Optional IntelPTInstruction::GetTimestampCounter() const { + return m_timestamp; +} + Error IntelPTInstruction::ToError() const { if (!IsError()) return Error::success(); diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h index d5983c363367b7..135d938dfc57b6 100644 --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -61,6 +61,9 @@ class IntelPTError : public llvm::ErrorInfo { /// As mentioned, any gap is represented as an error in this class. class IntelPTInstruction { public: + IntelPTInstruction(const pt_insn &pt_insn, uint64_t timestamp) + : m_pt_insn(pt_insn), m_timestamp(timestamp) {} + IntelPTInstruction(const pt_insn &pt_insn) : m_pt_insn(pt_insn) {} /// Error constructor @@ -84,6 +87,13 @@ class IntelPTInstruction { /// \a llvm::Error::success otherwise. llvm::Error ToError() const; + /// Get the timestamp associated with the current instruction. The timestamp + /// is similar to what a rdtsc instruction would return. + /// + /// \return + /// The timestamp or \b llvm::None if not available. + llvm::Optional GetTimestampCounter() const; + /// Get the \a lldb::TraceInstructionControlFlowType categories of the /// instruction. /// @@ -103,6 +113,7 @@ class IntelPTInstruction { const IntelPTInstruction &operator=(const IntelPTInstruction &other) = delete; pt_insn m_pt_insn; + llvm::Optional m_timestamp; std::unique_ptr m_error; }; diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp index fa9e42131f0a01..8c331841b54e69 100644 --- a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -9,6 +9,7 @@ #include "llvm/Support/MemoryBuffer.h" +#include "DecodedThread.h" #include "TraceIntelPT.h" #include "lldb/Core/Module.h" #include "lldb/Core/Section.h" @@ -136,7 +137,23 @@ DecodeInstructions(pt_insn_decoder &decoder) { break; } - instructions.emplace_back(insn); + uint64_t time; + int time_error = pt_insn_time(&decoder, &time, nullptr, nullptr); + if (time_error == -pte_invalid) { + // This happens if we invoke the pt_insn_time method incorrectly, + // but the instruction is good though. + instructions.emplace_back( + make_error(time_error, insn.ip)); + instructions.emplace_back(insn); + break; + } + if (time_error == -pte_no_time) { + // We simply don't have time information, i.e. None of TSC, MTC or CYC + // was enabled. + instructions.emplace_back(insn); + } else { + instructions.emplace_back(insn, time); + } } } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp index 25c2446162994e..edefdd0d3486e6 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp @@ -85,6 +85,10 @@ lldb::addr_t TraceCursorIntelPT::GetLoadAddress() { return m_decoded_thread_sp->GetInstructions()[m_pos].GetLoadAddress(); } +Optional TraceCursorIntelPT::GetTimestampCounter() { + return m_decoded_thread_sp->GetInstructions()[m_pos].GetTimestampCounter(); +} + TraceInstructionControlFlowType TraceCursorIntelPT::GetInstructionControlFlowType() { lldb::addr_t next_load_address = diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h index 4eb4e638905fa6..29d3792bb489ee 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h @@ -28,6 +28,8 @@ class TraceCursorIntelPT : public TraceCursor { lldb::addr_t GetLoadAddress() override; + llvm::Optional GetTimestampCounter() override; + lldb::TraceInstructionControlFlowType GetInstructionControlFlowType() override; diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp index 527e0d5e66287a..efbc0e2ed7f470 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -192,6 +192,10 @@ bool TraceIntelPT::IsTraced(const Thread &thread) { return m_thread_decoders.count(&thread); } +// The information here should match the description of the intel-pt section +// of the jLLDBTraceStart packet in the lldb/docs/lldb-gdb-remote.txt +// documentation file. Similarly, it should match the CLI help messages of the +// TraceIntelPTOptions.td file. const char *TraceIntelPT::GetStartConfigurationHelp() { return R"(Parameters: @@ -203,6 +207,38 @@ const char *TraceIntelPT::GetStartConfigurationHelp() { than or equal to 4096 (2^12). The trace is circular keeping the the most recent data. + - boolean enableTsc (default to false): + [process and thread tracing] + Whether to use enable TSC timestamps or not. This is supported on + all devices that support intel-pt. + + - psbPeriod (defaults to null): + [process and thread tracing] + This value defines the period in which PSB packets will be generated. + A PSB packet is a synchronization packet that contains a TSC + timestamp and the current absolute instruction pointer. + + This parameter can only be used if + + /sys/bus/event_source/devices/intel_pt/caps/psb_cyc + + is 1. Otherwise, the PSB period will be defined by the processor. + + If supported, valid values for this period can be found in + + /sys/bus/event_source/devices/intel_pt/caps/psb_periods + + which contains a hexadecimal number, whose bits represent + valid values e.g. if bit 2 is set, then value 2 is valid. + + The psb_period value is 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. + - int processBufferSizeLimit (defaults to 500 MB): [process tracing only] Maximum total trace size per process in bytes. This limit applies @@ -215,36 +251,47 @@ const char *TraceIntelPT::GetStartConfigurationHelp() { } Error TraceIntelPT::Start(size_t thread_buffer_size, - size_t total_buffer_size_limit) { + size_t total_buffer_size_limit, bool enable_tsc, + Optional psb_period) { TraceIntelPTStartRequest request; request.threadBufferSize = thread_buffer_size; request.processBufferSizeLimit = total_buffer_size_limit; + request.enableTsc = enable_tsc; + request.psbPeriod = psb_period.map([](size_t val) { return (int64_t)val; }); request.type = GetPluginName().AsCString(); return Trace::Start(toJSON(request)); } Error TraceIntelPT::Start(StructuredData::ObjectSP configuration) { - size_t thread_buffer_size = kThreadBufferSize; - size_t process_buffer_size_limit = kProcessBufferSizeLimit; + size_t thread_buffer_size = kDefaultThreadBufferSize; + size_t process_buffer_size_limit = kDefaultProcessBufferSizeLimit; + bool enable_tsc = kDefaultEnableTscValue; + Optional psb_period = kDefaultPsbPeriod; if (configuration) { if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { dict->GetValueForKeyAsInteger("threadBufferSize", thread_buffer_size); dict->GetValueForKeyAsInteger("processBufferSizeLimit", process_buffer_size_limit); + dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc); + dict->GetValueForKeyAsInteger("psbPeriod", psb_period); } else { return createStringError(inconvertibleErrorCode(), "configuration object is not a dictionary"); } } - return Start(thread_buffer_size, process_buffer_size_limit); + return Start(thread_buffer_size, process_buffer_size_limit, enable_tsc, + psb_period); } llvm::Error TraceIntelPT::Start(llvm::ArrayRef tids, - size_t thread_buffer_size) { + size_t thread_buffer_size, bool enable_tsc, + Optional psb_period) { TraceIntelPTStartRequest request; request.threadBufferSize = thread_buffer_size; + request.enableTsc = enable_tsc; + request.psbPeriod = psb_period.map([](size_t val) { return (int64_t)val; }); request.type = GetPluginName().AsCString(); request.tids.emplace(); for (lldb::tid_t tid : tids) @@ -254,18 +301,22 @@ llvm::Error TraceIntelPT::Start(llvm::ArrayRef tids, Error TraceIntelPT::Start(llvm::ArrayRef tids, StructuredData::ObjectSP configuration) { - size_t thread_buffer_size = kThreadBufferSize; + size_t thread_buffer_size = kDefaultThreadBufferSize; + bool enable_tsc = kDefaultEnableTscValue; + Optional psb_period = kDefaultPsbPeriod; if (configuration) { if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { dict->GetValueForKeyAsInteger("threadBufferSize", thread_buffer_size); + dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc); + dict->GetValueForKeyAsInteger("psbPeriod", psb_period); } else { return createStringError(inconvertibleErrorCode(), "configuration object is not a dictionary"); } } - return Start(tids, thread_buffer_size); + return Start(tids, thread_buffer_size, enable_tsc, psb_period); } Expected> diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h index 9433ab8601f13a..cd6fedb2e16163 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -80,18 +80,23 @@ class TraceIntelPT : public Trace { /// Trace size per thread in bytes. /// /// \param[in] total_buffer_size_limit - /// Maximum total trace size per process in bytes. This limit applies to - /// the sum of the sizes of all thread traces of this process, excluding - /// the threads traced explicitly. + /// Maximum total trace size per process in bytes. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). /// - /// Whenever a thread is attempted to be traced due to this operation and - /// the limit would be reached, the process is stopped with a "tracing" - /// reason, so that the user can retrace the process if needed. + /// \param[in] enable_tsc + /// Whether to use enable TSC timestamps or not. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// + /// \param[in] psb_period + /// + /// This value defines the period in which PSB packets will be generated. + /// More information in TraceIntelPT::GetStartConfigurationHelp(); /// /// \return /// \a llvm::Error::success if the operation was successful, or /// \a llvm::Error otherwise. - llvm::Error Start(size_t thread_buffer_size, size_t total_buffer_size_limit); + llvm::Error Start(size_t thread_buffer_size, size_t total_buffer_size_limit, + bool enable_tsc, llvm::Optional psb_period); /// \copydoc Trace::Start llvm::Error Start(StructuredData::ObjectSP configuration = @@ -105,11 +110,20 @@ class TraceIntelPT : public Trace { /// \param[in] thread_buffer_size /// Trace size per thread in bytes. /// + /// \param[in] enable_tsc + /// Whether to use enable TSC timestamps or not. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// + /// \param[in] psb_period + /// + /// This value defines the period in which PSB packets will be generated. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// /// \return /// \a llvm::Error::success if the operation was successful, or /// \a llvm::Error otherwise. - llvm::Error Start(llvm::ArrayRef tids, - size_t thread_buffer_size); + llvm::Error Start(llvm::ArrayRef tids, size_t thread_buffer_size, + bool enable_tsc, llvm::Optional psb_period); /// \copydoc Trace::Start llvm::Error Start(llvm::ArrayRef tids, diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h index a9d0d0e30b054a..c2bc1b57b2bd21 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h @@ -11,11 +11,15 @@ #include +#include + namespace lldb_private { namespace trace_intel_pt { -const size_t kThreadBufferSize = 4 * 1024; // 4KB -const size_t kProcessBufferSizeLimit = 5 * 1024 * 1024; // 500MB +const size_t kDefaultThreadBufferSize = 4 * 1024; // 4KB +const size_t kDefaultProcessBufferSizeLimit = 5 * 1024 * 1024; // 500MB +const bool kDefaultEnableTscValue = false; +const llvm::Optional kDefaultPsbPeriod = llvm::None; } // namespace trace_intel_pt } // namespace lldb_private diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td index 85350e4c585c33..9e8cab1ee5c4ce 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td @@ -1,5 +1,10 @@ include "../../../../source/Commands/OptionsBase.td" +// The information of the start commands here should match the description of +// the intel-pt section of the jLLDBTraceStart packet in the +// lldb/docs/lldb-gdb-remote.txt documentation file. Similarly, it should match +// the API help message of TraceIntelPT::GetStartConfigurationHelp(). + let Command = "thread trace start intel pt" in { def thread_trace_start_intel_pt_size: Option<"size", "s">, Group<1>, @@ -7,6 +12,26 @@ let Command = "thread trace start intel pt" in { Desc<"Trace size in bytes per thread. It must be a power of 2 greater " "than or equal to 4096 (2^12). The trace is circular keeping " "the most recent data. Defaults to 4096 bytes.">; + def thread_trace_start_intel_pt_tsc: Option<"tsc", "t">, + Group<1>, + Desc<"Enable the use of TSC timestamps. This is supported on all devices " + "that support intel-pt.">; + def thread_trace_start_intel_pt_psb_period: Option<"psb-period", "p">, + Group<1>, + Arg<"Value">, + Desc<"This value defines the period in which PSB packets will be " + "generated. A PSB packet is a synchronization packet that contains a " + "TSC timestamp and the current absolute instruction pointer. " + "This parameter can only be used if " + "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc is 1. Otherwise, " + "the PSB period will be defined by the processor. If supported, valid " + "values for this period can be found in " + "/sys/bus/event_source/devices/intel_pt/caps/psb_periods which " + "contains a hexadecimal number, whose bits represent valid values " + "e.g. if bit 2 is set, then value 2 is valid. The psb_period value is " + "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.">; } let Command = "process trace start intel pt" in { @@ -26,4 +51,24 @@ let Command = "process trace start intel pt" in { "the limit would be reached, the process is stopped with a " "\"processor trace\" reason, so that the user can retrace the process " "if needed. Defaults to 500MB.">; + def process_trace_start_intel_pt_tsc: Option<"tsc", "t">, + Group<1>, + Desc<"Enable the use of TSC timestamps. This is supported on all devices " + "that support intel-pt.">; + def process_trace_start_intel_pt_psb_period: Option<"psb-period", "p">, + Group<1>, + Arg<"Value">, + Desc<"This value defines the period in which PSB packets will be " + "generated. A PSB packet is a synchronization packet that contains a " + "TSC timestamp and the current absolute instruction pointer. " + "This parameter can only be used if " + "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc is 1. Otherwise, " + "the PSB period will be defined by the processor. If supported, valid " + "values for this period can be found in " + "/sys/bus/event_source/devices/intel_pt/caps/psb_periods which " + "contains a hexadecimal number, whose bits represent valid values " + "e.g. if bit 2 is set, then value 2 is valid. The psb_period value is " + "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.">; } diff --git a/lldb/source/Target/TraceInstructionDumper.cpp b/lldb/source/Target/TraceInstructionDumper.cpp index a8525d6a65c456..dc1e86481c3639 100644 --- a/lldb/source/Target/TraceInstructionDumper.cpp +++ b/lldb/source/Target/TraceInstructionDumper.cpp @@ -19,8 +19,10 @@ using namespace lldb_private; using namespace llvm; TraceInstructionDumper::TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, - int initial_index, bool raw) - : m_cursor_up(std::move(cursor_up)), m_index(initial_index), m_raw(raw) {} + int initial_index, bool raw, + bool show_tsc) + : m_cursor_up(std::move(cursor_up)), m_index(initial_index), m_raw(raw), + m_show_tsc(show_tsc) {} /// \return /// Return \b true if the cursor could move one step. @@ -177,6 +179,17 @@ void TraceInstructionDumper::DumpInstructions(Stream &s, size_t count) { auto printInstructionIndex = [&]() { s.Printf(" [%*d] ", digits_count, m_index); + + if (m_show_tsc) { + s.Printf("[tsc="); + + if (Optional timestamp = m_cursor_up->GetTimestampCounter()) + s.Printf("0x%016" PRIx64, *timestamp); + else + s.Printf("unavailable"); + + s.Printf("] "); + } }; InstructionSymbolInfo prev_insn_info; diff --git a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp index 69f773f673bb24..dbb93d8d1c5c6d 100644 --- a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +++ b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp @@ -17,6 +17,8 @@ bool fromJSON(const json::Value &value, TraceIntelPTStartRequest &packet, Path path) { ObjectMapper o(value, path); if (!o || !fromJSON(value, (TraceStartRequest &)packet, path) || + !o.map("enableTsc", packet.enableTsc) || + !o.map("psbPeriod", packet.psbPeriod) || !o.map("threadBufferSize", packet.threadBufferSize) || !o.map("processBufferSizeLimit", packet.processBufferSizeLimit)) return false; @@ -36,6 +38,8 @@ json::Value toJSON(const TraceIntelPTStartRequest &packet) { base.getAsObject()->try_emplace("threadBufferSize", packet.threadBufferSize); base.getAsObject()->try_emplace("processBufferSizeLimit", packet.processBufferSizeLimit); + base.getAsObject()->try_emplace("psbPeriod", packet.psbPeriod); + base.getAsObject()->try_emplace("enableTsc", packet.enableTsc); return base; } diff --git a/lldb/test/API/commands/trace/TestTraceTimestampCounters.py b/lldb/test/API/commands/trace/TestTraceTimestampCounters.py new file mode 100644 index 00000000000000..8126e36990133c --- /dev/null +++ b/lldb/test/API/commands/trace/TestTraceTimestampCounters.py @@ -0,0 +1,100 @@ +import lldb +from intelpt_testcase import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * + +class TestTraceTimestampCounters(TraceIntelPTTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + @testSBAPIAndCommands + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testTscPerThread(self): + self.expect("file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + self.expect("b main") + self.expect("r") + + self.traceStartThread(enableTsc=True) + + self.expect("n") + self.expect("thread trace dump instructions --tsc -c 1", + patterns=["\[0\] \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"]) + + @testSBAPIAndCommands + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testTscPerProcess(self): + self.expect("file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + self.expect("b main") + self.expect("r") + + self.traceStartProcess(enableTsc=True) + + self.expect("n") + self.expect("thread trace dump instructions --tsc -c 1", + patterns=["\[0\] \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"]) + + @testSBAPIAndCommands + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testDumpingAfterTracingWithoutTsc(self): + self.expect("file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + self.expect("b main") + self.expect("r") + + self.traceStartThread(enableTsc=False) + + self.expect("n") + self.expect("thread trace dump instructions --tsc -c 1", + patterns=["\[0\] \[tsc=unavailable\] 0x0000000000400511 movl"]) + + @testSBAPIAndCommands + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testPSBPeriod(self): + def isPSBSupported(): + caps_file = "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc" + if not os.path.exists(caps_file): + return False + with open(caps_file, "r") as f: + val = int(f.readline()) + if val != 1: + return False + return True + + def getValidPSBValues(): + values_file = "/sys/bus/event_source/devices/intel_pt/caps/psb_periods" + values = [] + with open(values_file, "r") as f: + mask = int(f.readline(), 16) + for i in range(0, 32): + if (1 << i) & mask: + values.append(i) + return values + + + if not isPSBSupported(): + self.skipTest("PSB period unsupported") + + valid_psb_values = getValidPSBValues() + # 0 should always be valid, and it's assumed by lldb-server + self.assertEqual(valid_psb_values[0], 0) + + self.expect("file " + (os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))) + self.expect("b main") + self.expect("r") + + # it's enough to test with two valid values + for psb_period in (valid_psb_values[0], valid_psb_values[-1]): + # we first test at thread level + self.traceStartThread(psbPeriod=psb_period) + self.traceStopThread() + + # we now test at process level + self.traceStartProcess(psbPeriod=psb_period) + self.traceStopProcess() + + # we now test invalid values + self.traceStartThread(psbPeriod=valid_psb_values[-1] + 1, error=True, + substrs=["Invalid psb_period. Valid values are: 0"]) + + # TODO: dump the perf_event_attr.config as part of the upcoming "trace dump info" + # command and check that the psb period is included there.