Skip to content

Commit a90a3e6

Browse files
Progress API (#12163)
Defines _Progress API_ - a way to start long running actions and report their progress: ```ruby type Progress advance self amount:Standard.Base.Data.Numbers.Integer= -> Standard.Base.Logging.Progress log self detail:Standard.Base.Data.Text.Text -> Standard.Base.Logging.Progress run label:Standard.Base.Data.Text.Text up_to:Standard.Base.Data.Numbers.Integer action:Standard.Base.Any.Any -> Standard.Base.Any.Any ``` The reported information is then collected in language server and delivered to the IDE to display the progress via #12126 & co. # 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 - `LoggedMessage.observe` is used to collect the messages in the language server
1 parent d395b75 commit a90a3e6

File tree

42 files changed

+1416
-253
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1416
-253
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
- [Added Table.Offset][12071]
6464
- [Implemented Generic JDBC connections.][12073]
6565
- [Added Column.Offset][12092]
66+
- [Progress API][12163]
6667
- [When reading a Delimited file, if a row with more columns than expected is
6768
encountered, extra columns can be added to the result.][12231]
6869
- In `Delimited` format, the `keep_invalid_rows` setting has been renamed to
@@ -82,6 +83,7 @@
8283
[12071]: https://github.com/enso-org/enso/pull/12071
8384
[12073]: https://github.com/enso-org/enso/pull/12073
8485
[12092]: https://github.com/enso-org/enso/pull/12092
86+
[12163]: https://github.com/enso-org/enso/pull/12163
8587
[12231]: https://github.com/enso-org/enso/pull/12231
8688
[12206]: https://github.com/enso-org/enso/pull/12206
8789
[12251]: https://github.com/enso-org/enso/pull/12251

app/gui/src/project-view/stores/project/computedValueRegistry.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ export class ComputedValueRegistry {
5656
processUpdates(updates: ExpressionUpdate[]) {
5757
for (const update of updates) {
5858
const info = this.db.get(update.expressionId)
59+
if (update.payload.type == 'Pending' && update.payload.progress == -1.0) {
60+
// just update the payload
61+
if (info) {
62+
info.payload = update.payload
63+
}
64+
continue
65+
}
5966
if (info) updateInfo(info, update, this.projectNames)
6067
else this.db.set(update.expressionId, combineInfo(undefined, update, this.projectNames))
6168
}

build.sbt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,8 @@ lazy val `logging-service-logback` = project
11211121
Compile / shouldCompileModuleInfoManually := true,
11221122
Compile / internalModuleDependencies := Seq(
11231123
(`logging-service` / Compile / exportedModule).value,
1124-
(`logging-config` / Compile / exportedModule).value
1124+
(`logging-config` / Compile / exportedModule).value,
1125+
(`logging-utils` / Compile / exportedModule).value
11251126
),
11261127
Test / shouldCompileModuleInfoManually := true,
11271128
Test / javaModuleName := "org.enso.logging.service.logback.test.provider",
@@ -3062,6 +3063,7 @@ lazy val `runtime-integration-tests` =
30623063
.dependsOn(`runtime`)
30633064
.dependsOn(`runtime-test-instruments`)
30643065
.dependsOn(`logging-service-logback` % "test->test")
3066+
.dependsOn(`logging-utils` % Test)
30653067
.dependsOn(testkit % Test)
30663068
.dependsOn(`connected-lock-manager-server`)
30673069
.dependsOn(`test-utils`)
@@ -4557,9 +4559,9 @@ lazy val `library-manager-test` = project
45574559
)
45584560
.dependsOn(`library-manager`)
45594561
.dependsOn(`process-utils`)
4560-
.dependsOn(`logging-utils` % "test->test")
4562+
.dependsOn(`logging-utils`)
45614563
.dependsOn(testkit)
4562-
.dependsOn(`logging-service-logback` % "test->test")
4564+
.dependsOn(`logging-service-logback` % Test)
45634565

45644566
lazy val `connected-lock-manager` = project
45654567
.in(file("lib/scala/connected-lock-manager"))

distribution/lib/Standard/Base/0.0.0-dev/docs/api/Logging.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,9 @@
66
- Info
77
- Severe
88
- Warning
9+
- type Progress
10+
- advance self amount:Standard.Base.Data.Numbers.Integer= -> Standard.Base.Logging.Progress
11+
- log self detail:Standard.Base.Data.Text.Text -> Standard.Base.Logging.Progress
12+
- run label:Standard.Base.Data.Text.Text up_to:Standard.Base.Data.Numbers.Integer action:Standard.Base.Any.Any -> Standard.Base.Any.Any
13+
- to_text self -> Standard.Base.Any.Any
914
- Standard.Base.Any.Any.log_message self ~message:Standard.Base.Data.Text.Text level:Standard.Base.Logging.Log_Level= -> Standard.Base.Any.Any

distribution/lib/Standard/Base/0.0.0-dev/src/Logging.enso

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import project.Any.Any
22
import project.Data.Text.Text
3+
import project.Data.Vector.Vector
4+
import project.Data.Numbers.Integer
35
import project.Meta
46
import project.Nothing.Nothing
57

@@ -13,14 +15,74 @@ Any.log_message : Text -> Log_Level -> Any
1315
Any.log_message self ~message:Text level:Log_Level=Log_Level.Info =
1416
type_name = self.to Meta.Type . qualified_name
1517
logger = LoggerFactory.getLogger type_name
16-
case level of
17-
Log_Level.Finest -> logger.trace message
18-
Log_Level.Fine -> logger.debug message
19-
Log_Level.Info -> logger.info message
20-
Log_Level.Warning -> logger.warn message
21-
Log_Level.Severe -> logger.error message
18+
log_impl logger message level []
2219
self
2320

21+
## Infrastructure for reporting logs and progress of computations.
22+
23+
Useful for longer running actions (more than a second). A must
24+
for long running actions (more than ten seconds).
25+
Start by invoking `Progress.run`, providing your own action
26+
to preform and report progress as your action advances. Nest
27+
your `Progress.run` invocations freely - the infrastructure is
28+
going to take care of combining the overall status of the computations
29+
together and will report it as a whole to a
30+
[progress displaying facility](https://github.com/enso-org/enso/pull/12126).
31+
type Progress
32+
private Running label:Text max:Integer logger
33+
34+
## Performs provided `action` while reporting its progress.
35+
36+
The `action` is a function that takes a `Progress` handle
37+
and uses its methods to report progress of the computation.
38+
The value returned from `action` is returned from this
39+
method.
40+
41+
Arguments:
42+
- label: Overall description of the action
43+
- up_to: how _"long"_ - e.g. how many steps the action is expected to take.
44+
It has to be at least one or higher.
45+
- action: function performing the computation while reporting its `Progress`
46+
47+
run label:Text up_to:Integer action:(Progress->Any) -> Any =
48+
logger = LoggerFactory.getLogger (Progress.to Meta.Type . qualified_name)
49+
progress = Progress.Running label (up_to.max 1) logger
50+
log_impl logger "INIT {}:{}@{}" Log_Level.Finest [progress, progress.label, progress.max]
51+
res = action progress
52+
progress.advance up_to
53+
res
54+
55+
## Reports progress of the computation.
56+
57+
Adds provided `amount` to the already
58+
accumulated work towards `up_to` value provided when creating this `Progress`.
59+
60+
Arguments:
61+
- amount: value to add to the progress. Must be >= 0
62+
63+
Returns:
64+
The self value.
65+
66+
advance self amount:Integer=1 -> Progress =
67+
add = if amount>=0 then amount else 0
68+
log_impl self.logger "ADVANCE {}+{}" Log_Level.Finest [self, add]
69+
self
70+
71+
## Provides additional human readable detail of the status of the computation.
72+
73+
Arguments:
74+
- info: textual description of what's going on
75+
76+
Returns:
77+
The self value.
78+
79+
log self detail:Text -> Progress =
80+
log_impl self.logger "LOG {}:{}" Log_Level.Finest [self, detail]
81+
self
82+
83+
## Simple name of the progress
84+
to_text self = "Progress"
85+
2486
## PRIVATE
2587
type Log_Level
2688
## Finest (Trace) level log message.
@@ -37,3 +99,17 @@ type Log_Level
3799

38100
## Severe level log message.
39101
Severe
102+
103+
private log_impl logger ~message level args =
104+
builder = case level of
105+
Log_Level.Finest -> logger.atTrace
106+
Log_Level.Fine -> logger.atDebug
107+
Log_Level.Info -> logger.atInfo
108+
Log_Level.Warning -> logger.atWarn
109+
Log_Level.Severe -> logger.atError
110+
111+
builder.setMessage message
112+
args.each a->
113+
builder.addArgument a
114+
115+
builder.log

engine/common/src/main/java/org/enso/common/RuntimeOptions.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ private RuntimeOptions() {}
9292
private static final OptionDescriptor ENABLE_GLOBAL_SUGGESTIONS_DESCRIPTOR =
9393
OptionDescriptor.newBuilder(ENABLE_GLOBAL_SUGGESTIONS_KEY, ENABLE_GLOBAL_SUGGESTIONS).build();
9494

95+
public static final String ENABLE_PROGRESS_REPORT = optionName("enableProgressReport");
96+
public static final OptionKey<Boolean> ENABLE_PROGRESS_REPORT_KEY = new OptionKey<>(true);
97+
private static final OptionDescriptor ENABLE_PROGRESS_REPORT_DESCRIPTOR =
98+
OptionDescriptor.newBuilder(ENABLE_PROGRESS_REPORT_KEY, ENABLE_PROGRESS_REPORT).build();
99+
95100
public static final String LANGUAGE_HOME_OVERRIDE = optionName("languageHomeOverride");
96101
public static final OptionKey<String> LANGUAGE_HOME_OVERRIDE_KEY = new OptionKey<>("");
97102
private static final OptionDescriptor LANGUAGE_HOME_OVERRIDE_DESCRIPTOR =
@@ -156,6 +161,7 @@ private RuntimeOptions() {}
156161
ENABLE_AUTO_PARALLELISM_DESCRIPTOR,
157162
ENABLE_PROJECT_SUGGESTIONS_DESCRIPTOR,
158163
ENABLE_GLOBAL_SUGGESTIONS_DESCRIPTOR,
164+
ENABLE_PROGRESS_REPORT_DESCRIPTOR,
159165
INTERACTIVE_MODE_DESCRIPTOR,
160166
DISABLE_LINTING_DESCRIPTOR,
161167
LANGUAGE_HOME_OVERRIDE_DESCRIPTOR,

engine/launcher/src/test/scala/org/enso/launcher/components/LauncherRunnerSpec.scala

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import org.enso.runtimeversionmanager.config.GlobalRunnerConfigurationManager
1010
import org.enso.runtimeversionmanager.runner._
1111
import org.enso.runtimeversionmanager.test.RuntimeVersionManagerTest
1212
import org.enso.launcher.project.ProjectManager
13-
import org.enso.logger.TestLogger
13+
import org.enso.logger.ObservedMessage
1414
import org.slf4j.event.Level
1515

1616
import org.enso.testkit.FlakySpec
1717

1818
import scala.concurrent.Future
19+
import org.slf4j.LoggerFactory
1920

2021
/** We test integration of both the underlying [[Runner]] and the
2122
* [[LauncherRunner]] in a single suite.
@@ -176,28 +177,34 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest with FlakySpec {
176177
val runner = makeFakeRunner()
177178
val projectPath = getTestDirectory / "project2"
178179
val nightlyVersion = SemVer.of(0, 0, 0, "SNAPSHOT.2000-01-01")
179-
val (_, logs) = TestLogger.gather[Any, Runner](
180-
classOf[Runner], {
181-
runner
182-
.newProject(
183-
path = projectPath,
184-
name = "ProjectName2",
185-
engineVersion = nightlyVersion,
186-
normalizedName = None,
187-
projectTemplate = None,
188-
authorName = None,
189-
authorEmail = None,
190-
additionalArguments = Seq()
191-
)
192-
.get
193-
}
180+
val logger = LoggerFactory.getLogger(classOf[Runner])
181+
val action: Runnable = () => {
182+
runner
183+
.newProject(
184+
path = projectPath,
185+
name = "ProjectName2",
186+
engineVersion = nightlyVersion,
187+
normalizedName = None,
188+
projectTemplate = None,
189+
authorName = None,
190+
authorEmail = None,
191+
additionalArguments = Seq()
192+
)
193+
.get
194+
}
195+
val logs = ObservedMessage.collect(
196+
logger,
197+
action
194198
)
195199
assert(
196-
logs.exists(msg =>
197-
msg.level == Level.WARN && msg.msg.contains(
198-
"Consider using a stable version."
200+
logs.stream
201+
.filter(msg =>
202+
msg.getLevel == Level.WARN && msg.getMessage.contains(
203+
"Consider using a stable version."
204+
)
199205
)
200-
)
206+
.findAny
207+
.isPresent
201208
)
202209
}
203210

engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.Map;
77
import java.util.UUID;
88
import java.util.function.Consumer;
9+
import org.enso.common.CachePreferences;
910
import org.enso.interpreter.instrument.ExpressionExecutionState;
1011
import org.enso.interpreter.instrument.MethodCallsCache;
1112
import org.enso.interpreter.instrument.OneshotExpression;
@@ -39,6 +40,8 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks {
3940
private final Consumer<ExpressionValue> onComputedCallback;
4041
private final Consumer<ExpressionCall> functionCallCallback;
4142
private final Consumer<ExecutedVisualization> onExecutedVisualizationCallback;
43+
private final Consumer<ExpressionValue> onProgressCallbackOrNull;
44+
private ExecutionProgressObserver progressObserver;
4245

4346
/**
4447
* Creates callbacks instance.
@@ -53,6 +56,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks {
5356
* @param onCachedCallback the consumer of the cached value events.
5457
* @param functionCallCallback the consumer of function call events.
5558
* @param onExecutedVisualizationCallback the consumer of an executed visualization result.
59+
* @param onProgressCallbackOrNull the consumer of progress events
5660
*/
5761
ExecutionCallbacks(
5862
VisualizationHolder visualizationHolder,
@@ -64,7 +68,8 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks {
6468
Consumer<ExpressionValue> onCachedCallback,
6569
Consumer<ExpressionValue> onComputedCallback,
6670
Consumer<ExpressionCall> functionCallCallback,
67-
Consumer<ExecutedVisualization> onExecutedVisualizationCallback) {
71+
Consumer<ExecutedVisualization> onExecutedVisualizationCallback,
72+
Consumer<ExpressionValue> onProgressCallbackOrNull) {
6873
this.visualizationHolder = visualizationHolder;
6974
this.nextExecutionItem = nextExecutionItem;
7075
this.cache = cache;
@@ -75,6 +80,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks {
7580
this.onComputedCallback = onComputedCallback;
7681
this.functionCallCallback = functionCallCallback;
7782
this.onExecutedVisualizationCallback = onExecutedVisualizationCallback;
83+
this.onProgressCallbackOrNull = onProgressCallbackOrNull;
7884
}
7985

8086
@Override
@@ -92,24 +98,69 @@ public Object findCachedResult(IdExecutionService.Info info) {
9298
if (result != null && !nodeId.equals(nextExecutionItem)) {
9399
callOnCachedCallback(nodeId, result);
94100
return result;
101+
} else {
102+
if (onProgressCallbackOrNull != null) {
103+
reportEvaluationProgress(nodeId);
104+
}
95105
}
96106

97107
return null;
98108
}
99109

110+
@CompilerDirectives.TruffleBoundary
111+
private void reportEvaluationProgress(UUID nodeId) {
112+
if (cache.getPreferences().get(nodeId) == CachePreferences.Kind.BINDING_EXPRESSION) {
113+
var newObserver =
114+
ExecutionProgressObserver.startComputation(
115+
nodeId,
116+
(progress, msg) -> {
117+
CompilerDirectives.transferToInterpreter();
118+
var expressionValue = ExpressionValue.progress(nodeId, progress, msg);
119+
onProgressCallbackOrNull.accept(expressionValue);
120+
});
121+
refreshObserver(newObserver);
122+
}
123+
}
124+
125+
private void refreshObserver(ExecutionProgressObserver newObserverOrNull) {
126+
var o = progressObserver;
127+
if (o != null) {
128+
try {
129+
o.close();
130+
} catch (Exception ex) {
131+
throw ExecutionService.raise(RuntimeException.class, ex);
132+
}
133+
}
134+
this.progressObserver = newObserverOrNull;
135+
}
136+
100137
@Override
101138
public void updateCachedResult(IdExecutionService.Info info) {
102139
Object result = info.getResult();
103140
TypeInfo resultType = typeOf(result);
104141
UUID nodeId = info.getId();
142+
143+
if (progressObserver instanceof ExecutionProgressObserver o && nodeId.equals(o.nodeId())) {
144+
refreshObserver(null);
145+
}
146+
105147
TypeInfo cachedType = cache.getType(nodeId);
106148
FunctionCallInfo call = functionCallInfoById(nodeId);
107149
FunctionCallInfo cachedCall = cache.getCall(nodeId);
108150
ProfilingInfo[] profilingInfo = new ProfilingInfo[] {new ExecutionTime(info.getElapsedTime())};
109151

110152
ExpressionValue expressionValue =
111153
new ExpressionValue(
112-
nodeId, result, resultType, cachedType, call, cachedCall, profilingInfo, false);
154+
nodeId,
155+
result,
156+
resultType,
157+
cachedType,
158+
call,
159+
cachedCall,
160+
profilingInfo,
161+
false,
162+
-1.0,
163+
null);
113164
syncState.setExpressionUnsync(nodeId);
114165
syncState.setVisualizationUnsync(nodeId);
115166

@@ -172,7 +223,9 @@ private void callOnCachedCallback(UUID nodeId, Object result) {
172223
calls.get(nodeId),
173224
cache.getCall(nodeId),
174225
new ProfilingInfo[] {ExecutionTime.empty()},
175-
true);
226+
true,
227+
-1.0,
228+
null);
176229

177230
onCachedCallback.accept(expressionValue);
178231
}

0 commit comments

Comments
 (0)