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

Progress API #12163

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open

Progress API #12163

wants to merge 14 commits into from

Conversation

JaroslavTulach
Copy link
Member

@JaroslavTulach JaroslavTulach commented Jan 28, 2025

Pull Request Description

Defines Progress API - a way to start long running actions and to report their progress.

Important Notes

The "Enso facing API" consists of type Progress with its static method to execute a progress reporting operations. The operation is a function that gets Progress instance as a callback and can use its methods to report the advance as well as log details of the actual operation in progress.

This is all very similar to logging:

  • hence the Progress type is placed into logging
  • logging is used to report the operation progress
  • testing log appender that collects the provided messages verifies log messages are delivered

Please review this initial concept. If it is found OK, we can move towards collecting the information in language server and delivering them to the IDE to display the progress via #12126 & co.

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    TypeScript,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
  • Unit tests have been written where possible.
  • Design structured logging details
  • Define some performance goals and reach them

Prior work

  • there is NetBeans Progress API
  • it offers callback like mode - similar to Progress.run
  • however it also provides a life time unbounded handle
    • probably because an action can span multiple threads
    • not really a problem for Enso right now as our execution is single threaded
    • encapsulating Progress.run into a callback avoids stalled progress actions
    • once action is over, the progress is automatically reported as finished: 59d63bb
  • there is a special API for aggregating handles
    • a huge mistake in the design in my opinion
    • all progress reports should be composable by default -
    • just like nested Progress.run invocations are allowed
  • the NetBeans API is very unforgiving
    • when one sets progress to a value higher than 100% it yields IllegalStateException
    • there is nothing worse than if your code fails due to a bug in progress estimation in production
    • the Enso API is going to be forgiving - whatever is reported will be turned into a percentage somehow

var geom = ctx.eval("enso", code).invokeMember(MethodNames.Module.EVAL_EXPRESSION, "geom");

var oneTimeLog =
TestLogger.apply(
Copy link
Member Author

@JaroslavTulach JaroslavTulach Jan 28, 2025

Choose a reason for hiding this comment

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

To use this TestLogger class I created new logging-test-utils module in e80dfa0 and moved existing classes from logging-utils/src/test/ into it. That way we can have a regular JPMS dependency on the new module and we don't need to patch/trick the module system to exposes classes originally placed in test subdirectory.

Progress.run "from 0 to "+n.to_text n progress->
loop count_down =
if count_down <= 0 then combine.get else
combine.accept count_down progress
Copy link
Member Author

@JaroslavTulach JaroslavTulach Jan 28, 2025

Choose a reason for hiding this comment

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

Report Progress from Java

The useExistingProgressFromJava test demonstrates how a Java code inside ofAccumulator.accept method can advance a progress that has previously been created in Enso code. Enough to accept the handle to progress as a Value argument and invokeMember("advance", 1) on it.

Such a call is crossing Java/Enso boundary. Is that fast? It is:

  • fast for reporting progress of an HTTP download
  • fast for reporting progress of executing test/Base_Tests
  • not fast when reporting progress on every iteration of 1.up_to 100000 . map (+ 2)

2818c0e shows another approach using own typed interface instead of a generic Value.

Copy link
Member

Choose a reason for hiding this comment

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

I assume the speed of typed interface is still relatively similar to the invokeMember, right?

Is the solution to just report the progress every n iterations (e.g. in methods like Table::join)?

Copy link
Member Author

Choose a reason for hiding this comment

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

the speed of typed interface is still relatively similar to the invokeMember, right?

Yes, very likely it is identical.

not fast when reporting progress on every iteration of 1.up_to 100000 . map (+ 2)

Is the solution to just report the progress every n iterations (e.g. in methods like Table::join)?

How fast you want the progress reporting to be? With the proposal we have right now the performance is going to be similar to logging and I doubt you can do:

var res = new int[arr.length];
for (var i = 0; i < arr.length; i++) {
  res[i] = arr[i] + 2;
  logger.info("Just added two to " + arr[i] + " yielding " + res[i]);
}

and assume the performance will not be affected!

Define Performance Goals

We should probably set some expectations up:

  • how fast it should be to advance by default (e.g. 1)
  • how fast it should be to advance by more
  • how fast it should be to log a detailed message about the progress
  • how fast it should be from Enso?
  • how fast it should be from Java?
  • is it OK to require more complicated code in Java to reach "zero overhead" progress reporting?

Only then we can measure if we are fast enough.

Copy link
Member

@radeusgd radeusgd left a comment

Choose a reason for hiding this comment

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

I think the shape of the API looks good, I can't wait to have progress reporting in our major methods :)

* Example of a progress aggregator able to nest multiple Progress and compute % of aggregated
* progress.
*/
public final class ProgressAggregator {
Copy link
Member Author

@JaroslavTulach JaroslavTulach Jan 28, 2025

Choose a reason for hiding this comment

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

Aggregation of Nested Progress Instances

An essential concept this Progress API offers is aggregation of nested Progress instances into a single percentage number. The general idea is: language server instrument will create new ProgressAggregator when new node computation is being onEntered:

it will consume all the requests for Progress creation and their advance or close calls. Based on that it will compute % of computation of the node state and report it to the IDE using Pending.progress: number?.

The algorithm should look like the one demonstrated in the ProgressAggregator class. E.g.:

  • each Progress is created with a finite amount of steps
    • immediately step zero is assumed to be in progress
    • e.g. we are working on interval 0.0 - 1.0/max
    • once the code advance by 1, we report 1.0/max percentage is finished
    • and start working on 1.0/max to 2.0/max interval
  • when a nested progress is created with max2 steps
    • we split the current interval into max2 intervals
    • we let the nested progress to work on these intervals only
    • e.g. never crossing the current step interval of parent Progress
  • when a Progress is closed - in Enso: when its action is over
    • it reports 100% own status
    • whole its interval is reported as finished
  • the progress monotonically increases - it shall never decrease

distribution/lib/Standard/Base/0.0.0-dev/src/Logging.enso Outdated Show resolved Hide resolved

advance self amount:Integer -> Progress =
add = if amount>=0 then amount else 0
self.log_message self.label+"+"+add.to_text Log_Level.Finest # TBD
Copy link
Contributor

@4e6 4e6 Jan 29, 2025

Choose a reason for hiding this comment

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

I don't like that we use special characters as delimiters. Instead of using strings, the Progress class can emit more structured events

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, you are right. Emitting and parsing text does not form the best (internal) API.

This is not the final state of the internals yet (note the # TBD comment at the end of line). My plan is to use Structured Logging for Communication just like we did in NetBeans.

However from the point of Enso Progress API, it is not really important how exactly we deliver the information to language server - thus I asked for inception review already with this detail remaining to be done.

}

public static final class AccumulatorWithProgress {
// implements BiConsumer<Long, Progress>, Supplier<Long> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

?

Copy link
Member Author

@JaroslavTulach JaroslavTulach Jan 30, 2025

Choose a reason for hiding this comment

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

I tried to be type safe in 08497fb#diff-6bf51b15bc0a5c0977d3598560cda370dd47d1a02454122b2489016f56513036R139, but then I found such an approach cannot be expanded to 2818c0e because of generic type erasure (Truffle sees value of Object and not of Progress or Value) and had to comment the implements out. Now Truffle find the real method signatures via reflection and uses them directly.

@JaroslavTulach JaroslavTulach linked an issue Jan 30, 2025 that may be closed by this pull request
Copy link
Member

@jdunkerley jdunkerley left a comment

Choose a reason for hiding this comment

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

Looks a good approach. Hard to feel how the aggregator will feel but seems reasonable.

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.

API to report progress to the GUI
5 participants