Repository navigation
Conversation
…erpreter configurations
…ile directory) to the `:set` shell command
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2956 +/- ##
========================================
Coverage 45% 46%
- Complexity 6815 6885 +70
========================================
Files 844 851 +7
Lines 68826 69174 +348
Branches 10040 10075 +35
========================================
+ Hits 31434 31993 +559
+ Misses 34995 34756 -239
- Partials 2397 2425 +28 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
DavyLandman
left a comment
There was a problem hiding this comment.
Looks nice, I think my biggest question is about the raw JSON output in the CpuProfileFormatter and the base64 in json overhead for the tempfile.
| @@ -0,0 +1,243 @@ | |||
| package org.rascalmpl.interpreter.utils.profiler; | |||
There was a problem hiding this comment.
all the new files need a license header (see other new files in for example the maven code for one that's okay)
| public final Set<ProfileNode> nodes; // CDT protocol | ||
| public final long startTime; // CDT protocol | ||
| public final long endTime; // CDT protocol | ||
| public final List<Integer> samples; // CDT protocol | ||
| public final List<Long> timeDeltas; // CDT protocol |
There was a problem hiding this comment.
what is the reason all these fields (and the one of the other class) are all commented that they are "CDT protocol"?
| this.timeDeltas = timeDeltas; | ||
| } | ||
|
|
||
| public void write(Writer w, int tabs) { |
| appendlnUnchecked(w, indent(tabs, " \"nodes\": [")); | ||
|
|
||
| var iterator = nodes.iterator(); | ||
| while (iterator.hasNext()) { |
There was a problem hiding this comment.
Can we not use a JSON writer? We already have gson dependencies, and we there is a gson stream writer that would allow us to abstract away from the json serialization?
Or is there a specific reason we need to be this frugal?
| this.children = new ArrayList<>(); | ||
| } | ||
|
|
||
| public ProfileNode addChildIfAbsent(ProfileNode child) { |
There was a problem hiding this comment.
maybe this function should be renamed, because externally it behaves more like a "lookup"/memo/identity?
| // Configure the handle to store samples on disk as base 64-encoded `IValue`s | ||
| decoded -> GsonUtils.base64Encode(decoded.toIValue(evaluator.getValueFactory())), | ||
| encoded -> Sample.of(GsonUtils.<IValue> base64Decode(encoded, typeStore)) |
There was a problem hiding this comment.
why are we using gson and base64 to store IValue? the underlying IValueOutputStream should be a lot faster.
If we want to use it in a streaming way (like we should), we have to encode some kind of header for every chunk we serialize. which can just be a single byte that marks a new chunk of samples coming up.
doing that would make buffer a IListWriter and we just do a .done() before sending it to the serializer.
|
|
||
| public TempFile(Function<T, String> encode, Function<String, T> decode) throws IOException { | ||
| this.executor = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory()); | ||
| this.path = Files.createTempFile("", ""); // Prefix and suffix don't matter |
There was a problem hiding this comment.
it's still nice to give it a name, when a user finds some random files on their disk, they know what is the reason they exist.
| @Override | ||
| public void run(){ | ||
| while(running) { | ||
| sampler.produceSample(); |
There was a problem hiding this comment.
while we're sampling, can we not skip the below capturing of the current AST etc? or alternativly reuse the data that is calcualted here? as it too collects some information about the current state (al be it not the call stack).
| out.flush(); | ||
| } | ||
|
|
||
| private void reportSamples() { |
There was a problem hiding this comment.
what if this method is called when the sampling was disabled?
| // Print to out | ||
| var out = eval.getOutPrinter(); | ||
| out.printf("FORMATTED PROFILE: %s\n", message); | ||
| out.flush(); |
There was a problem hiding this comment.
is there a reason this cannot use URIResolverRegistry and instead has to be limited to local files only?



Summary
This PR adds support to the profiler to write profiles in formats that can be visualized as flame charts/graphs by other tools. Two formats are currently supported:
The

cpuprofileformat originates from the Chrome Dev Tools (CDT) protocol. Files written in this format can be opened, for instance, in VS Code using the "Flame Chart Visualizer for JavaScript Profiles" extension, or in Chrome using the built-in developer tools. Example:Note: The video demonstrates the flame chart for a typecheck of
bird-core. I cut a few frames in the middle of the video, though, to skip 45 seconds of typechecking time.The

foldedformat originates from Brendan Gregg's originalflamegraph.plscript. Files written in this format can be post-processed by that script to SVG. Example:Anecdotally, the profiler adds 10% runtime overhead.
Design/implementation notes
Intuitively, each profiling run consists of two phases: (a) while the evaluator is running, the sampler produces a stream of samples, each of which contains the call stack and a timestamp; (b) after the evaluator has run, the formatter consumes the stream of samples and generates the formatted output.
Following these two phases, new package
org.rascalmpl.interpreter.utils.profilerconsists of new interfacesSamplerandFormatter, as well as their implementations. Through these interfaces, sampling and formatting are decoupled as much as possible.To keep memory pressure low, samples are temporarily written to a file in the sampling phase, and lazily read from that file in the formatting phase, so samples don't need to be kept in memory all at the same time.
IO with the temporary file, as well as de/serialization (using base64-encoded
IValues to avoid the need to pretty-print/parseISourceLocations), are done in a separate auxiliary thread to not block the main thread.Following the existing naming conventions, there are two new configuration properties:
rascal.profileFmtcontrols the format of the profile. Possible values:"none","folded", and"cpuprofile"(default).rascal.profileLoccontrols the directory to which formatted profiles can be written. Possible values: any location (default:|cwd:///|).Like other configuration properties, the new ones can be set when the REPL process is started (e.g.,
'-Drascal.profileFmt="folded"'and-Drascal.profileDir=|home:///Desktop|) or when the REPL process is running (e.g.,:set profileFmt "folded"and:set profileDir |home:///Desktop|).To support strings and locations as arguments of
-Drascal.xyz=...and:set, the existing code for configuration properties was adapted a bit (minor changes).TODO