jj10306 updated this revision to Diff 359848.
jj10306 retitled this revision from "[trace] Add `thread trace dump ctf -f 
<filename>` command" to "[trace] Add `thread trace export` command for Intel PT 
trace visualization".
jj10306 edited the summary of this revision.
jj10306 added a comment.

- Add HTR documentation to `lldb/docs/htr.rst`
- Change new command to `thread trace export --format <format> --outfile 
<filename>`
- Distinguish between the instruction layer and all other layers - add 
`IHTRLayer` interface that `HTRInstructionLayer` and `HTRBlockLayer` implement
- Remove unnecessary templates
- Add accessor methods to HTR classes and remove all uses of `friend`


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D105741/new/

https://reviews.llvm.org/D105741

Files:
  lldb/docs/htr.rst
  lldb/docs/media/basic_super_block_pass.png
  lldb/docs/media/htr_example.png
  lldb/include/lldb/Target/Trace.h
  lldb/include/lldb/Target/TraceHTR.h
  lldb/include/lldb/lldb-enumerations.h
  lldb/source/Commands/CommandObjectThread.cpp
  lldb/source/Commands/Options.td
  lldb/source/Target/CMakeLists.txt
  lldb/source/Target/Trace.cpp
  lldb/source/Target/TraceCursor.cpp
  lldb/source/Target/TraceHTR.cpp
  lldb/test/API/commands/trace/TestTraceExport.py

Index: lldb/test/API/commands/trace/TestTraceExport.py
===================================================================
--- /dev/null
+++ lldb/test/API/commands/trace/TestTraceExport.py
@@ -0,0 +1,60 @@
+import lldb
+from intelpt_testcase import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+from lldbsuite.test.decorators import *
+import os
+
+class TestTraceExport(TraceIntelPTTestCaseBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def testErrorMessages(self):
+        ctf_test_file = self.getBuildArtifact("ctf-test.json")
+        # We first check the output when there are no targets
+        self.expect(f"thread trace export --format ctf --outfile {ctf_test_file}",
+            substrs=["error: invalid target, create a target using the 'target create' command"],
+            error=True)
+
+        # We now check the output when there's a non-running target
+        self.expect("target create " +
+            os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
+
+        self.expect(f"thread trace export --format ctf --outfile {ctf_test_file}",
+            substrs=["error: invalid process"],
+            error=True)
+
+        # Now we check the output when there's a running target without a trace
+        self.expect("b main")
+        self.expect("run")
+
+        self.expect(f"thread trace export --format ctf --outfile {ctf_test_file}",
+            substrs=["error: Process is not being traced"],
+            error=True)
+
+    def testExportChromeTraceFormat(self):
+        self.expect("trace load -v " +
+            os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
+            substrs=["intel-pt"])
+
+        ctf_test_file = self.getBuildArtifact("ctf-test.json")
+
+        # file name, no count
+        if os.path.exists(ctf_test_file):
+            remove_file(ctf_test_file)
+        self.expect(f"thread trace export --format ctf --outfile {ctf_test_file}",
+                substrs=["Success", f"{ctf_test_file}"])
+        self.assertTrue(os.path.exists(ctf_test_file))
+
+    def testInvalidExportFormat(self):
+        self.expect("trace load -v " +
+            os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
+            substrs=["intel-pt"])
+
+        ctf_test_file = self.getBuildArtifact("ctf-test.json")
+        unknown_format = "DEADBEEF"
+
+        self.expect(f"thread trace export --format {unknown_format} --outfile {ctf_test_file}",
+                substrs=[f"error: unknown format '{unknown_format}' specified."],
+                error=True)
+
Index: lldb/source/Target/TraceHTR.cpp
===================================================================
--- /dev/null
+++ lldb/source/Target/TraceHTR.cpp
@@ -0,0 +1,468 @@
+//===-- TraceHTR.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/Target/TraceHTR.h"
+
+#include "lldb/Symbol/Function.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/Target.h"
+#include "llvm/Support/JSON.h"
+#include <sstream>
+#include <string>
+
+using namespace lldb_private;
+using namespace lldb;
+
+size_t HTRBlockMetadata::GetNumInstructions() const {
+  return m_num_instructions;
+}
+
+llvm::Optional<std::string>
+HTRBlockMetadata::GetMostFrequentylyCalledFunction() const {
+  size_t max_ncalls = 0;
+  llvm::Optional<std::string> max_name = llvm::None;
+  for (const auto &[name, ncalls] : m_func_calls) {
+    if (ncalls > max_ncalls) {
+      max_ncalls = ncalls;
+      max_name = name.AsCString();
+    }
+  }
+  return max_name;
+}
+
+llvm::DenseMap<ConstString, size_t> const &
+HTRBlockMetadata::GetFunctionCalls() const {
+  return m_func_calls;
+}
+
+lldb::addr_t HTRBlockMetadata::GetFirstInstructionLoadAddress() const {
+  return m_first_instruction_load_address;
+}
+
+size_t HTRBlock::GetOffset() const { return m_offset; }
+
+size_t HTRBlock::GetSize() const { return m_size; }
+
+HTRBlockMetadata const &HTRBlock::GetMetadata() const { return m_metadata; }
+
+std::vector<HTRBlockLayer> const &TraceHTR::GetBlockLayers() const {
+  return m_block_layers;
+}
+
+HTRInstructionLayer const &TraceHTR::GetInstructionLayer() const {
+  return m_instruction_layer;
+}
+
+void TraceHTR::AddNewBlockLayer(HTRBlockLayer &block_layer) {
+  m_block_layers.emplace_back(block_layer);
+}
+
+size_t IHTRLayer::GetLayerId() const { return layer_id; }
+
+void IHTRLayer::SetLayerId(size_t id) { layer_id = id; }
+
+void HTRBlockLayer::AddNewBlock(size_t block_id, HTRBlock &block) {
+  AppendBlockId(block_id);
+  m_block_defs.emplace(block_id, block);
+}
+
+void HTRBlockLayer::AppendBlockId(size_t block_id) {
+  m_block_id_trace.emplace_back(block_id);
+}
+
+std::vector<lldb::addr_t> const &
+HTRInstructionLayer::GetInstructionTrace() const {
+  return m_instruction_trace;
+}
+
+void HTRInstructionLayer::AddCallInstructionMetadata(
+    lldb::addr_t load_addr, llvm::Optional<ConstString> func_name) {
+  m_call_isns.emplace(load_addr, func_name);
+}
+
+void HTRInstructionLayer::AppendInstruction(lldb::addr_t load_addr) {
+  m_instruction_trace.emplace_back(load_addr);
+}
+
+std::unordered_map<size_t, HTRBlock> const &
+HTRBlockLayer::GetBlockDefs() const {
+  return m_block_defs;
+}
+
+std::vector<size_t> const &HTRBlockLayer::GetBlockIdTrace() const {
+  return m_block_id_trace;
+}
+
+size_t HTRBlockLayer::GetNumUnits() const { return m_block_id_trace.size(); }
+
+HTRBlockMetadata HTRInstructionLayer::GetMetadataByIndex(size_t index) const {
+  lldb::addr_t instruction_load_address = m_instruction_trace[index];
+  llvm::DenseMap<ConstString, size_t> func_calls;
+
+  auto func_name_it = m_call_isns.find(instruction_load_address);
+  if (func_name_it != m_call_isns.end()) {
+    if (llvm::Optional<ConstString> func_name = func_name_it->second) {
+      func_calls[*func_name] = 1;
+    }
+  }
+  return {instruction_load_address, 1, func_calls};
+}
+
+size_t HTRInstructionLayer::GetNumUnits() const {
+  return m_instruction_trace.size();
+}
+
+HTRBlockMetadata HTRBlockLayer::GetMetadataByIndex(size_t index) const {
+  size_t block_id = m_block_id_trace[index];
+  HTRBlock block = m_block_defs.find(block_id)->second;
+  return block.GetMetadata();
+}
+
+TraceHTR::TraceHTR(Thread &thread, lldb::TraceCursorUP &&cursor) {
+  // Layer 0 of HTR
+  HTRInstructionLayer instruction_layer;
+  instruction_layer.SetLayerId(0);
+
+  // Move cursor to the first instruction in the trace
+  cursor->SeekToBegin();
+
+  auto function_name_from_load_address =
+      [&thread](lldb::addr_t load_address) -> llvm::Optional<ConstString> {
+    lldb_private::Address pc_addr;
+    Target &target = thread.GetProcess()->GetTarget();
+    SymbolContext sc;
+    if (target.ResolveLoadAddress(load_address, pc_addr) &&
+        pc_addr.CalculateSymbolContext(&sc))
+      return sc.GetFunctionName()
+                 ? llvm::Optional<ConstString>(sc.GetFunctionName())
+                 : llvm::None;
+    else
+      return llvm::None;
+  };
+
+  // This flag becomes true when cursor->Next() returns false
+  bool valid_cursor = true;
+  while (valid_cursor) {
+    // TODO: how should we handle cursor errors in this loop?
+    lldb::addr_t current_instruction_load_address = cursor->GetLoadAddress();
+    lldb::TraceInstructionControlFlowType current_instruction_type =
+        cursor->GetInstructionControlFlowType();
+
+    instruction_layer.AppendInstruction(current_instruction_load_address);
+    valid_cursor = cursor->Next();
+    if (current_instruction_type & lldb::eTraceInstructionControlFlowTypeCall) {
+      if (valid_cursor) {
+        instruction_layer.AddCallInstructionMetadata(
+            current_instruction_load_address,
+            function_name_from_load_address(cursor->GetLoadAddress()));
+      } else {
+        // No next instruction in the trace - pass None to indicate the name of
+        // the function being called is not known
+        instruction_layer.AddCallInstructionMetadata(
+            current_instruction_load_address, llvm::None);
+      }
+    }
+  }
+  m_instruction_layer = instruction_layer;
+}
+
+HTRBlockMetadata HTRBlockMetadata::MergeMetadata(HTRBlockMetadata const &m1,
+                                                 HTRBlockMetadata const &m2) {
+  size_t num_instructions = m1.m_num_instructions + m2.m_num_instructions;
+  llvm::DenseMap<ConstString, size_t> func_calls;
+  func_calls.insert(m1.m_func_calls.begin(), m1.m_func_calls.end());
+  for (const auto &[name, num_calls] : m2.m_func_calls) {
+    if (func_calls.find(name) == func_calls.end())
+      func_calls[name] = num_calls;
+    else
+      func_calls[name] += num_calls;
+  }
+
+  return {m1.m_first_instruction_load_address, num_instructions, func_calls};
+}
+
+HTRBlock IHTRLayer::MergeUnits(size_t start_unit_index, size_t num_units) {
+  assert(num_units > 0);
+  llvm::Optional<HTRBlockMetadata> merged_metadata = llvm::None;
+  for (size_t i = start_unit_index; i < start_unit_index + num_units; i++) {
+    HTRBlockMetadata current_metadata = GetMetadataByIndex(i);
+    if (!merged_metadata) {
+      merged_metadata = current_metadata;
+    } else {
+      merged_metadata =
+          HTRBlockMetadata::MergeMetadata(*merged_metadata, current_metadata);
+    }
+  }
+  return {start_unit_index, num_units, *merged_metadata};
+}
+
+void TraceHTR::ExecutePasses() {
+  // Why does using `IHTRLayer &current_layer = m_instruction_layer;` not work?
+  HTRInstructionLayer instruction_layer = m_instruction_layer;
+  HTRBlockLayer current_layer = BasicSuperBlockMerge(instruction_layer);
+  if (instruction_layer.GetNumUnits() == current_layer.GetNumUnits())
+    return;
+  AddNewBlockLayer(current_layer);
+  while (true) {
+    HTRBlockLayer new_block_layer = BasicSuperBlockMerge(current_layer);
+    if (current_layer.GetNumUnits() == new_block_layer.GetNumUnits())
+      break;
+    AddNewBlockLayer(new_block_layer);
+    current_layer = new_block_layer;
+  }
+}
+
+void TraceHTR::Export(Stream &s, lldb::TraceExportFormat export_format,
+                      std::string outfile) {
+  if (export_format == lldb::eTraceExportChromeTraceFormat) {
+    std::error_code ec;
+    llvm::raw_fd_ostream os(outfile, ec, llvm::sys::fs::OF_Text);
+    auto outfile_cstr = outfile.c_str();
+    if (os.has_error()) {
+      s.Printf("error opening %s to export trace representation\n",
+               outfile_cstr);
+    } else {
+      os << toJSON(*this);
+      os.flush();
+      if (os.has_error()) {
+        s.Printf("error exporting trace representation to %s\n", outfile_cstr);
+      } else {
+        s.Printf("Success! Exported trace representation as Chrome Trace "
+                 "Format to %s\n",
+                 outfile_cstr);
+      }
+    }
+  } else {
+    // this shouldn't be reachable since the export format's validity is
+    // enforced in CommandObjectThread.cpp
+    s.Printf("unknown export format");
+  }
+}
+
+namespace lldb_private {
+
+HTRBlockLayer BasicSuperBlockMerge(IHTRLayer &layer) {
+  HTRBlockLayer new_block_layer;
+  new_block_layer.SetLayerId(layer.GetLayerId() + 1);
+
+  if (layer.GetNumUnits()) {
+    // Future Improvement: split this into two functions - one for finding heads
+    // and tails, one for merging/creating the next layer A 'head' is defined to
+    // be a block whose occurrences in the trace do not have a unique preceding
+    // block.
+    std::unordered_set<size_t> heads;
+
+    // The load address of the first instruction of a block is the unique ID for
+    // that block (i.e. blocks with the same first instruction load address are
+    // the same block)
+
+    // Future Improvement: no need to store all its preceding block ids, all we
+    // care about is that there is more than one preceding block id, so an enum
+    // could be used
+    std::unordered_map<lldb::addr_t, std::unordered_set<lldb::addr_t>> head_map;
+    lldb::addr_t prev_id =
+        layer.GetMetadataByIndex(0).GetFirstInstructionLoadAddress();
+    size_t num_units = layer.GetNumUnits();
+    // This excludes the first unit since it has no previous unit
+    for (size_t i = 1; i < num_units; i++) {
+      lldb::addr_t current_id =
+          layer.GetMetadataByIndex(i).GetFirstInstructionLoadAddress();
+      head_map[current_id].insert(prev_id);
+      prev_id = current_id;
+    }
+    for (const auto &[idx, predecessor_set] : head_map) {
+      if (predecessor_set.size() > 1)
+        heads.insert(idx);
+    }
+
+    // Future Improvement: identify heads and tails in the same loop
+    // A 'tail' is defined to be a block whose occurrences in the trace do
+    // not have a unique succeeding block.
+    std::unordered_set<lldb::addr_t> tails;
+    std::unordered_map<lldb::addr_t, std::unordered_set<lldb::addr_t>> tail_map;
+
+    // This excludes the last unit since it has no next unit
+    for (size_t i = 0; i < num_units - 1; i++) {
+      lldb::addr_t current_id =
+          layer.GetMetadataByIndex(i).GetFirstInstructionLoadAddress();
+      lldb::addr_t next_id =
+          layer.GetMetadataByIndex(i + 1).GetFirstInstructionLoadAddress();
+      tail_map[current_id].insert(next_id);
+    }
+
+    // Mark last block as tail so the algorithm stops gracefully
+    lldb::addr_t last_id = layer.GetMetadataByIndex(num_units - 1)
+                               .GetFirstInstructionLoadAddress();
+    tails.insert(last_id);
+    for (const auto &[idx, successor_set] : tail_map) {
+      if (successor_set.size() > 1)
+        tails.insert(idx);
+    }
+
+    // Need to keep track of size of string since things we push are variable
+    // length
+    size_t size = 0;
+    // Each super block always has the same first unit (we call this the
+    // super block head) This gurantee allows us to use the super block head as
+    // the unique key mapping to the super block it begins
+    llvm::Optional<size_t> superblock_head = llvm::None;
+    auto construct_next_layer = [&](size_t merge_start, size_t n) -> void {
+      if (!superblock_head)
+        return;
+      if (new_block_layer.GetBlockDefs().find(*superblock_head) ==
+          new_block_layer.GetBlockDefs().end()) {
+        HTRBlock new_block = layer.MergeUnits(merge_start, n);
+        new_block_layer.AddNewBlock(*superblock_head, new_block);
+      } else {
+        new_block_layer.AppendBlockId(*superblock_head);
+      }
+    };
+
+    for (size_t i = 0; i < num_units; i++) {
+      lldb::addr_t unit_id =
+          layer.GetMetadataByIndex(i).GetFirstInstructionLoadAddress();
+      auto isHead = heads.count(unit_id) > 0;
+      auto isTail = tails.count(unit_id) > 0;
+
+      if (isHead && isTail) {
+        // Head logic
+        if (size) { // this handles (tail, head) adjacency - otherwise an empty
+                    // block is created
+          // End previous super block
+          construct_next_layer(i - size, size);
+        }
+        // Current id is first in next super block since it's a head
+        superblock_head = unit_id;
+        size = 1;
+
+        // Tail logic
+        construct_next_layer(i - size + 1, size);
+        // Reset the block_head since the prev super block has come to and end
+        superblock_head = llvm::None;
+        size = 0;
+      } else if (isHead) {
+        if (size) { // this handles (tail, head) adjacency - otherwise an empty
+                    // block is created
+          // End previous super block
+          construct_next_layer(i - size, size);
+        }
+        // Current id is first in next super block since it's a head
+        superblock_head = unit_id;
+        size = 1;
+      } else if (isTail) {
+        if (!superblock_head)
+          superblock_head = unit_id;
+        size++;
+
+        // End previous super block
+        construct_next_layer(i - size + 1, size);
+        // Reset the block_head since the prev super block has come to and end
+        superblock_head = llvm::None;
+        size = 0;
+      } else {
+        if (!superblock_head)
+          superblock_head = unit_id;
+        size++;
+      }
+    }
+  }
+  return new_block_layer;
+}
+
+llvm::json::Value toJSON(const TraceHTR &htr) {
+  std::vector<llvm::json::Value> layers_as_json;
+  for (size_t i = 0; i < htr.GetInstructionLayer().GetInstructionTrace().size();
+       i++) {
+    size_t layer_id = htr.GetInstructionLayer().GetLayerId();
+    HTRBlockMetadata metadata = htr.GetInstructionLayer().GetMetadataByIndex(i);
+    lldb::addr_t load_address = metadata.GetFirstInstructionLoadAddress();
+
+    std::string display_name;
+
+    std::stringstream stream;
+    stream << "0x" << std::hex << load_address;
+    std::string load_address_hex_string(stream.str());
+    display_name.assign(load_address_hex_string);
+
+    layers_as_json.emplace_back(llvm::json::Object{
+        {"name", display_name},
+        {"ph", "B"},
+        {"ts", (ssize_t)i}, // TODO: is there a way to serialize size_t to JSON?
+                            // if not, how should we handle overflow here?
+        {"pid", (ssize_t)layer_id},
+        {"tid", (ssize_t)layer_id},
+    });
+
+    layers_as_json.emplace_back(llvm::json::Object{
+        {"ph", "E"},
+        {"ts", (ssize_t)i + 1},
+        {"pid", (ssize_t)layer_id},
+        {"tid", (ssize_t)layer_id},
+    });
+  }
+
+  for (const auto &layer : htr.GetBlockLayers()) {
+    size_t start_ts = 0;
+    std::vector<size_t> block_id_trace = layer.GetBlockIdTrace();
+    for (size_t i = 0; i < block_id_trace.size(); i++) {
+      size_t id = block_id_trace[i];
+      HTRBlock block = layer.GetBlockDefs().find(id)->second;
+      llvm::json::Value block_json = toJSON(block);
+      size_t layer_id = layer.GetLayerId();
+
+      HTRBlockMetadata metadata = block.GetMetadata();
+
+      size_t end_ts = start_ts + metadata.GetNumInstructions();
+
+      // Can't use ConstString here because no toJSON implementation exists for
+      // ConstString
+      llvm::Optional<std::string> most_freq_func =
+          metadata.GetMostFrequentylyCalledFunction();
+      std::stringstream stream;
+      stream << "0x" << std::hex << metadata.GetFirstInstructionLoadAddress();
+      std::string offset_hex_string(stream.str());
+      std::string display_name =
+          most_freq_func ? offset_hex_string + ": " + *most_freq_func
+                         : offset_hex_string;
+
+      layers_as_json.emplace_back(llvm::json::Object{
+          {"name", display_name},
+          {"ph", "B"},
+          {"ts", (ssize_t)start_ts},
+          {"pid", (ssize_t)layer_id},
+          {"tid", (ssize_t)layer_id},
+      });
+
+      layers_as_json.emplace_back(llvm::json::Object{
+          {"ph", "E"},
+          {"ts", (ssize_t)end_ts},
+          {"pid", (ssize_t)layer_id},
+          {"tid", (ssize_t)layer_id},
+          {"args", block_json},
+      });
+      start_ts = end_ts;
+    }
+  }
+  return layers_as_json;
+}
+
+llvm::json::Value toJSON(const HTRBlock &block) {
+  return llvm::json::Value(
+      llvm::json::Object{{"Functions", block.GetMetadata()}});
+}
+
+llvm::json::Value toJSON(const HTRBlockMetadata &metadata) {
+  std::vector<llvm::json::Value> function_calls;
+  for (const auto &[name, n_calls] : metadata.GetFunctionCalls())
+    function_calls.emplace_back(llvm::formatv("({0}: {1})", name, n_calls));
+
+  return llvm::json::Value(llvm::json::Object{
+      {"Number of Instructions", (ssize_t)metadata.GetNumInstructions()},
+      {"Functions", function_calls}});
+}
+} // namespace lldb_private
Index: lldb/source/Target/TraceCursor.cpp
===================================================================
--- lldb/source/Target/TraceCursor.cpp
+++ lldb/source/Target/TraceCursor.cpp
@@ -14,6 +14,7 @@
 #include "lldb/Target/Process.h"
 #include "lldb/Target/SectionLoadList.h"
 #include "lldb/Target/Trace.h"
+#include "lldb/Target/TraceHTR.h"
 
 using namespace lldb;
 using namespace lldb_private;
Index: lldb/source/Target/Trace.cpp
===================================================================
--- lldb/source/Target/Trace.cpp
+++ lldb/source/Target/Trace.cpp
@@ -18,6 +18,7 @@
 #include "lldb/Target/SectionLoadList.h"
 #include "lldb/Target/Thread.h"
 #include "lldb/Target/ThreadPostMortemTrace.h"
+#include "lldb/Target/TraceHTR.h"
 #include "lldb/Utility/Stream.h"
 
 using namespace lldb;
@@ -216,6 +217,14 @@
   DoRefreshLiveProcessState(std::move(live_process_state));
 }
 
+void Trace::ExportTrace(Thread &thread, lldb::TraceCursorUP &&cursor, Stream &s,
+                        lldb::TraceExportFormat export_format,
+                        std::string outfile) {
+  TraceHTR trace_htr(thread, std::move(cursor));
+  trace_htr.ExecutePasses();
+  trace_htr.Export(s, export_format, outfile);
+}
+
 uint32_t Trace::GetStopID() {
   RefreshLiveProcessState();
   return m_stop_id;
Index: lldb/source/Target/CMakeLists.txt
===================================================================
--- lldb/source/Target/CMakeLists.txt
+++ lldb/source/Target/CMakeLists.txt
@@ -69,6 +69,7 @@
   ThreadPostMortemTrace.cpp
   Trace.cpp
   TraceCursor.cpp
+  TraceHTR.cpp
   TraceSessionFileParser.cpp
   UnixSignals.cpp
   UnwindAssembly.cpp
Index: lldb/source/Commands/Options.td
===================================================================
--- lldb/source/Commands/Options.td
+++ lldb/source/Commands/Options.td
@@ -1250,6 +1250,14 @@
     Desc<"Show verbose trace information.">;
 }
 
+let Command = "thread trace export" in {
+  def thread_trace_export_format: Option<"format", "f">, Required, Group<1>,
+    EnumArg<"None", "TraceExportFormats()">, Desc<"Format to export the trace data to">;
+  def thread_trace_export_outfile: Option<"outfile", "o">, Required, Group<1>,
+    Arg<"Filename">,
+    Desc<"Path of the file to export the trace data">;
+}
+
 let Command = "trace schema" in {
   def trace_schema_verbose : Option<"verbose", "v">, Group<1>,
     Desc<"Show verbose trace schema logging for debugging the plug-in.">;
Index: lldb/source/Commands/CommandObjectThread.cpp
===================================================================
--- lldb/source/Commands/CommandObjectThread.cpp
+++ lldb/source/Commands/CommandObjectThread.cpp
@@ -2114,6 +2114,96 @@
   std::map<const Thread *, TraceCursorUP> m_cursors;
 };
 
+static constexpr OptionEnumValueElement g_trace_export_format[] = {
+    {eTraceExportChromeTraceFormat, "ctf",
+     "Export trace data to Chrome Trace Format (ctf)"},
+};
+
+static constexpr OptionEnumValues TraceExportFormats() {
+  return OptionEnumValues(g_trace_export_format);
+}
+
+// CommandObjectTraceExport
+#define LLDB_OPTIONS_thread_trace_export
+#include "CommandOptions.inc"
+
+class CommandObjectTraceExport : public CommandObjectIterateOverThreads {
+public:
+  class CommandOptions : public Options {
+  public:
+    CommandOptions() : Options() { OptionParsingStarting(nullptr); }
+
+    ~CommandOptions() override = default;
+
+    Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+                          ExecutionContext *execution_context) override {
+      Status error;
+      const int short_option = m_getopt_table[option_idx].val;
+
+      switch (short_option) {
+      case 'f': {
+        m_trace_export_format =
+            (TraceExportFormat)OptionArgParser::ToOptionEnum(
+                option_arg, GetDefinitions()[option_idx].enum_values,
+                eTraceExportUnknownFormat, error);
+        if (m_trace_export_format == eTraceExportUnknownFormat)
+          error.SetErrorStringWithFormat("unknown format '%s' specified.",
+                                         option_arg.str().c_str());
+        break;
+      }
+      case 'o': {
+        m_out_file.assign(std::string(option_arg));
+        break;
+      }
+      default:
+        llvm_unreachable("Unimplemented option");
+      }
+      return error;
+    }
+
+    void OptionParsingStarting(ExecutionContext *execution_context) override {
+      m_trace_export_format = eTraceExportUnknownFormat;
+      m_out_file.clear();
+    }
+
+    llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+      return llvm::makeArrayRef(g_thread_trace_export_options);
+    }
+
+    // Instance variables to hold the values for command options.
+    //
+    TraceExportFormat m_trace_export_format;
+    std::string m_out_file;
+  };
+
+  CommandObjectTraceExport(CommandInterpreter &interpreter)
+      : CommandObjectIterateOverThreads(
+            interpreter, "thread trace export",
+            "Export a thread's trace to the specified the specified format.",
+            nullptr,
+            eCommandRequiresProcess | eCommandTryTargetAPILock |
+                eCommandProcessMustBeLaunched | eCommandProcessMustBePaused |
+                eCommandProcessMustBeTraced),
+        m_options() {}
+
+  ~CommandObjectTraceExport() override = default;
+
+  Options *GetOptions() override { return &m_options; }
+
+protected:
+  bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override {
+    const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace();
+    ThreadSP thread_sp =
+        m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid);
+    trace_sp->ExportTrace(
+        *thread_sp, trace_sp->GetCursor(*thread_sp), result.GetOutputStream(),
+        m_options.m_trace_export_format, m_options.m_out_file);
+    return true;
+  }
+
+  CommandOptions m_options;
+};
+
 // CommandObjectMultiwordTraceDump
 class CommandObjectMultiwordTraceDump : public CommandObjectMultiword {
 public:
@@ -2145,6 +2235,8 @@
                    CommandObjectSP(new CommandObjectTraceStart(interpreter)));
     LoadSubCommand("stop",
                    CommandObjectSP(new CommandObjectTraceStop(interpreter)));
+    LoadSubCommand("export",
+                   CommandObjectSP(new CommandObjectTraceExport(interpreter)));
   }
 
   ~CommandObjectMultiwordTrace() override = default;
Index: lldb/include/lldb/lldb-enumerations.h
===================================================================
--- lldb/include/lldb/lldb-enumerations.h
+++ lldb/include/lldb/lldb-enumerations.h
@@ -1145,6 +1145,10 @@
   eSaveCoreDirtyOnly = 2,
 };
 
+enum TraceExportFormat {
+  eTraceExportUnknownFormat = 0,
+  eTraceExportChromeTraceFormat = 1,
+};
 } // namespace lldb
 
 #endif // LLDB_LLDB_ENUMERATIONS_H
Index: lldb/include/lldb/Target/TraceHTR.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/Target/TraceHTR.h
@@ -0,0 +1,226 @@
+//===-- TraceHTR.h --------------------------------------------------------===//
+//
+// Hierarchical Trace Representation (HTR) - see lldb/docs/htr.rst for
+// comprehensive HTR documentation// 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_TARGET_TRACE_HTR_H
+#define LLDB_TARGET_TRACE_HTR_H
+
+#include "lldb/Target/Thread.h"
+
+#include <unordered_map>
+#include <unordered_set>
+
+namespace lldb_private {
+
+/// \class HTRBlockMetadata TraceHTR.h "lldb/Target/TraceHTR.h"
+/// Metadata associated with an HTR block
+/// See lldb/docs/htr.rst for comprehensive HTR documentation
+class HTRBlockMetadata {
+public:
+  HTRBlockMetadata(lldb::addr_t first_instruction_load_address,
+                   size_t num_instructions,
+                   llvm::DenseMap<ConstString, size_t> &func_calls)
+      : m_first_instruction_load_address(first_instruction_load_address),
+        m_num_instructions(num_instructions), m_func_calls(func_calls) {}
+  static HTRBlockMetadata MergeMetadata(HTRBlockMetadata const &m1,
+                                        HTRBlockMetadata const &m2);
+  size_t GetNumInstructions() const;
+  llvm::Optional<std::string> GetMostFrequentylyCalledFunction() const;
+  lldb::addr_t GetFirstInstructionLoadAddress() const;
+  llvm::DenseMap<ConstString, size_t> const &GetFunctionCalls() const;
+
+private:
+  lldb::addr_t m_first_instruction_load_address;
+  size_t m_num_instructions;
+  llvm::DenseMap<ConstString, size_t> m_func_calls;
+};
+
+/// \class HTRBlock TraceHTR.h "lldb/Target/TraceHTR.h"
+/// Block structure representing a sequence of trace "units" (ie instructions)
+/// Sequences of blocks are merged to create a new, single block
+/// See lldb/docs/htr.rst for comprehensive HTR documentation
+class HTRBlock {
+public:
+  HTRBlock(size_t offset, size_t size, HTRBlockMetadata metadata)
+      : m_offset(offset), m_size(size), m_metadata(metadata) {}
+  size_t GetOffset() const;
+  size_t GetSize() const;
+  HTRBlockMetadata const &GetMetadata() const;
+
+private:
+  /// Offset in the previous layer
+  size_t m_offset;
+  /// Number of blocks/instructions that make up this block in the previous
+  /// layer
+  size_t m_size;
+  /// General metadata for this block
+  HTRBlockMetadata m_metadata;
+};
+
+/// \class HTRLayer TraceHTR.h "lldb/Target/TraceHTR.h"
+/// HTR layer interface
+/// See lldb/docs/htr.rst for comprehensive HTR documentation
+class IHTRLayer {
+public:
+  size_t GetLayerId() const;
+  void SetLayerId(size_t id);
+  /// Get the metadata associated with the unit (instruction or block) at
+  /// position `index` of the layer
+  virtual HTRBlockMetadata GetMetadataByIndex(size_t index) const = 0;
+  /// Get the total number of units (instruction or block) in this layer
+  virtual size_t GetNumUnits() const = 0;
+
+  virtual ~IHTRLayer() = default;
+
+  /// Creates a new block from the result of merging a contiguous sequence of
+  /// "units" (instructions or blocks depending on layer type) in this layer
+  ///
+  /// \param[in] start_unit_index
+  ///     The index of the first unit to be merged
+  ///
+  /// \param[in] num_units
+  ///     The number of units to be merged
+  ///
+  /// \return
+  ///     A new block instance representing the merge of the specified blocks
+  HTRBlock MergeUnits(size_t start_unit_index, size_t num_units);
+
+protected:
+  /// Unique layer ID
+  size_t layer_id;
+};
+
+/// See lldb/docs/htr.rst for comprehensive HTR documentation
+class HTRInstructionLayer : public IHTRLayer {
+public:
+  ~HTRInstructionLayer() override = default;
+  /// Constructs an `HTRBlockMetadata` for a single instruction of this layer.
+  /// This allows passes to operate on an IHTRLayer without knowing whether it
+  /// is an HTRInstructionLayer
+  // or HTRBlockLayer
+  HTRBlockMetadata GetMetadataByIndex(size_t index) const override;
+  size_t GetNumUnits() const override;
+
+  std::vector<lldb::addr_t> const &GetInstructionTrace() const;
+  void AddCallInstructionMetadata(lldb::addr_t load_addr,
+                                  llvm::Optional<ConstString> func_name);
+  void AppendInstruction(lldb::addr_t load_addr);
+
+private:
+  std::vector<lldb::addr_t> m_instruction_trace;
+  // Only store metadata for instructions of interest (call instructions)
+  // If we stored metadata for each instruction this would be wasteful since
+  // most instructions don't contain useful metadata
+
+  // This map contains the load address of all the call instructions.
+  // load address maps to the name of the function it calls (None if function
+  // name can't be determined)
+  std::unordered_map<lldb::addr_t, llvm::Optional<ConstString>> m_call_isns;
+};
+
+/// See lldb/docs/htr.rst for comprehensive HTR documentation
+class HTRBlockLayer : public IHTRLayer {
+public:
+  ~HTRBlockLayer() override = default;
+  HTRBlockMetadata GetMetadataByIndex(size_t index) const override;
+  size_t GetNumUnits() const override;
+
+  std::unordered_map<size_t, HTRBlock> const &GetBlockDefs() const;
+  std::vector<size_t> const &GetBlockIdTrace() const;
+  /// Updates block_id: block mapping and append `block_id` to the block id
+  /// trace
+  void AddNewBlock(size_t block_id, HTRBlock &block);
+  /// Append `block_id` to the block id trace
+  void AppendBlockId(size_t block_id);
+
+private:
+  /// Maps a unique Block ID to the corresponding HTRBlock
+  std::unordered_map<size_t, HTRBlock> m_block_defs;
+  /// Reduce memory footprint by just storing a trace of block IDs and use
+  /// m_block_id_map to map a block_id to its corresponding HTRBlock
+  std::vector<size_t> m_block_id_trace;
+};
+
+/// \class TraceHTR TraceHTR.h "lldb/Target/TraceHTR.h"
+/// Top-level HTR class
+/// See lldb/docs/htr.rst for comprehensive HTR documentation
+class TraceHTR {
+
+public:
+  TraceHTR(Thread &thread, lldb::TraceCursorUP &&cursor);
+  /// Executes passes on the HTR layers until no further
+  /// summarization/compression is achieved
+  void ExecutePasses();
+  /// Export HTR layers to the specified format and outfile
+  void Export(Stream &s, lldb::TraceExportFormat export_format,
+              std::string outfile);
+
+  std::vector<HTRBlockLayer> const &GetBlockLayers() const;
+  HTRInstructionLayer const &GetInstructionLayer() const;
+  void AddNewBlockLayer(HTRBlockLayer &block_layer);
+
+private:
+  // There is a single instruction layer per HTR
+  HTRInstructionLayer m_instruction_layer;
+  // There are one or more block layers per HTR
+  std::vector<HTRBlockLayer> m_block_layers;
+};
+
+// Serialization functions for exporting HTR to Chrome TraceFormat
+llvm::json::Value toJSON(const TraceHTR &htr);
+llvm::json::Value toJSON(const HTRBlock &block);
+llvm::json::Value toJSON(const HTRBlockMetadata &metadata);
+
+/// The HTR passes are defined below:
+
+/// Creates a new layer by merging the "basic super blocks" in the current layer
+///
+/// A "basic super block" is the longest sequence of blocks that always occur in
+/// the same order. (The concept is akin to “Basic Block" in compiler theory,
+/// but refers to dynamic occurrences rather than CFG nodes)
+///
+/// Procedure to find all basic super blocks:
+//
+///   - For each block, compute the number of distinct predecessor and
+///   successor blocks.
+///       Predecessor - the block that occurs directly before (to the left of)
+///       the current block Successor  - the block that occurs directly after
+///       (to the right of) the current block
+///   - A block with more than one distinct successor is always the start of a
+///   super block, the super block will continue until the next block with
+///   more than one distinct predecessor or successor.
+///
+/// The implementation makes use of two terms - 'heads' and 'tails' known as
+/// the 'endpoints' of a basic super block:
+///   A 'head' is defined to be a block in the trace that doesn't have a
+///   unique predecessor
+///   A 'tail' is defined to be a block in the trace that doesn't have a
+///   unique successor
+///
+/// A basic super block is defined to be a sequence of blocks between two
+/// endpoints
+///
+/// A head represents the start of the next group, so the current group
+/// ends at the block preceding the head and the next group begins with
+/// this head block
+///
+/// A tail represents the end of the current group, so the current group
+/// ends with the tail block and the next group begins with the
+/// following block.
+///
+/// See lldb/docs/htr.rst for comprehensive HTR documentation
+///
+/// \return
+///     A new layer instance representing the merge of blocks in the
+///     previous layer
+HTRBlockLayer BasicSuperBlockMerge(IHTRLayer &layer);
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_TRACE_HTR_H
Index: lldb/include/lldb/Target/Trace.h
===================================================================
--- lldb/include/lldb/Target/Trace.h
+++ lldb/include/lldb/Target/Trace.h
@@ -55,6 +55,22 @@
   ///     A stream object to dump the information to.
   virtual void Dump(Stream *s) const = 0;
 
+  /// Dump the trace into Chrome Trace Format (CTF)
+  ///
+  /// \param[in] thread
+  ///     The thread that owns the trace in question
+  ///
+  /// \param[in] s
+  ///     A stream object to dump the information to.
+  ///
+  /// \param[in] count
+  ///     The number of trace instructions to include in the CTF dump
+  ///
+  /// \param[in] outfile
+  ///     Path of the file to output the CTF dump
+  void ExportTrace(Thread &thread, lldb::TraceCursorUP &&cursor, Stream &s,
+                   lldb::TraceExportFormat export_format, std::string outfile);
+
   /// Find a trace plug-in using JSON data.
   ///
   /// When loading trace data from disk, the information for the trace data
Index: lldb/docs/htr.rst
===================================================================
--- /dev/null
+++ lldb/docs/htr.rst
@@ -0,0 +1,45 @@
+Hierarchical Trace Representation (HTR)
+======================================
+The humongous amount of data processor traces like the ones obtained with Intel PT contain is not digestible to humans in its raw form. Given this, it is useful to summarize these massive traces by extracting useful information. Hierarchical Trace Representation (HTR) is the way lldb represents a summarized trace internally. HTR efficiently stores trace data and allows the trace data to be transformed in a way akin to compiler passes.
+
+Concepts
+--------
+**Block:** One or more contiguous units of the trace. At minimum, the unit of a trace is the load address of an instruction.
+
+**Block Metadata:** Metadata associated with each *block*. For processor traces, some metadata examples are the number of instructions in the block or information on what functions are called in the block.
+
+**Layer:** The representation of trace data between passes. For Intel PT there are two types of layers:
+
+ **Instruction Layer:** Composed of the oad addresses of the instructions in the trace. In an effort to save space, 
+ metadata is only stored for instructions that are of interest, not every instruction in the trace. HTR contains a 
+ single instruction layer.
+
+ **Block Layer:** Composed of blocks - a block in *layer n* refers to a sequence of blocks in *layer n - 1*. A block in 
+ *layer 1* refers to a sequence of instructions in *layer 0* (the instruction layer). Metadata is stored for each block in 
+ a block layer. HTR contains one or more block layers.
+
+**Pass:** A transformation applied to a *layer* that generates a new *layer* that is a more summarized, consolidated representation of the trace data.
+A pass merges instructions/blocks based on its specific purpose - for example, a pass designed to summarize a processor trace by function calls would merge all the blocks of a function into a single block representing the entire function.l
+
+The image below illusrates the transformation of a trace's representation (HTR)
+
+.. image:: media/htr-example.png
+
+Passes
+------
+A *pass* is applied to a *layer* to extract useful information (summarization) and compress the trace representation into a new *layer*. The idea is to have a series of passes where each pass specializes in extracting certain information about the trace. Some examples of potential passes include: identifying functions, identifying loops, or a more general purpose such as identifying long sequences of instructions that are repeated (i.e. Basic Super Block). Below you will find a description of each pass currently implemented in lldb.
+
+**Basic Super Block Reduction**
+
+A “basic super block” is the longest sequence of blocks that always occur in the same order. (The concept is akin to “Basic Block'' in compiler theory, but refers to dynamic occurrences rather than CFG nodes).
+
+The image below shows the "basic super blocks" of the sequence. Each unique "basic super block" is marked with a different color
+
+.. image:: media/basic_super_block_pass.png
+
+*Procedure to find all super blocks:*
+
+- For each block, compute the number of distinct predecessor and successor blocks.
+ - **Predecessor** - the block that occurs directly before (to the left of) the current block
+ - **Successor** - the block that occurs directly after (to the right of) the current block
+- A block with more than one distinct successor is always the start of a super block, the super block will continue until the next block with more than one distinct predecessor or successor.
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to