jj10306 created this revision.
jj10306 added reviewers: wallace, persona0220.
Herald added a subscriber: mgorny.
Herald added a project: All.
jj10306 requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

Add bindings for the `TraceCursor` to allow for programatic traversal of
traces.
This diff adds bindings for all public `TraceCursor` methods except
`GetHwClock` and also adds `SBTrace::CreateNewCursor`. A new unittest
has been added to TestTraceLoad.py that uses the new `SBTraceCursor` API
to test that the sequential and random access APIs of the `TraceCursor`
are equivalent.

This diff depends on D130925 <https://reviews.llvm.org/D130925>.

Test Plan:
`ninja lldb-dotest && ./bin/lldb-dotest -p TestTraceLoad`


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D130930

Files:
  lldb/bindings/interface/SBTrace.i
  lldb/bindings/interface/SBTraceCursor.i
  lldb/bindings/interfaces.swig
  lldb/include/lldb/API/SBDefines.h
  lldb/include/lldb/API/SBTrace.h
  lldb/include/lldb/API/SBTraceCursor.h
  lldb/source/API/CMakeLists.txt
  lldb/source/API/SBTrace.cpp
  lldb/source/API/SBTraceCursor.cpp
  lldb/test/API/commands/trace/TestTraceLoad.py

Index: lldb/test/API/commands/trace/TestTraceLoad.py
===================================================================
--- lldb/test/API/commands/trace/TestTraceLoad.py
+++ lldb/test/API/commands/trace/TestTraceLoad.py
@@ -264,3 +264,84 @@
         expected_substrs = ['error: missing value at traceBundle.processes[1].pid']
         self.traceLoad(traceDescriptionFilePath=trace_description_file_path, error=True, substrs=expected_substrs)
         self.assertEqual(self.dbg.GetNumTargets(), 0)
+
+    def testLoadTraceCursor(self):
+        src_dir = self.getSourceDir()
+        trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace.json")
+        traceDescriptionFile = lldb.SBFileSpec(trace_description_file_path, True)
+
+        error = lldb.SBError()
+        trace = self.dbg.LoadTraceFromFile(error, traceDescriptionFile)
+        self.assertSBError(error)
+
+        target = self.dbg.GetSelectedTarget()
+        process = target.process
+
+        # Helper function to check equality of the current item of two trace cursors.
+        def assertCurrentTraceCursorItemEqual(lhs, rhs):
+            self.assertTrue(lhs.HasValue() and rhs.HasValue())
+
+            self.assertEqual(lhs.GetId(), rhs.GetId())
+            self.assertEqual(lhs.GetItemKind(), rhs.GetItemKind())
+            if lhs.IsError():
+                self.assertEqual(lhs.GetError(), rhs.GetError())
+            elif lhs.IsEvent():
+                self.assertEqual(lhs.GetEventType(), rhs.GetEventType())
+                self.assertEqual(lhs.GetEventTypeAsString(), rhs.GetEventTypeAsString())
+            elif lhs.IsInstruction():
+                self.assertEqual(lhs.GetLoadAddress(), rhs.GetLoadAddress())
+            else:
+                self.fail("Unknown trace item kind")
+
+        for thread in process.threads:
+            sequentialTraversalCursor = trace.CreateNewCursor(error, thread) 
+            self.assertSBError(error)
+            # Skip threads with no trace items
+            if not sequentialTraversalCursor.HasValue():
+                continue
+                
+
+            # Test "End" boundary of the trace by advancing past the trace's last item. 
+            sequentialTraversalCursor.Seek(0, lldb.SBTraceCursor.End)
+            self.assertTrue(sequentialTraversalCursor.HasValue())
+            sequentialTraversalCursor.SetForwards(True)
+            sequentialTraversalCursor.Next()
+            self.assertFalse(sequentialTraversalCursor.HasValue())
+
+
+
+            # Test sequential traversal using sequential access API (ie Next())
+            # and random access API (ie GoToId()) simultaneously.
+            randomAccessCursor = trace.CreateNewCursor(error, thread) 
+            self.assertSBError(error)
+            # Reset the sequential cursor 
+            sequentialTraversalCursor.Seek(0, lldb.SBTraceCursor.Beginning)
+            sequentialTraversalCursor.SetForwards(True)
+            self.assertTrue(sequentialTraversalCursor.IsForwards())
+
+            while sequentialTraversalCursor.HasValue():
+                itemId = sequentialTraversalCursor.GetId()
+                randomAccessCursor.GoToId(itemId)
+                assertCurrentTraceCursorItemEqual(sequentialTraversalCursor, randomAccessCursor)
+                sequentialTraversalCursor.Next()
+
+
+
+            # Test a random access with random access API (ie Seek()) and
+            # sequential access API (ie consecutive calls to Next()).
+            TEST_SEEK_ID = 3
+            randomAccessCursor.GoToId(TEST_SEEK_ID )
+            # Reset the sequential cursor 
+            sequentialTraversalCursor.Seek(0, lldb.SBTraceCursor.Beginning)
+            sequentialTraversalCursor.SetForwards(True)
+            for _ in range(TEST_SEEK_ID): sequentialTraversalCursor.Next()
+            assertCurrentTraceCursorItemEqual(sequentialTraversalCursor, randomAccessCursor)
+
+
+            
+                
+
+
+
+
+        
Index: lldb/source/API/SBTraceCursor.cpp
===================================================================
--- /dev/null
+++ lldb/source/API/SBTraceCursor.cpp
@@ -0,0 +1,129 @@
+//===-- SBTraceCursor.cpp
+//-------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/API/SBTraceCursor.h"
+#include "Utils.h"
+#include "lldb/Utility/Instrumentation.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+// relevant to the clone issue:
+// https://stackoverflow.com/questions/16030081/copy-constructor-for-a-class-with-unique-ptr
+SBTraceCursor::SBTraceCursor() { LLDB_INSTRUMENT_VA(this); }
+
+SBTraceCursor::SBTraceCursor(TraceCursorSP trace_cursor_sp)
+    : m_opaque_sp{std::move(trace_cursor_sp)} {
+  LLDB_INSTRUMENT_VA(this, trace_cursor_sp);
+}
+
+void SBTraceCursor::SetForwards(bool forwards) {
+  LLDB_INSTRUMENT_VA(this, forwards);
+  m_opaque_sp->SetForwards(forwards);
+}
+
+bool SBTraceCursor::IsForwards() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->IsForwards();
+}
+
+void SBTraceCursor::Next() {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->Next();
+}
+
+bool SBTraceCursor::HasValue() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->HasValue();
+}
+
+bool SBTraceCursor::GoToId(lldb::user_id_t id) {
+  LLDB_INSTRUMENT_VA(this, id);
+  return m_opaque_sp->GoToId(id);
+}
+
+bool SBTraceCursor::HasId(lldb::user_id_t id) const {
+  LLDB_INSTRUMENT_VA(this, id);
+  return m_opaque_sp->HasId(id);
+}
+
+lldb::user_id_t SBTraceCursor::GetId() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->GetId();
+}
+
+bool SBTraceCursor::Seek(int64_t offset, SeekType origin) {
+  LLDB_INSTRUMENT_VA(this, offset, origin);
+  // Convert from public `SBTraceCursor::SeekType` enum to private
+  // `TraceCursor::SeekType` enum.
+  auto convert_seek_type_enum = [](SeekType seek_type) {
+    switch (seek_type) {
+    case SeekType::Beginning:
+      return TraceCursor::SeekType::Beginning;
+    case SeekType::Current:
+      return TraceCursor::SeekType::Current;
+    case SeekType::End:
+      return TraceCursor::SeekType::End;
+    };
+  };
+
+  return m_opaque_sp->Seek(offset, convert_seek_type_enum(origin));
+}
+
+lldb::TraceItemKind SBTraceCursor::GetItemKind() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->GetItemKind();
+}
+
+bool SBTraceCursor::IsError() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->IsError();
+}
+
+const char *SBTraceCursor::GetError() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->GetError();
+}
+
+bool SBTraceCursor::IsEvent() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->IsEvent();
+}
+
+lldb::TraceEvent SBTraceCursor::GetEventType() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->GetEventType();
+}
+
+const char *SBTraceCursor::GetEventTypeAsString() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->GetEventTypeAsString();
+}
+
+bool SBTraceCursor::IsInstruction() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->IsInstruction();
+}
+
+lldb::addr_t SBTraceCursor::GetLoadAddress() const {
+  LLDB_INSTRUMENT_VA(this);
+  return m_opaque_sp->GetLoadAddress();
+}
+
+// TODO: should we define LLDB_INVALID_CPU_ID so this matches the behavior of
+// `GetLoadAddress()`?
+lldb::cpu_id_t SBTraceCursor::GetCPU(SBError &error) const {
+  LLDB_INSTRUMENT_VA(this, error);
+  if (auto cpu_opt = m_opaque_sp->GetCPU()) {
+    return *cpu_opt;
+  } else {
+    error.SetErrorString("CPU id unknown for current trace item");
+    return 0;
+  }
+}
Index: lldb/source/API/SBTrace.cpp
===================================================================
--- lldb/source/API/SBTrace.cpp
+++ lldb/source/API/SBTrace.cpp
@@ -43,6 +43,22 @@
   return SBTrace(trace_or_err.get());
 }
 
+SBTraceCursor SBTrace::CreateNewCursor(SBError &error, SBThread &thread) {
+  LLDB_INSTRUMENT_VA(this);
+  if (!m_opaque_sp || !thread.get()) {
+    error.SetErrorString("error: invalid trace");
+    return SBTraceCursor();
+  }
+
+  if (llvm::Expected<lldb::TraceCursorSP> trace_cursor_sp =
+          m_opaque_sp->CreateNewCursor(*thread.get())) {
+    return SBTraceCursor(std::move(*trace_cursor_sp));
+  } else {
+    error.SetErrorString(llvm::toString(trace_cursor_sp.takeError()).c_str());
+    return SBTraceCursor();
+  }
+}
+
 SBFileSpec SBTrace::SaveToDisk(SBError &error, const SBFileSpec &bundle_dir,
                                bool compact) {
   LLDB_INSTRUMENT_VA(this, error, bundle_dir, compact);
Index: lldb/source/API/CMakeLists.txt
===================================================================
--- lldb/source/API/CMakeLists.txt
+++ lldb/source/API/CMakeLists.txt
@@ -72,6 +72,7 @@
   SBThreadCollection.cpp
   SBThreadPlan.cpp
   SBTrace.cpp
+  SBTraceCursor.cpp
   SBType.cpp
   SBTypeCategory.cpp
   SBTypeEnumMember.cpp
Index: lldb/include/lldb/API/SBTraceCursor.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/API/SBTraceCursor.h
@@ -0,0 +1,190 @@
+//===-- SBTraceCursor.h -----------------------------------------------*- C++
+//-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_API_SBTRACECURSOR_H
+#define LLDB_API_SBTRACECURSOR_H
+
+#include "lldb/API/SBDefines.h"
+#include "lldb/API/SBError.h"
+#include "lldb/API/SBExecutionContext.h"
+#include "lldb/Target/TraceCursor.h"
+
+namespace lldb {
+
+class LLDB_API SBTraceCursor {
+public:
+  /// Helper enum to indicate the reference point when invoking / \a
+  /// SBTraceCursor::Seek(). The following values are inspired by \a
+  /// std::istream::seekg.
+  enum class SeekType {
+    /// The beginning of the trace, i.e the oldest item.
+    Beginning = 0,
+    /// The current position in the trace.
+    Current,
+    /// The end of the trace, i.e the most recent item.
+    End
+  };
+
+  SBTraceCursor();
+
+  /// Create a cursor that initially points to the end of the trace, i.e. the
+  /// most recent item.
+  SBTraceCursor(lldb::TraceCursorSP trace_cursor_sp);
+
+  /// Set the direction to use in the \a SBTraceCursor::Next() method.
+  ///
+  /// \param[in] forwards
+  ///     If \b true, then the traversal will be forwards, otherwise backwards.
+  void SetForwards(bool forwards);
+
+  /// Check if the direction to use in the \a SBTraceCursor::Next() method is
+  /// forwards.
+  ///
+  /// \return
+  ///     \b true if the current direction is forwards, \b false if backwards.
+  bool IsForwards() const;
+
+  /// Move the cursor to the next item (instruction or error).
+  ///
+  /// Direction:
+  ///     The traversal is done following the current direction of the trace. If
+  ///     it is forwards, the instructions are visited forwards
+  ///     chronologically. Otherwise, the traversal is done in
+  ///     the opposite direction. By default, a cursor moves backwards unless
+  ///     changed with \a SBTraceCursor::SetForwards().
+  void Next();
+
+  /// \return
+  ///     \b true if the cursor is pointing to a valid item. \b false if the
+  ///     cursor has reached the end of the trace.
+  bool HasValue() const;
+
+  /// Instruction identifiers:
+  ///
+  /// When building complex higher level tools, fast random accesses in the
+  /// trace might be needed, for which each instruction requires a unique
+  /// identifier within its thread trace. For example, a tool might want to
+  /// repeatedly inspect random consecutive portions of a trace. This means that
+  /// it will need to first move quickly to the beginning of each section and
+  /// then start its iteration. Given that the number of instructions can be in
+  /// the order of hundreds of millions, fast random access is necessary.
+  ///
+  /// An example of such a tool could be an inspector of the call graph of a
+  /// trace, where each call is represented with its start and end instructions.
+  /// Inspecting all the instructions of a call requires moving to its first
+  /// instruction and then iterating until the last instruction, which following
+  /// the pattern explained above.
+  ///
+  /// Instead of using 0-based indices as identifiers, each Trace plug-in can
+  /// decide the nature of these identifiers and thus no assumptions can be made
+  /// regarding their ordering and sequentiality. The reason is that an
+  /// instruction might be encoded by the plug-in in a way that hides its actual
+  /// 0-based index in the trace, but it's still possible to efficiently find
+  /// it.
+  ///
+  /// Requirements:
+  /// - For a given thread, no two instructions have the same id.
+  /// - In terms of efficiency, moving the cursor to a given id should be as
+  ///   fast as possible, but not necessarily O(1). That's why the recommended
+  ///   way to traverse sequential instructions is to use the \a
+  ///   SBTraceCursor::Next() method and only use \a SBTraceCursor::GoToId(id)
+  ///   sparingly.
+
+  /// Make the cursor point to the item whose identifier is \p id.
+  ///
+  /// \return
+  ///     \b true if the given identifier exists and the cursor effectively
+  ///     moved to it. Otherwise, \b false is returned and the cursor now points
+  ///     to an invalid item, i.e. calling \a HasValue() will return \b false.
+  bool GoToId(lldb::user_id_t id);
+
+  /// \return
+  ///     \b true if and only if there's an instruction item with the given \p
+  ///     id.
+  bool HasId(lldb::user_id_t id) const;
+
+  /// \return
+  ///     A unique identifier for the instruction or error this cursor is
+  ///     pointing to.
+  lldb::user_id_t GetId() const;
+  /// \}
+
+  /// Make the cursor point to an item in the trace based on an origin point and
+  /// an offset.
+  ///
+  /// The resulting position of the trace is
+  ///     origin + offset
+  ///
+  /// If this resulting position would be out of bounds, the trace then points
+  /// to an invalid item, i.e. calling \a HasValue() returns \b false.
+  ///
+  /// \param[in] offset
+  ///     How many items to move forwards (if positive) or backwards (if
+  ///     negative) from the given origin point. For example, if origin is \b
+  ///     End, then a negative offset would move backward in the trace, but a
+  ///     positive offset would move past the trace to an invalid item.
+  ///
+  /// \param[in] origin
+  ///     The reference point to use when moving the cursor.
+  ///
+  /// \return
+  ///     \b true if and only if the cursor ends up pointing to a valid item.
+  bool Seek(int64_t offset, SeekType origin);
+
+  /// \return
+  ///   The \a ExecutionContextRef of the backing thread from the creation time
+  ///   of this cursor.
+  SBExecutionContext &GetExecutionContextRef();
+
+  /// Trace item information (instructions, errors and events)
+  /// \{
+
+  /// \return
+  ///     The kind of item the cursor is pointing at.
+  lldb::TraceItemKind GetItemKind() const;
+
+  /// \return
+  ///     Whether the cursor points to an error or not.
+  bool IsError() const;
+
+  /// \return
+  ///     The error message the cursor is pointing at.
+  const char *GetError() const;
+
+  /// \return
+  ///     Whether the cursor points to an event or not.
+  bool IsEvent() const;
+
+  /// \return
+  ///     The specific kind of event the cursor is pointing at, or \b
+  ///     TraceEvent::eTraceEventNone if the cursor not pointing to an event.
+  lldb::TraceEvent GetEventType() const;
+
+  /// \return
+  ///     A human-readable description of the event this cursor is pointing at.
+  const char *GetEventTypeAsString() const;
+
+  /// \return
+  ///     Whether the cursor points to an instruction.
+  bool IsInstruction() const;
+
+  /// \return
+  ///     The load address of the instruction the cursor is pointing at.
+  lldb::addr_t GetLoadAddress() const;
+
+  // TODO: should we define LLDB_INVALID_CPU_ID so this matches the behavior of
+  // `GetLoadAddress()`?
+  lldb::cpu_id_t GetCPU(SBError &error) const;
+
+protected:
+  lldb::TraceCursorSP m_opaque_sp;
+};
+} // namespace lldb
+
+#endif // LLDB_API_SBTRACECURSOR_H
Index: lldb/include/lldb/API/SBTrace.h
===================================================================
--- lldb/include/lldb/API/SBTrace.h
+++ lldb/include/lldb/API/SBTrace.h
@@ -11,6 +11,7 @@
 
 #include "lldb/API/SBDefines.h"
 #include "lldb/API/SBError.h"
+#include "lldb/API/SBTraceCursor.h"
 
 namespace lldb {
 
@@ -25,6 +26,19 @@
   static SBTrace LoadTraceFromFile(SBError &error, SBDebugger &debugger,
                                    const SBFileSpec &trace_description_file);
 
+  /// Get a \a TraceCursor for the given thread's trace.
+  ///
+  /// \param[out] error
+  ///   This will be set with an error in case of failures.
+  //
+  /// \param[in] thread
+  ///   The thread to get a \a TraceCursor for.
+  //
+  /// \return
+  ///     A \a SBTraceCursor. If the thread is not traced or its trace
+  ///     information failed to load, an \a llvm::Error is returned.
+  SBTraceCursor CreateNewCursor(SBError &error, SBThread &thread);
+
   /// Save the trace to the specified directory, which will be created if
   /// needed. This will also create a a file \a <directory>/trace.json with the
   /// main properties of the trace session, along with others files which
Index: lldb/include/lldb/API/SBDefines.h
===================================================================
--- lldb/include/lldb/API/SBDefines.h
+++ lldb/include/lldb/API/SBDefines.h
@@ -88,6 +88,7 @@
 class LLDB_API SBThreadCollection;
 class LLDB_API SBThreadPlan;
 class LLDB_API SBTrace;
+class LLDB_API SBTraceCursor;
 class LLDB_API SBType;
 class LLDB_API SBTypeCategory;
 class LLDB_API SBTypeEnumMember;
Index: lldb/bindings/interfaces.swig
===================================================================
--- lldb/bindings/interfaces.swig
+++ lldb/bindings/interfaces.swig
@@ -69,6 +69,7 @@
 %include "./interface/SBThreadCollection.i"
 %include "./interface/SBThreadPlan.i"
 %include "./interface/SBTrace.i"
+%include "./interface/SBTraceCursor.i"
 %include "./interface/SBType.i"
 %include "./interface/SBTypeCategory.i"
 %include "./interface/SBTypeEnumMember.i"
Index: lldb/bindings/interface/SBTraceCursor.i
===================================================================
--- /dev/null
+++ lldb/bindings/interface/SBTraceCursor.i
@@ -0,0 +1,63 @@
+//===-- SWIG Interface for SBTraceCursor.h ----------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+namespace lldb {
+
+%feature("docstring",
+"Represents a trace cursor."
+) SBTrace;
+class LLDB_API SBTraceCursor {
+public:
+  enum class SeekType {
+    /// The beginning of the trace, i.e the oldest item.
+    Beginning = 0,
+    /// The current position in the trace.
+    Current,
+    /// The end of the trace, i.e the most recent item.
+    End
+  };
+
+  SBTraceCursor();
+
+  SBTraceCursor(lldb::TraceCursorSP trace_cursor_sp);
+
+  void SetForwards(bool forwards);
+
+  bool IsForwards() const;
+
+  void Next();
+
+  bool HasValue();
+
+  bool GoToId(lldb::user_id_t id);
+
+  bool HasId(lldb::user_id_t id) const;
+
+  lldb::user_id_t GetId() const;
+
+  bool Seek(int64_t offset, SeekType origin);
+
+  lldb::TraceItemKind GetItemKind() const;
+
+  bool IsError() const;
+
+  const char *GetError() const;
+
+  bool IsEvent() const;
+
+  lldb::TraceEvent GetEventType() const;
+
+  const char *GetEventTypeAsString() const;
+
+  bool IsInstruction() const;
+
+  lldb::addr_t GetLoadAddress() const;
+
+  lldb::cpu_id_t GetCPU(SBError &error) const;
+};
+} // namespace lldb
Index: lldb/bindings/interface/SBTrace.i
===================================================================
--- lldb/bindings/interface/SBTrace.i
+++ lldb/bindings/interface/SBTrace.i
@@ -15,6 +15,8 @@
 public:
   SBTrace();
 
+  SBTraceCursor CreateNewCursor(SBError &error, SBThread &thread);
+
   const char *GetStartConfigurationHelp();
 
   SBFileSpec SaveToDisk(SBError &error, const SBFileSpec &bundle_dir, bool compact = false);
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to