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

Add trace functionality #244

Open
wants to merge 30 commits into
base: hkmc2
Choose a base branch
from

Conversation

CrescentonC
Copy link
Member

@CrescentonC CrescentonC commented Nov 30, 2024

TODO:

  • avoid obscure tracing outputs like here, which is caused by the toString() methods, which are called to compose tracing messages.
  • cleanup

The to avoid obscure tracing outputs and non-termination caused by toString(), I currently use util.inspect to show the argument and return result info in the tracing messages.

@CrescentonC CrescentonC force-pushed the hkmc2-tracelog branch 2 times, most recently from d6a43aa to 124f84d Compare November 30, 2024 16:03
@CrescentonC CrescentonC marked this pull request as ready for review November 30, 2024 18:53
@CrescentonC CrescentonC changed the title WIP: add trace functionality Add trace functionality Dec 1, 2024
@LPTK
Copy link
Contributor

LPTK commented Dec 2, 2024

Obviously you shouldn't pollute every single diff test block with a useless call to this.Predef.TraceLogger.resetIndent(0). Why is that call even necessary, when we do enable tracing?

@LPTK
Copy link
Contributor

LPTK commented Dec 2, 2024

There is no need to use :sjs on so many test cases. Just pick one representative example and leave the others without the command. So you realize that the more useless noise you add to the difftests, the more work it I will be for us to review future changes?

@CrescentonC
Copy link
Member Author

Obviously you shouldn't pollute every single diff test block with a useless call to this.Predef.TraceLogger.resetIndent(0). Why is that call even necessary, when we do enable tracing?

Thanks, I just realized that I could have done this better without polluting every single diff test block by using try/finally in the instrumentation, such that in the finally block the indentation level is always reset regardless of whether there is an uncaught error. I will try to make the change now.

I originally wrote this to reset indentation at the beginning of every diff test block because if a previous block throws an error at a higher indent level, blocks after that will start the trace log at a wrong level (this is because functions are instrumented when they are declared in blocks with flag traceJS, so the log will be printed no matter whether at call site traceJS is on).

There is no need to use :sjs on so many test cases. Just pick one representative example and leave the others without the command. So you realize that the more useless noise you add to the difftests, the more work it I will be for us to review future changes?

Thanks I see, I will also make the change to remove unnecessary :sjss.

@CrescentonC
Copy link
Member Author

I have made the changes to cleanup :sjs in tests and use try/finally to avoid polluting diff test blocks. I also made a minor improvement for multiline trace messages.

Copy link
Contributor

@LPTK LPTK left a comment

Choose a reason for hiding this comment

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

Thanks, great work.

Some remarks, though:

  • I actually preferred the way without finally, as it's much less invasive and likely to add much less runtime overhead. Can we move back to it? Before a block that contains tracing, in JSBackendDiffMaker you could always just host.send a nodeJS command to manually reset the indent, as in host.send("globalThis.Predef.TraceLogger.indent = 0")

  • I'd prefer it if tracing was disabled in non-tracing blocks, even when the block's execution involves functions that were instrumented. You could simply add a Boolean flag to Predef.TraceLogger (or set the indent to -1, tho I don't like sentinel values) which is always false unless we're tracing.

  • I don't like the "calling"/"return" pair. Repetitive but not very easy to parse visually. Have you considered using something like the following syntax?

    f(1,1)(-2)
    //│ > CALL g(-2)
    //│ > | CALL g(-1)
    //│ > | => 42
    //│ > => 42
    //│ = 44
    

… blocks calling functions defined in traced blocks will show trace by adding an `enabled` flag to predef.tracelogger
@CrescentonC
Copy link
Member Author

Some remarks, though

Thanks for your suggestions and instructions! I have pushed the changes to avoid try/finally, and in JSBackendDifftest use host.execute to directly reset the indentation level for those diff test blocks that are marked with traceJS flag. I added a flag in Predef.TraceLog so that all functionalities of tracelog is disabled when traceJS is not set for a given block, even if it calls some instrumented functions. And now CALL/=> is used.

:traceJS
:re
h(3)
//│ ═══[RUNTIME ERROR] Error: Function 'plus' expected 2 arguments but got 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Any idea why we're not seeing the trace up to the runtime error?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, because when the ReplHost encounters an error, only the error messages are kept in its replies (

final case class Error(syntax: Bool, message: Str) extends Reply {
override def map(f: Str => Reply): Reply = this
override def toString(): Str =
if syntax then
s"[syntax error] $message"
else
s"[runtime error] $message"
}
}
), maybe I can also make a patch to this in this PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure, that would be great, if you don't mind.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks! I have pushed a commit to address this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Great!

One last tweak, though: can we get the runtime error message after the output? Currently, it appears out of logical order:

//│ ═══[RUNTIME ERROR] Error: Function 'plus' expected 2 arguments but got 1
//│ CALL h(3)
//│ | CALL g(3)
//│ | | CALL g(2)
//│ | | | CALL g(1)
//│ | | | | CALL g(0)

It should be:

//│ CALL h(3)
//│ | CALL g(3)
//│ | | CALL g(2)
//│ | | | CALL g(1)
//│ | | | | CALL g(0)
//│ ═══[RUNTIME ERROR] Error: Function 'plus' expected 2 arguments but got 1

Copy link
Member Author

@CrescentonC CrescentonC Dec 3, 2024

Choose a reason for hiding this comment

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

Thanks, I have pushed the change. I also added the missing ">" at the start of each line of the output, so that now it's:

//│ > CALL h(3)
//│ > | CALL g(3)
//│ > | | CALL g(2)
//│ > | | | CALL g(1)
//│ > | | | | CALL g(0)
//│ ═══[RUNTIME ERROR] Error: Function 'plus' expected 2 arguments but got 1

@@ -108,6 +108,10 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker:
// Otherwise, it is considered a simple runtime error.
raise(ErrorReport(msg"${message}" -> N :: Nil,
source = Diagnostic.Source.Runtime))
if traceJS.isSet then
output("All outputs:")
Copy link
Contributor

@LPTK LPTK Dec 3, 2024

Choose a reason for hiding this comment

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

What's with this weird "All outputs" thing? Why would the logging output preceding an error only appear when traceJS.isSet? I don't understand how you make this sort of decisions. Is there a relevant rationale, from the point of view of the user of the system?

Copy link
Contributor

Choose a reason for hiding this comment

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

PS: Implementation or historical details are not relevant rationales.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess you were not thinking about the fact that the output is useful even when no tracing is ongoing. Indeed, that's the output of printing stuff, which is currently being silenced when errors occurs (very annoying).

Copy link
Member Author

@CrescentonC CrescentonC Dec 3, 2024

Choose a reason for hiding this comment

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

I previously chose to only show all outputs when traceJS is set because I found out that in most of the tests, "all output"s are just the duplicates of the error messages:
image

But indeed I now see that the output is useful even when no tracing is ongoing, I think maybe I can just not print out the lines showing duplicated messages and "undefined"? Or maybe I can another flag which controls whether to print all output when error?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or maybe I can another flag which controls whether to print all output when error?

No, please! Not another flag. Again, I ask you to think about the semantics of what we're implementing here. How could adding a flag like this that controls low-level details in surprising ways be any good?

Rather, you should figure out why the runtime error appears part of the retrieved output. I believe errors are actually guarded by some invisible unicode characters. Just check how this part of the code works and simply fix it. It's not very complicated at all, as far as I remember.

And of course we should also not print the final undefined. I'm confident you can figure it out.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, I have just pushed the changes to filter error messages and final undefined in the retrieved output for ReplHost.Error. Following the implementation in ReplHost.scala I realized that uncaught errors are not guarded by special invisible characters, so I just filter them using the index of substring "Uncaught " (here and here); And others are filtered out by using the invisible character

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

Successfully merging this pull request may close these issues.

2 participants