Trace segments alpha + inlined frames#353
Conversation
| thread. *) | ||
| type t | ||
|
|
||
| val create : Ocaml_exception_info.t option -> in_filtered_region:bool -> t |
There was a problem hiding this comment.
Even once it's the only argument (as I'm getting rid of in_filtered_region in favor of externalizing that in response to your other review comments)? I'm happy either way, just clarifying.
6abeb17 to
d1c106d
Compare
8ca37d7 to
b689daf
Compare
3f580d3 to
babd49e
Compare
Svetlitski
left a comment
There was a problem hiding this comment.
Note that this review does not cover Trace_segment; I'll take a final pass over that module on Monday.
|
|
||
| let equal = phys_equal | ||
| let compare t1 t2 = Int.compare (Obj.magic t1) (Obj.magic t2) | ||
| let hash t = Int.hash (Obj.magic t) |
There was a problem hiding this comment.
This is problematic because the hash will change if the string is relocated, either via promotion to the major heap or during compaction.
| @@ -0,0 +1,6 @@ | |||
| open! Core | |||
There was a problem hiding this comment.
Given that this module is only used to intern DSO/executable paths, I think it makes more sense to replace this with an equivalent Build_id module, where instead of Interned_string.intern you have Build_id.resolve which gets the Build_id corresponding to that path, resolved via LLVM.
| module Response : sig | ||
| type t [@@deriving sexp_of] | ||
|
|
||
| val physical_frame : t -> Info.t |
There was a problem hiding this comment.
Yes, this isn't used at present. The idea is it will be useful when we completely replace Owee with LLVM.
| @@ -1 +1 @@ | |||
| (lang dune 2.0) | |||
| (lang dune 3.0) | |||
There was a problem hiding this comment.
I needed to change this in order to get access to the link_flags environment setting in the top-level dune, which we need for our statically linked build to work. This came with the unfortunate side-effect of changing dune's auto-formatting of its own files, hence why most of the project's dune files are modified in this PR.
babd49e to
b9112ac
Compare
69ed37d to
6d5cf09
Compare
Svetlitski
left a comment
There was a problem hiding this comment.
Took another pass at Trace_segment
| end | ||
|
|
||
| module Control_flow = struct | ||
| type t = |
There was a problem hiding this comment.
Making this private and gating construction behind val call : int -> t and val return : int -> t functions that internally return values from a statically allocated array when 0 <= {depth,distance} <= 15 would be an easy way to eliminate most allocations for this type.
There was a problem hiding this comment.
This PR is already gigantic, so I'll punt this to the follow-up with all of the other performance improvements.
| let dst_frame = Frame.create dst ~parent:(t.root :> Frame.t) ~kind:Physical in | ||
| append_inlined_frames t time ~physical_frame:dst_frame ~physical_frame_is_new:true | ||
| | This parent -> | ||
| (* This is probably a non-recursive tail-call. *) |
There was a problem hiding this comment.
It's outside the scope of this PR, but in the future it would be nice to retain this information in the Callstack.t so that we can produce a flow event to make the control-flow more obvious in the UI.
| ~dso:t.last_known_location.dso | ||
| ~addr:t.last_known_location.instruction_pointer | ||
| in | ||
| (* TODO I fear symbolizing at every byte like this is likely to be *very* expensive, but let's focus on just getting things working for now. *) |
There was a problem hiding this comment.
According to Claude's reading of the LLVM source, it would not be hard to make a small patch to LLVM that surfaces the enclosing address range for a queried address, so we could avoid making N symbolization requests for N bytes of contiguous code that all have the same debuginfo. Given just how expensive this has turned out to be, this seems worthwhile.
There was a problem hiding this comment.
I have a working prototype of this on another branch; it makes things appreciably faster.
4868404 to
c2f98f5
Compare
3545c82 to
75afa99
Compare
c69c09c to
db94b3c
Compare
|
One thing to guard before the new writer is usable with If callstack compression is not ready yet, it may be safer to reject event-output with the new writer or fall back to the old writer for that mode. Otherwise downstream consumers get a valid-looking events file with all callstack context stripped. |
Signed-off-by: Kevin Svetlitski <ksvetlitski@janestreet.com>
4b78e6b to
24e7fa5
Compare
83f9e6b to
790010b
Compare
ebf11df to
e422693
Compare
Xyene
left a comment
There was a problem hiding this comment.
LGTM, thanks a lot for working on this!
Signed-off-by: Kevin Svetlitski <ksvetlitski@janestreet.com>
This is useful for testing. Signed-off-by: Kevin Svetlitski <ksvetlitski@janestreet.com>
This is in preparation for writing the new backend. After we've made substantial changes to `src/new_trace_writer.ml`, you'll still be able to track the history by running: ```bash git blame -C -C src/new_trace_writer.ml ``` Yes, you need to pass `-C` *twice*: > -C ... when this option is given twice, the command additionally looks for copies from other files in the commit that creates the file. Signed-off-by: Kevin Svetlitski <ksvetlitski@janestreet.com>
The existing `Trace_writer` module works well enough (albeit not perfectly) most of the time. However, it is difficult to reason about, in large part because it writes the trace in a streaming fashion. That introduces significant additional complexity and bookkeeping, and limits the ability of the trace-writer to make use of information discovered later in the trace (I believe the latter is why traces produced today often have the few frames closest to the root wrong). Because we want to extend the trace-writer with new functionality, we're starting fresh with a different design that's easier to reason about. The new implementation currently exists alongside the original, but the goal is to eventually replace it entirely. Instead of writing the trace in a streaming fashion, we construct an internal representation of the trace in memory, and write out the trace in a separate, final pass once all of the events have been consumed. The module responsible for doing most of the heavy lifting is the new `Trace_segment`, which represents a continuous, lossless, and error-free segment of the trace; we create a new trace-segment whenever we encounter an error. The other major addition is that the new implementation includes inlined function calls, using LLVM for symbolization, dramatically increasing the fidelity of the trace. **This PR is effectively an alpha of the new implementation.** The code here does indeed work, and produces better traces than the existing backend in many cases, but there are a couple critical issues: 1. **Error recovery**: We create a new trace-segment whenever we encounter an error, **but at present we naively treat each trace-segment as disjoint**. We need to add an additional "stitching" pass before the trace is written out, making a heuristic, best-effort attempt to join together adjacent trace-segments in a way that preserves control-flow continuity. All of this is a long way of saying that if you encounter *any* error while using the new implementation, your trace is likely to be horribly broken. 2. **Performance**: Including inlined frames makes the traces significantly larger, and the supporting code for this new functionality is written pretty naively from a performance standpoint. As a result, the new implementation is roughly 2x slower than the old implementation. It should also go without saying that while this code appears to work well on the traces I've tried it on, I would not at all be surprised if there are still bugs/edge-cases. We will address these shortcomings over time, but in the meantime the new implementation is opt-in; setting the environment variable `MAGIC_TRACE_USE_NEW_TRACE_WRITER=1` will enable it. Signed-off-by: Kevin Svetlitski <ksvetlitski@janestreet.com>
446cd09 to
25c9aa8
Compare
Introduce new, experimental trace-writing backend
The existing
Trace_writermodule works well enough (albeit not perfectly) most of the time. However, it is difficult to reason about, in large part because it writes the trace in a streaming fashion. That introduces significant additional complexity and bookkeeping, and limits the ability of the trace-writer to make use of information discovered later in the trace (I believe the latter is why traces produced today often have the few frames closest to the root wrong). Because we want to extend the trace-writer with new functionality, we're starting fresh with a different design that's easier to reason about. The new implementation currently exists alongside the original, but the goal is to eventually replace it entirely.Instead of writing the trace in a streaming fashion, we construct an internal representation of the trace in memory, and write out the trace in a separate, final pass once all of the events have been consumed. The module responsible for doing most of the heavy lifting is the new
Trace_segment, which represents a continuous, lossless, and error-free segment of the trace; we create a new trace-segment whenever we encounter an error.The other major addition is that the new implementation includes inlined function calls, using LLVM for symbolization, dramatically increasing the fidelity of the trace.
This PR is effectively an alpha of the new implementation. The code here does indeed work,
and produces better traces than the existing backend in many cases, but there are a couple
critical issues:
but at present we naively treat each trace-segment as disjoint. We need to add an additional
"stitching" pass before the trace is written out, making a heuristic, best-effort attempt to
join together adjacent trace-segments in a way that preserves control-flow continuity.
All of this is a long way of saying that if you encounter any error while using the new
implementation, your trace is likely to be horribly broken.
code for this new functionality is written pretty naively from a performance standpoint. As a result,
the new implementation is roughly 2x slower than the old implementation.
It should also go without saying that while this code appears to work well on the traces I've tried it on, I would not at all be surprised if there are still bugs/edge-cases.
We will address these shortcomings over time, but in the meantime the new implementation is opt-in; setting the environment variable
MAGIC_TRACE_USE_NEW_TRACE_WRITER=1will enable it.