This is an archive of the discontinued LLVM Phabricator instance.

[trace] Introduce Hierarchical Trace Representation (HTR) and add `thread trace export ctf` command for Intel PT trace visualization
ClosedPublic

Authored by jj10306 on Jul 9 2021, 2:51 PM.

Details

Summary

This diff introduces Hierarchical Trace Representation (HTR) and creates the thread trace export ctf -f <filename> -t <thread_id> command to export an Intel PT trace's HTR to Chrome Trace Format (CTF) for visualization.

See lldb/docs/htr.rst for context/documentation on HTR.

Overview of Changes

  • Add HTR documentation (see lldb/docs/htr.rst)
  • Add HTR structures (layer, block, block metadata)
  • Implement "Basic Super Block" HTR pass
  • Add 'thread trace export ctf' command to export the HTR of an Intel PT trace to Chrome Trace Format (CTF)

As this diff is the first iteration of HTR and trace visualization, future diffs will build on this work by generalizing the internal design of HTR and implementing new HTR passes that provide better trace summarization/visualization.

See attached video for an example of Intel PT trace visualization:

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
wallace added inline comments.Jul 13 2021, 2:20 PM
lldb/include/lldb/Target/TraceHTR.h
33 ↗(On Diff #357641)

pass the map by reference

42–68 ↗(On Diff #357641)

the consistent style is that toJSON methods are not class members but static functions

73 ↗(On Diff #357641)

+1
I don't think you should go with templates unless you had more TMetadata objects

wallace added inline comments.Jul 14 2021, 3:14 PM
lldb/include/lldb/Target/TraceHTR.h
29 ↗(On Diff #357641)

This is a very expensive structure if you are going to have for Layer 1, which is just a bunch of instructions => m_num_instructions will always be 1 and m_func_calls will have 0 or 1 entries.

I'd suggest creating two classes:

InstructionLayer (layer 0), where you could assume that m_num_instructions is 1 and instead of having the map m_func_calls, you can have an Optional<ConstString> for the function call, or you could also have a map (address -> ConstString) at the layer level that could use to look up function calls instead of storing this information in each instruction. If you follow the latter advice, the memory usage of Layer 0 would have small overhead, i.e. InstructionLayer = Instruction list + function map

BlockLayer (layer 1+), here you have blocks of 1 or more instructions. Assuming that the number of blocks will be small, you could store more metadata in each block and here statistics like number of function calls make more sense.

41 ↗(On Diff #357641)

See my comment above, this is too memory expensive

jj10306 marked 6 inline comments as done.Jul 14 2021, 4:57 PM
jj10306 added inline comments.
lldb/include/lldb/Target/TraceHTR.h
3 ↗(On Diff #357641)

Working on comprehensive HTR documentation that I plan to store in docs/ - will update the documentation in this file to mention where the comprehensive documentation can be found.

27 ↗(On Diff #357641)

Working on comprehensive HTR documentation that I plan to store in docs/ - will update the documentation in this file to mention where the comprehensive documentation can be found.

29 ↗(On Diff #357641)

Discussed offline - decided on creating an ILayer that the InstructionLayer and BlockLayer will inherit from. ILayer will define the interfaces to interact with a layer.

41 ↗(On Diff #357641)

I tried using ConstString here, but I was running into issues because I don't believe there's a hash implementation for ConstString so it's unable to be the key type for an unordered_map. I'm considering using a map instead which should allow the use of ConstString, but at the cost of performance because lookups in these map are done quite often (every time two blocks are merged). What are your thoughts?

48–50 ↗(On Diff #357641)

Working on comprehensive HTR documentation that I plan to store in docs/ - will update the documentation in this file to mention where the comprehensive documentation can be found.

73 ↗(On Diff #357641)

Currently, the templates are not needed because we have a single metadata type, but I added the templates to make this design extensible to different use cases that may require different metadata.

Since we currently have just a single metadata type, I'll go ahead and remove the templates and if we ever need to add a new metadata type we can discuss how to extend the design.

143–144 ↗(On Diff #357641)

I'm going to add getters to these classes so this should allow for all the friends to be removed

lldb/source/Commands/CommandObjectThread.cpp
2172 ↗(On Diff #357641)

I agree that the allowing exporting to different formats is useful, so I think the trace export plug-in is a great idea.

Discussed with @wallace offline and we decided for this diff we are going to add a thread trace export command that has a --format option and in a future diff we could add implement the plug-in logic.

lldb/source/Target/TraceHTR.cpp
56 ↗(On Diff #357641)

Correct, each unique address gets an entry in this map.

For the example you gave load_address_to_block_id would end up containing:

0x1000 -> 0
0x1004 -> 1
0x1008 -> 2
0x100c -> 3
0x2000 -> 4
0x2004 -> 5
0x2008 -> 6

The goal of this map is to assign each load address a new, unique id. The idea with assigning a new unique id is that this id could be smaller in size compared to a load address (ie 4 bytes versus 8 bytes) since the number of unique instructions in a trace is typically significantly smaller than the total number of instructions in a trace (<1% based on some testing I did) - this obviously would depend on the specific trace but the high level idea is there is potentially an opportunity to save space since it is very unlikely that any useful trace would have more than 2^32 - 1 unique instructions.

size_t is currently used as the type for the ids so this memory saving idea isn't implemented here, but wanted to get feedback on this idea.

For this diff, I'll go ahead and remove this map as a whole and just use the load addresses themselves as the unique id for a block, but I'd like to keep the idea I described above in mind for future work.

clayborg added inline comments.Jul 15 2021, 3:28 PM
lldb/include/lldb/Target/TraceHTR.h
41 ↗(On Diff #357641)

The constant string pool uses llvm::StringMap which ends up using "uint32_t llvm::djbHash(StringRef)". That being said, I would try and use the llvm::DenseMap instead of std::unordered_map since we already wrote the needed hashing accessors for it (search for "template <> struct DenseMapInfo<lldb_private::ConstString>" in the ConstString.h file for details).

68 ↗(On Diff #357641)

ah yes. Makes sense.

73 ↗(On Diff #357641)

That sounds good to me

lldb/source/Commands/CommandObjectThread.cpp
2172 ↗(On Diff #357641)

Sounds good!

lldb/source/Target/TraceHTR.cpp
56 ↗(On Diff #357641)

Sounds good, thanks for the explanation.

jj10306 updated this revision to Diff 359848.Jul 19 2021, 11:28 AM
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. (Show Details)
  • 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
jj10306 marked 19 inline comments as done.Jul 19 2021, 11:33 AM
jj10306 added inline comments.
lldb/docs/htr.rst
2

@clayborg @wallace Is there a way to view the "rendered" rst to ensure the the format is as I intended and that the image links are working?

lldb/include/lldb/Target/Trace.h
65–68 ↗(On Diff #359848)

Update docs - remove count and add export_format

jj10306 added inline comments.Jul 21 2021, 9:50 AM
lldb/source/Target/TraceHTR.cpp
205–217 ↗(On Diff #359848)

Any idea why the "Edit" I made above does not work?
I tried this first but after using lldb to understand why it wasn't working, it appeared that current_layer = new_block_layer wasn't properly updating the value stored in current_layer. I thought that using a reference to an abstract class as the type for current_layer would allow this variable to bind to any value that implements the interface, but after changing the code to only use concrete types (the non-edited version of this code), the code behaves as expected.

wallace requested changes to this revision.Jul 21 2021, 11:17 AM

Much better! We are getting closer. There's documentation missing. Besides, you should use unique_ptrs as mentioned in the comments so that you prevent copying massively large objects. I also left some indications on how to deal with errors.

lldb/docs/htr.rst
2

TL;DR: just do the small fix I mention below

you can use http://rst.ninjs.org/ to check the rendering. According to it you need to make the small fix I mention below.

Regarding the image, well, probably you could build the documentation locally and see that everything is working. If you look at docs/CMakeLists.txt, you'll see that there's a target called lldb-cpp-doc, but you need to enable doxygen first.

However, some parts of the documentation are kept within the tree and not exposed in the lldb website. For example, if you look at docs/lldb-gdb-remote.txt, it's not shown anywhere in https://lldb.llvm.org/index.html. So if we keep this documentation internal, this file is okay as it is, as the reader can open the image manually if needed. Once all of tracing becomes more stable and with more features, we can start making documentation like this more visible in the website, but that won't happen soon.

43–46

you need some blank lines

lldb/include/lldb/Target/Trace.h
66–68 ↗(On Diff #359848)

remove this and add an entry for export_format

lldb/include/lldb/Target/TraceHTR.h
1–9 ↗(On Diff #359848)
26–36 ↗(On Diff #359848)

leave spaces between functions for readability and add documentation for all the methods

45 ↗(On Diff #359848)

add a period . after the parenthesis

50–54 ↗(On Diff #359848)

spaces between methods and documentation

57–63 ↗(On Diff #359848)

leave this comments here, but generally we try to make the documentation of the API methods more verbose than the private members, which are rarely seen

72 ↗(On Diff #359848)

better have a constructor here that expects an ID, so that at any point in the life of the object, there's always a valid ID

99 ↗(On Diff #359848)

Add a small line in the documentation summarizing what this is. The htr.rst file should be used for digging deeper into this structure

104–106 ↗(On Diff #359848)

remove this, instead mention in the original function declaration that the implementation class can decide how to store or generate this data, as for instructions we want to do it lazily

107–113 ↗(On Diff #359848)

documentation of non-override methods

116 ↗(On Diff #359848)

mention here about the order of the trace. Are the instructions stored backwards or forwards chronologically?

124 ↗(On Diff #359848)

pretty nice!

134 ↗(On Diff #359848)

documentation

136–140 ↗(On Diff #359848)

why do you need both?

170 ↗(On Diff #359848)

Make this a std::unique_ptr<HTRInstructionLayer> so that you avoid making copies of it. You can use std::make_unique<HTRInstructionLayer>(constructor args...) to create a new instance.
Otherwise you are at the risk of creating copies, which would be super memory inefficient

172 ↗(On Diff #359848)

Same here, you should use unique pointers. You could create typedef/using declarations to have shorter names HTRInstructionLayerUP and HTRBlockLayerUP. Also your variable should have the _up suffix, e.g. m_instruction_layer_up

lldb/include/lldb/lldb-enumerations.h
1148–1151 ↗(On Diff #359848)

move this enum to Trace.h. to avoid exposing this too much.

Also remove the "unknown" format, as it's useless

I'm working on a way to create Trace Plugins, so that you could add "exporter" plugins without having to modify anything from lldb proper. That would be similar to TraceIntelPT, which is self-contained in its own folder

lldb/source/Commands/CommandObjectThread.cpp
2145–2151 ↗(On Diff #359848)

as we are going to have exporter plugins, to simplify this code, just expect the "ctf" string here explicitly. Later I'll do the smart lookup

lldb/source/Target/TraceHTR.cpp
26 ↗(On Diff #359848)

s/GetMostFrequentyly/GetMostFrequently

28–34 ↗(On Diff #359848)

a possible future optimization could be to calculate this maximum number on the fly as the map is being generated

39–41 ↗(On Diff #359848)

you have to explain somewhere how you identify functions: do you find the corresponding function of each instruction, or do you do it just for CALL instructions?

53 ↗(On Diff #359848)

remove a llvm::ArrayRef<HTRBlockLayerUP>

53–57 ↗(On Diff #359848)

as these layers could change because of merges and what not, better remove the consts out of these methods

57 ↗(On Diff #359848)

return HTRInstructionLayer &

61–62 ↗(On Diff #359848)

it might be better to use move semantics here, to avoid creating a copy inside m_block_layers. Something like this should be fine

67 ↗(On Diff #359848)

it would be better if the layer id is set at construction time

71 ↗(On Diff #359848)

consider moving the HTRBlock instead of copying

78 ↗(On Diff #359848)

Use an ArrayRef, and remove the const from the return type

126 ↗(On Diff #359848)

probably you don't want to get the ownership of the cursor, just request a lldb::TraceCursor & reference instead of the unique_ptr. You can use *cursor_up to get that reference

151 ↗(On Diff #359848)

you need to enhance the Instruction object, to support errors. You can store the error string in it using a char * pointer from a ConstString. Errors are not frequent, so that should be fine. You need to display the errors in the export data as well, as hiding them could be misleading to the user

159 ↗(On Diff #359848)

check here as well that the current position of the trace is not an error

If you need a trace with an error, try doing

trace load test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json

e.g.

(lldb) trace load /data/users/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json
(lldb) thread trace dump instructions -f -c 100
thread #1: tid = 815455

a.out`main + 15 at main.cpp:10
  [ 0] 0x000000000040066f    callq  0x400540                  ; symbol stub for: foo()
a.out`symbol stub for: foo()
  [ 1] 0x0000000000400540    jmpq   *0x200ae2(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 40
  [ 2] 0x0000000000400546    pushq  $0x2
  [ 3] 0x000000000040054b    jmp    0x400510
a.out`(none)
  [ 4] 0x0000000000400510    pushq  0x200af2(%rip)            ; _GLOBAL_OFFSET_TABLE_ + 8
  [ 5] 0x0000000000400516    jmpq   *0x200af4(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 16
  [ 6] 0x00007ffff7df1950    error: no memory mapped at this address
  ...missing instructions
a.out`main + 20 at main.cpp:10
206 ↗(On Diff #359848)

remove this assignment, just use m_instruction_layer directly

207 ↗(On Diff #359848)

you might need to cast *m_instruction_layer_up to ILayer &

This revision now requires changes to proceed.Jul 21 2021, 11:17 AM
jj10306 added inline comments.Jul 21 2021, 12:03 PM
lldb/include/lldb/Target/TraceHTR.h
136–140 ↗(On Diff #359848)

AppendBlockId is for when we just need to append a block id to the block id trace. This is needed when we encounter a block that we have previously encountered, so we only need to append its block id and not worry about creating a new block.
AddNewBlock is needed when a new block has been encountered. In this case, the new block needs to be added to m_block_defs and the its block id needs to be appended to the block id trace.

lldb/source/Commands/CommandObjectThread.cpp
2145–2151 ↗(On Diff #359848)

Are you suggesting that, for the time being, we just check that option_arg is "ctf" and set m_trace_export_format to eTraceExportChromeTraceFormat if so and report an error otherwise?

lldb/source/Target/TraceHTR.cpp
151 ↗(On Diff #359848)

What Instruction object are you referring to?
Given the way the InstructionLayer currently works, vector of load addresses and a map for metadata, how would you suggest incorporating the error information?
My first thought was to store a special value (such as 0) in the vector of load addresses for each error, but this seems a bit hacky and doesn't allow a way to map a specific error back to its error message.
What are your thoughts?

395 ↗(On Diff #359848)

Is there a way to store uint64s in JSON? If not, what's the suggested way to handle overflow here?

wallace added inline comments.Jul 21 2021, 1:44 PM
lldb/include/lldb/Target/TraceHTR.h
136–140 ↗(On Diff #359848)

ahh, then better rename them. AppendRepeatingBlockToEnd and AppendNewBlockToEnd might be better. You could omit the ToEnd suffix

lldb/source/Commands/CommandObjectThread.cpp
2145–2151 ↗(On Diff #359848)

yes, just to simplify the code. This check will go away as soon as I implement the barebones for the TraceExporter plugins

lldb/source/Target/TraceHTR.cpp
151 ↗(On Diff #359848)

If in CTR you can embed error messages, it would be nice to have that in the metadata. It's also valid to have an instruction address of 0 in the case of errors. However, you have end up having abnormal blocks like

insn1 insn2 error_kind1 insn1 insn2 error_kind2

so if you just look at the addresses, [insn1, insn2, 0] is a block that appears twice.

Now the question is: if your users will want to see the specific errors, then you'll have to handle it accordingly in the metadata. But if your users don't case what kind of errors are displayed in the chrome trace view, then replacing an error with address 0 makes sense. You could leave a TODO here in that case. Let's sync up offline anyway

395 ↗(On Diff #359848)

no, only int64_t, that's the type you have to use. And you won't have an overflow, AFAIK OSs don't use the last bits of addresses, as they use them internally for other things.

jj10306 marked 39 inline comments as done.Jul 22 2021, 2:10 PM
jj10306 added inline comments.
lldb/source/Target/TraceHTR.cpp
53–57 ↗(On Diff #359848)

Layers are not mutated after being constructed by a pass, so there is currently no reason to expose mutable references to these structures.

151 ↗(On Diff #359848)

Discussed offline - decided to replace errors with an address of 0 for the time being. If decoding errors are common/users want to know the specifics of the error, we can add logic to store the error's message in a future diff.

jj10306 updated this revision to Diff 360965.Jul 22 2021, 2:13 PM
jj10306 marked 2 inline comments as done.

Address comments:

  • use unique_ptr to prevent unnecessary copying
  • add support for trace decoding errors
wallace requested changes to this revision.Jul 23 2021, 12:33 AM

So much better. There's an optimization that you can make when merging maps, and besides that you need a more comprehensive test.

lldb/include/lldb/Target/TraceHTR.h
66 ↗(On Diff #360965)

just return a ConstString

210 ↗(On Diff #360965)

you dont need this btw, as the parent's destructor is already virtual

272 ↗(On Diff #360965)

As this is const, the only way to use this unordered_map is to do look ups, then let's better hide the implementation detail that is an unordered map and create this method instead

const HTRBlock &GetBlockById(size_t id);

that way we can later change unordered_map for anything else without affecting any callers

323 ↗(On Diff #360965)

the constructor shouldn't print anything and it it doesn't look right for a TraceHTR object to own a stream It's not part of its state.

339 ↗(On Diff #360965)

better return an llvm::Error that will contain the error message, if any. The CommandObject that invokes this method can then get the error and dump it if necessary. So, don't pass any streams here

lldb/include/lldb/lldb-enumerations.h
1147 ↗(On Diff #360965)

revert this change to diminish the noise

lldb/include/lldb/lldb-forward.h
330–331 ↗(On Diff #360965)

move this to the same file as HTR as we'll end up moving HTR to its own plugin folder

lldb/source/Target/TraceHTR.cpp
75 ↗(On Diff #360965)

add an empty life before this

124 ↗(On Diff #360965)

remove this

126 ↗(On Diff #360965)

once you rebase with the current code, you might need to invoke

cursor.SetForwards(true) 
cursor.SeekToBegin(TraceCursor::SeekType::Set, 0)
131 ↗(On Diff #360965)

calculate this outside of this function and reuse the variable

wallace added inline comments.Jul 23 2021, 12:33 AM
lldb/source/Target/TraceHTR.cpp
152 ↗(On Diff #360965)

this Printf is too noisy. You don't really need that. Just remove it and instead of invoking cursor.GetError(), invoke cursor.IsError() after rebasing the code

160 ↗(On Diff #360965)

valid_cursor is not a nice name, just call it no_more_data, end_of_trace, or something like that

164 ↗(On Diff #360965)

just call IsError as you don't need the error message

164 ↗(On Diff #360965)

i'm a little bit confused here, lines 165 -> 167 will only be execute if the current instruction is an error, whose load address is going to be 0, so you'll get an invalid function name. This should instead be

if(!end_of_trace && !cursor.IsError()) {
  m_instruction_layer_up->AddCallInstructionMetadata(
              current_instruction_load_address,
              function_name_from_load_address(cursor.GetLoadAddress()));
} else {
  m_instruction_layer_up->AddCallInstructionMetadata(
            current_instruction_load_address, llvm::None);
}
168 ↗(On Diff #360965)

remove this

184–185 ↗(On Diff #360965)

you could also just do

llvm::DenseMap<ConstString, size_t> func_calls = m1.m_func_calls;

if the object doesn't allow it, then it's fine

187–190 ↗(On Diff #360965)

did you try just doing

func_calls[name] += num_calls;

std::map will create a zeroed value for a new key when using the [] operator, so I imagine DenseMap work the same way. Nevermind if that doesn't work.

197 ↗(On Diff #360965)

remove this assert, the code wouldn't break if num_units is 0

204–205 ↗(On Diff #360965)

this is very expensive. Let's suppose that num_units is N, then you'll invoke MergeMetadata N - 1 times, and each element of the first map will be copied N - 1 times, each one from the second map will be copied N - 2 times, etc. So you'll have N^2 copies, which is just too slow.

Instead, you have two options:

  • when you merge A and B, you don't create a new map, you just copy the elements from B to A, effectively modifying A's map. That way after N merges, each element from each map will need to be copied only once. This might intuitively makes sense from an API standpoint, because when you merge blocks, you might expect the original blocks to go away
  • remove the merge method that accepts only two blocks and do the merging here coping everything to the same single map

up to you

212–214 ↗(On Diff #360965)

remove this comment

216 ↗(On Diff #360965)

better create a new method ArePassesDone

223 ↗(On Diff #360965)

also call the ArePassesDone method here

225–227 ↗(On Diff #360965)

remove this

233 ↗(On Diff #360965)

return an llvm::Error instead of receiving a Stream

233–234 ↗(On Diff #360965)

remove the TraceExportFormat enum. You don't really need it because there's one single case for now

240–241 ↗(On Diff #360965)

include the error code in the error message. See how other parts of the code emit a meaningful error message for this kind of calls

245 ↗(On Diff #360965)

can you also show the actual error message from os?

253–257 ↗(On Diff #360965)

remove

260 ↗(On Diff #360965)

you don't need this

412 ↗(On Diff #360965)

remove this comment

412–422 ↗(On Diff #360965)

use int64_t just in case, as that's how it's defined

439–440 ↗(On Diff #360965)

this comment doens't make sense, as you are instead passing display_name to the json object

lldb/test/API/commands/trace/TestTraceExport.py
61

Add a new test in which you run this algorithm on a program with a few levels of function calls, and check that the output trace makes sense. Don't check for individual load addresses, but instead check for the appearance of the methods and blocks that you expect.

This revision now requires changes to proceed.Jul 23 2021, 12:33 AM
jj10306 marked 2 inline comments as done.Jul 23 2021, 9:41 AM
jj10306 added inline comments.
lldb/include/lldb/Target/TraceHTR.h
272 ↗(On Diff #360965)

Makes sense. How should the case when an id that the layer doesn't contain is passed in? I would like to use llvm::Optional here but it's not possible to have an Optional reference (i.e. Optional<& const HTRBlock>). I was thinking about creating different methods:

bool ContainsBlock(size_t id) const for checking if the layer contains an ID
HTRBlock const & GetBlockById(size_t id) const for retrieving a reference to a block in the layer
but this still doesn't solve the issue of if a caller calls GetBlockId with an invalid ID.

Another option would be to change GetBlockId to return Optional<HTRBlock> instead of a reference, but this would result in an unnecessary copy, when all we really need is an const reference.

339 ↗(On Diff #360965)

I based this method off of TraceCursor::DumpInstructions which takes in a Stream & to output its success/error messages to. This method (TraceHTR::Export) prints a message to the console on success (not only failure) so I'm not sure how the success message could be propagated back up to the caller. I agree that using llvm::Error here would make sense if we were only printing if an Error occurred, but since we print on success I'm not sure if we should use llvm::Error over passing the Stream.

lldb/source/Target/TraceHTR.cpp
152 ↗(On Diff #360965)

Initially I wasn't planning on displaying the error, but then I was getting runtime errors because I wasn't doing anything with the error. As a result, I decided to display the error and looked at how TraceCursor::DumpInstructions does it: s << toString(std::move(err));.
I believe if an isError() method existed then I would not have this issue about not checking/using the error and I could just use the isError() method to check for the presence of an error without actually retrieving the error object.

197 ↗(On Diff #360965)

If num_units is 0 then the loop body would never execute and thus merged_metadata would be None at the time of return. This would be problematic since we return *merged_metadata.

204–205 ↗(On Diff #360965)

Ahh yep, I've been meaning to update this method to take the first approach you described.

233 ↗(On Diff #360965)

See comment on this function's declaration in TraceHTR.h

439–440 ↗(On Diff #360965)

I was trying to indicate that GetMostFrequentlyCalledFunction returns a std::string instead of ConstString because eventually the ConsString function name will need to be converted to std::string in order to be serialized, but I will remove this comment and just change the function to return ConstString and then do the conversion in the ternary that defines display_name.

lldb/test/API/commands/trace/TestTraceExport.py
61

Yes, this makes sense. Is the way to do this by loading the JSON file's contents into memory in the test and doing assertions on that structure?

wallace added inline comments.Jul 23 2021, 10:10 AM
lldb/include/lldb/Target/TraceHTR.h
272 ↗(On Diff #360965)

Optional<const HTRBlock &> should work like a charm. You can't put the & before the const AFAIK. People normally read the & backwards as in "reference to <const HTRBlock>"

339 ↗(On Diff #360965)

It's different, because DumpInstructions is supposed to dump information in the stream, but in this case Export is printing to a file. So what if someone wants to invoke Export but not from a command? e.g. from a python script. You are making this case a little bit more complicated.
You can just return llvm::success() and let the caller print anything to Stream if available. In any case, it's standard in lldb to not print anything if the operation is a success

lldb/source/Target/TraceHTR.cpp
152 ↗(On Diff #360965)

yes my bad =P After rebasing you should be able to use IsError() :) Sorry for the hiccup

197 ↗(On Diff #360965)

Ahh, true. Then also mention in the documentation that num_units must be >= 1, otherwise someone might think that could somehow get an empty HTRBlock ><

439–440 ↗(On Diff #360965)

you shouldn't change a generic API just to ease a single instance of a usage. Better return a ConstString, or even better, a llvm::StringRef, which can point to anything. ConstString has a method to get a StringRef out of it. StringRef::str() gets you a std::string if you need it, or you can use StringRef::data() to get the const char *, which helps you avoid one copy.

lldb/test/API/commands/trace/TestTraceExport.py
61

yes, that should be fine. You can also precompile a non-trivial a.out file and include it here, if you want the code to be fully deterministic. If you make it non-trivial enough, we should be able to reuse for other intel pt tests as well

jj10306 marked an inline comment as done.Jul 23 2021, 10:28 AM
jj10306 added inline comments.
lldb/include/lldb/Target/TraceHTR.h
272 ↗(On Diff #360965)

I tried what you suggested and also just a reference (no const), but it none of them work - this is the error message:

llvm/include/llvm/ADT/Optional.h:281:5: error: 'getPointer' declared as a pointer to a reference of type 'lldb_
private::HTRBlock &'
  T *getPointer() { return &Storage.getValue(); }

A cursory search of the issue brought up many articles, here's one:
https://www.fluentcpp.com/2018/10/05/pros-cons-optional-references/

339 ↗(On Diff #360965)

Ah I see, that makes sense. I'll switch it to use llvm::Error and not print anything in the case of success, because the success indication isn't particularly useful.

lldb/source/Target/TraceHTR.cpp
439–440 ↗(On Diff #360965)

Will update that function to return a StringRef 🙂

jj10306 marked 29 inline comments as done.Jul 23 2021, 2:41 PM
jj10306 added inline comments.
lldb/include/lldb/Target/TraceHTR.h
272 ↗(On Diff #360965)

Discussed offline - decided to return HTRBlock * const with a nullptr indicating that the layer does not contain a bock with the specified block id.

lldb/source/Target/TraceHTR.cpp
160 ↗(On Diff #360965)

I was trying to use "positive logic", otherwise using something like end_of_trace would require and negating every use of it and every time the Next() is called (i.e end_of_trace =!cursor->Next()).

This is obviously a very minor thing, but I think using a well named "positive logic" variable makes the code easier to read - thoughts on using a name like more_data_in_trace? It's definitely harder to come up with a well named positive logic variable so let me know if you have any better suggestions

245 ↗(On Diff #360965)

I don't believe llvm::raw_fd_ostream stores the error message, only the error code.

wallace added inline comments.Jul 23 2021, 2:48 PM
lldb/source/Target/TraceHTR.cpp
160 ↗(On Diff #360965)

more_data_in_trace is fine!

245 ↗(On Diff #360965)

you can invoke os.error() and then convert that value to a string :)

jj10306 marked 11 inline comments as done.Jul 24 2021, 8:50 AM
jj10306 updated this revision to Diff 361460.Jul 24 2021, 8:52 AM
jj10306 edited the summary of this revision. (Show Details)

Rebase and address comments

Looks like I pulled in some unwanted changes related to the traceinstruction dumper and tsc when I rebased - will resolve this issue before landing

Pretty good. Just some minor nits. Once you rebase on top of the trace exporter plugin patch, this should be good to go.

lldb/test/API/commands/trace/TestTraceExport.py
55–57

delete this. You can actually compile a main.cpp file as part of the test and trace it with intel pt, and then check its output. You don't need "trace save" to do that.

lldb/test/API/commands/trace/intelpt-trace/functions_example.cpp
1 ↗(On Diff #361460)

rename this file to export_htr_input.cpp or something like that. Same for the a.out

26 ↗(On Diff #361460)

given that we are going to add a binary and we don't want to do it often, add another function like

void iterative_handle_request_by_id(int id, int reps) {
  for (int i = 0; i < reps; i++) {
    if (i % 2 == 0)
      slow_handle_request(id);
    else
      fast_handle_request(id);
}

just to have someone interesting to test later. Then recompile your a.out

jj10306 updated this revision to Diff 361916.Jul 26 2021, 11:34 PM

Rebased (integrate with https://reviews.llvm.org/D106501#change-SoRRVpoqDNdx) and address comments

wallace accepted this revision.Jul 27 2021, 12:35 AM

Nice! I'll land this for you

jj10306 retitled this revision from [trace] Add `thread trace export` command for Intel PT trace visualization to [trace] Introduce Hierarchical Trace Representation (HTR) and add `thread trace export ctf` command for Intel PT trace visualization.Jul 27 2021, 10:09 AM
jj10306 edited the summary of this revision. (Show Details)
This revision was not accepted when it landed; it landed in state Needs Review.Jul 28 2021, 11:04 AM
This revision was landed with ongoing or failed builds.
This revision was automatically updated to reflect the committed changes.

Temporarily reverted this change as it breaks LLDB build on 32 bit Arm/Linux bot:
https://lab.llvm.org/buildbot/#/builders/17/builds/9497

teemperor added inline comments.
lldb/source/Plugins/TraceExporter/ctf/CommandObjectThreadTraceExportCTF.cpp
78

This generates a compiler warning:

[3344/4085] Building CXX object tools/lldb/source/Plugins/TraceExporter/ctf/CMakeFiles/lldbPluginTraceExporterCTF.dir/CommandObjectThreadTraceExportCTF.cpp.o
/Users/teemperor/3llvm/llvm-project/lldb/source/Plugins/TraceExporter/ctf/CommandObjectThreadTraceExportCTF.cpp:79:82: warning: format specifies type 'unsigned long long' but the argument has type 'size_t' (aka 'unsigned long') [-Wformat]
        "Thread index %" PRIu64 " is out of range (valid values are 0 - %u).\n", tid,
                      ~~~~~~~~~                                                  ^~~

The variadic format methods are obsolete and you can just use llvm::formatv(...).str() until we have a type-safe replacement.

wallace added inline comments.Jul 29 2021, 6:52 PM
lldb/source/Plugins/TraceExporter/ctf/CommandObjectThreadTraceExportCTF.cpp
78

thanks for the suggestion. TIL :)
I'll make a quick fix for this

vsk added a subscriber: vsk.Aug 2 2021, 12:09 PM

Hey @jj10306, thanks for working on this.

@wallace @clayborg -- It seems like there are a ton of logic changes introduced in this patch that are tested at too coarse a level. I'm not confident in the changes being made here. For example, there's a bunch of subtle work being done in BasicSuperBlockMerge, but only ~one opaque high-level check that attempts to validate any of the logic: self.assertTrue(num_units_by_layer[1] == 383). Where does 383 come from? How do we know that that's the right result? If there's a bug in BasicSuperBlockMerge, would the regression test just look like changing the magic 383 number?

I'm not really comfortable with this being checked in, and would suggest a revert until some more granular unit tests can be added to the patch. (Also paging @JDevlieghere in case he has thoughts on the testing.)

lldb/docs/htr.rst
27

Would be helpful to describe a brief feature roadmap here, explaining what debugger functionality can be built on top of this HTR concept.

lldb/source/Plugins/TraceExporter/common/TraceHTR.h
38

Why not move 'func_calls' instead of copying it?

Sure thing. Sorry for not being that thorough.

foad added a subscriber: foad.Aug 3 2021, 2:17 AM
foad added inline comments.
lldb/docs/htr.rst
13

Typo "oad".

22

Stray "l" at end of line.

Hey @jj10306, thanks for working on this.

@wallace @clayborg -- It seems like there are a ton of logic changes introduced in this patch that are tested at too coarse a level. I'm not confident in the changes being made here. For example, there's a bunch of subtle work being done in BasicSuperBlockMerge, but only ~one opaque high-level check that attempts to validate any of the logic: self.assertTrue(num_units_by_layer[1] == 383). Where does 383 come from? How do we know that that's the right result? If there's a bug in BasicSuperBlockMerge, would the regression test just look like changing the magic 383 number?

I'm not really comfortable with this being checked in, and would suggest a revert until some more granular unit tests can be added to the patch. (Also paging @JDevlieghere in case he has thoughts on the testing.)

Apologies for this - will submit a patch with more comprehensive tests and other minor fixes this week!

A more comprehensive test for this logic was added in https://reviews.llvm.org/D107674