Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Obtain a checkpoint AppMap #91

Merged
merged 12 commits into from
Aug 2, 2021
Merged

Obtain a checkpoint AppMap #91

merged 12 commits into from
Aug 2, 2021

Conversation

kgilpin
Copy link
Contributor

@kgilpin kgilpin commented Jul 27, 2021

A checkpoint AppMap returns an appmap JSON, but also leaves the current recording in progress.

This can be used to get an AppMap as an intermediate result, then continue the program execution without erasing the events that led up to that point.

Also:

  • Stream all events to a temp file; removing the Memory recorder.
  • Recording is returned as an object, which can be deleted, or renamed,
    or read into a Writer.
  • ActiveSessionException is sometimes meaningful, but usually it's just
    like a RuntimeException. So, a lot of this behavior has been removed.
    The main explicit check that remains is to see if a recording is or is
    not in progress when certain actions are performed. Other types of
    unexpected errors are not client-caused and therefore they are just
    runtime exceptions and will probably result in a 500 error to the
    client.
  • Because events are written into a temp file, the name of the AppMap file
    (if any), is not needed until the recording is stopped.

Resolves #78
Resolves #50
Resolves #44

kgilpin added 9 commits July 28, 2021 10:44
Stream all events to a temp file; removing the Memory recorder.

Recording is returned as an object, which can be deleted, or renamed,
or read into a Writer.

ActiveSessionException is sometimes meaningful, but usually it's just
like a RuntimeException. So, a lot of this behavior has been removed.
The main explicit check that remains is to see if a recording is or is
not in progress when certain actions are performed. Other types of
unexpected errors are not client-caused and therefore they are just
runtime exceptions and will probably result in a 500 error to the
client.

Because events are written into a temp file, the name of the AppMap file
(if any), is not needed until the recording is stopped.
return events don't duplicate the function call fields.
http_server_request and sql_query don't need function call fields either.

Delivers #50
Delivers #44
Setting System property appmap.debug=true also enables
the other debug settings (hooks, http).
Method wrappers invoke base method as 'invokeWrappedMethod'
rather than 'invoke'
parent_id is now set on 'return' events.
@@ -67,10 +62,17 @@ public static void premain(String agentArgs, Instrumentation inst) {
metadata.recorderName = "remote_recording";
metadata.scenarioName = appmapName;

recorder.start(fileName, metadata);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fileName is not needed until stop.

public static final Boolean DebugLocals = (System.getProperty("appmap.debug.locals") != null);
public static final Boolean DebugHttp = Debug || System.getProperty("appmap.debug.http") != null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems more intuitive to me to have appmap.debug also enable these settings.

*/
public Event() {
this.setId(issueId());
public static Event functionCallEvent(Event master) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function calls need all this info, but returns don't.

@@ -177,7 +201,7 @@ public static void doFilterJakarta(Event event, Object[] args) throws ExitEarly
junit = true;
}
}
IRecordingSession.Metadata metadata = new IRecordingSession.Metadata();
RecordingSession.Metadata metadata = new RecordingSession.Metadata();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's only one type of RecordingSession now - it streams events to a temp file.

final String fileName = String.join("_", event.definedClass, event.methodId)
.replaceAll("[^a-zA-Z0-9-_]", "_");
IRecordingSession.Metadata metadata = getMetadata(event);
metadata.feature = identifierToSentence(event.methodId);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feature and featureGroup are deprecated.

}

/**
* Closes outstanding JSON objects and closes the writer.
* @throws IOException If a writer error occurs
*/
public void finalize() throws IOException {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to override finalize - it's used in garbage collection.

Event lastEvent;
// Avoid accepting new events on a thread that's already processing an event.
boolean isProcessing;
Stack<Event> callStack = new Stack<>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used to populate parent_id.

throws ActiveSessionException {
if (this.hasActiveSession()) {
throw new ActiveSessionException(ERROR_SESSION_PRESENT);
static class ActiveSession {
Copy link
Contributor Author

@kgilpin kgilpin Jul 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap the active session in an interface for safety and consistency.


ThreadState ts = threadState();

// We don't want re-entrant events on the same thread.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I don't see why we'd need to block other threads here.

ts.callStack.push(event);
} else if ( event.event.equals("return") ) {
if ( ts.callStack.isEmpty() ) {
Logger.println("Discarding 'return' event because the call stack is empty for this thread");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could happen if a recording is started and the first event that's recorded is a return.

return false;
}

private static <V> V tryClass(CtClass cls, String member, ClassAccessor<V> accessor) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By testing each access, we don't abort the isChildOf entirely if one access attempt raises an exception.

} catch (NotFoundException e) {
if (Properties.DebugHooks) {
Logger.println("could not resolve class hierarchy");
Logger.println(e);
Logger.printf("NotFoundException resolving %s of class %s: %s\n", member, cls.getName(), e.getMessage());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these messages are benign, we may want to suppress them even more.

@@ -27,7 +27,7 @@ ${@:-run_bats}
bats_ret=$?

kill ${JVM_PID}
wait -f ${JVM_PID}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-f was causing a failure in Travis - not sure why.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like -f is a new feature in bash 5.

@@ -121,9 +122,8 @@ load 'helper'
# Sanity check the events and classmap
assert_json_eq '.events | length' 6

assert_json_eq '.classMap | length' 2
assert_json_eq '[.classMap[0] | recurse | .name?] | join(".")' javax.servlet.http.HttpServlet.service
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTTP server request no longer includes defined_class or method_id.

@kgilpin kgilpin marked this pull request as ready for review July 30, 2021 11:59
@apotterri
Copy link
Contributor

FWIW, doesn't look like GH understands "Delivers", but there are some alternatives to link a PR to an issue: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue .

@kgilpin kgilpin requested a review from apotterri July 30, 2021 14:03
@kgilpin kgilpin requested a review from dustinbyrne July 30, 2021 14:03
Copy link
Contributor

@apotterri apotterri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good. There's a fair bit of overlap among the commits, you might want to do some history cleanup before you merge.

@@ -27,7 +27,7 @@ ${@:-run_bats}
bats_ret=$?

kill ${JVM_PID}
wait -f ${JVM_PID}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like -f is a new feature in bash 5.

@kgilpin
Copy link
Contributor Author

kgilpin commented Jul 30, 2021 via email

@apotterri
Copy link
Contributor

apotterri commented Aug 1, 2021

Are there tools that make it easy?

Not really, no. There are a number of GUI tools that say they let you do an interactive rebase, but they're really not any better than the command line....

If it was me, I'd consider doing git reset origin/master. This will throw away the current history, but leave you with all your changes in your working copy. You can then construct a new history by adding and committing subsets of the files. (I said "throw away", but, if you need to, you can easily recover the current history using git reflog.)

Lately I've been using Sourcetree to stage subsets of files before committing. I find it makes it easy to see and stage changes in files, and also to revert hunks if I need to. I haven't used it as much, but GitLens also seems pretty good.

@kgilpin kgilpin merged commit 41cd257 into master Aug 2, 2021
@kgilpin kgilpin deleted the feat/checkpoint branch August 2, 2021 12:20
@appland-release
Copy link

🎉 This PR is included in version 1.3.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
3 participants