weiqingy commented on code in PR #923:
URL: https://github.com/apache/flink-agents/pull/923#discussion_r3654008748
##########
docs/content/docs/operations/monitoring.md:
##########
@@ -184,14 +184,44 @@ Each record contains a top-level `timestamp`, the
resolved `logLevel`, and a top
{
"timestamp": "2024-01-15T10:30:00Z",
"logLevel": "STANDARD",
- "eventType": "_input_event",
+ "eventType": "MiddleEvent",
"event": {
- "eventType": "_input_event",
- "...": "..."
+ "eventType": "MiddleEvent",
+ "id": "39361629-4f1d-4b62-b734-a48b181cb6e0",
+ "attributes": {},
+ "type": "MiddleEvent",
+ "upstreamEventId": "dad5ed00-80e2-4746-8bdb-f126bad504b5",
+ "upstreamActionName": "action1"
}
}
```
+`upstreamEventId` identifies the Event consumed by the Action that emitted the
current Event, and `upstreamActionName` identifies that Action. The framework
maintains both fields directly on the `event` object, outside business
`attributes`. A root `InputEvent` omits both fields.
+
+### Trace Tree Reconstruction
+
+The local reader rebuilds InputEvent-rooted Trace Trees from a saved File
Event Log. From the repository root, pass either one log file for text output
or a log directory for Trace Tree JSON:
Review Comment:
This is an operations page, so the audience is people running jobs from an
installed wheel, but `tools/reconstruct_trace_tree.py` only exists in a git
checkout. `python/pyproject.toml` packages `flink_agents` only and has no
`[project.scripts]` block, so nothing installs the reader or puts it on `PATH`.
Everything else in `tools/` is a build or dev script (`build.sh`, `lint.sh`,
`ut.sh`, and so on), and this is the first user-facing program to land there.
The placement has two side effects worth weighing at the same time.
`tools/lint.sh` points ruff at `python/` only (lines 89-113), so the new
203-line file gets no ruff rules, no 88-column limit and no docstring checks,
which are the Python standards `AGENTS.md` states for this repo. And
`python/flink_agents/api/tests/test_trace_tree_reader.py:19-20` has to walk up
to `parents[4]` and `subprocess.run` a repo-root script from inside the `api`
module.
Should the reader live under `python/flink_agents/` with a
`[project.scripts]` entry (something like `flink-agents-trace-tree`), or would
you rather drop the docs pointer until it is packaged?
##########
tools/reconstruct_trace_tree.py:
##########
@@ -0,0 +1,203 @@
+#!/usr/bin/env python3
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Reconstruct InputEvent-rooted Trace Trees from an Event Log."""
+
+import argparse
+import json
+import sys
+from collections import defaultdict
+from pathlib import Path
+from typing import Any, Iterator
+
+INPUT_EVENT_TYPE = "_input_event"
+
+
+def find_log_files(path: Path) -> list[Path]:
+ """Return Event Log files in deterministic file-name order."""
+ if path.is_file():
+ return [path]
+ log_files = sorted(path.glob("events-*.log"))
+ return log_files or sorted(path.glob("*.log"))
+
+
+def read_json_objects(path: Path) -> Iterator[dict[str, Any]]:
+ """Read consecutive JSON objects separated by whitespace."""
+ content = path.read_text(encoding="utf-8")
+ decoder = json.JSONDecoder()
+ position = 0
+ while position < len(content):
+ while position < len(content) and content[position].isspace():
+ position += 1
+ if position == len(content):
+ return
+ record, position = decoder.raw_decode(content, position)
Review Comment:
The reader has a five-code warning framework and `monitoring.md:223`
promises that valid InputEvent-rooted trees are retained while warnings go to
stderr, but there is no `try`/`except` anywhere in the file. One malformed
record makes `raw_decode` raise `json.JSONDecodeError` and the whole run
aborts, so every valid tree in every file is lost, not just the bad record.
That is reachable while a job is still running: `FileEventLogger.append` writes
with `writer.println(json)` and exposes `flush()` as a separate method, so a
log read mid-run can genuinely end mid-record.
There is a second path at line 59. `event = record["event"]` raises a bare
`KeyError` on any JSON object that is not an `EventLogRecord`, and the fallback
glob at line 33 (`return log_files or sorted(path.glob("*.log"))`) sweeps in
any `.log` file in the directory when no `events-*.log` matches.
Could the per-file and per-record decoding be wrapped and routed through the
existing `warning()` helper, as something like `MALFORMED_RECORD` and
`UNREADABLE_FILE`?
##########
runtime/src/test/java/org/apache/flink/agents/runtime/operator/ActionExecutionOperatorTest.java:
##########
@@ -748,6 +781,16 @@ agentPlanWithStateStore, true, new
InMemoryActionStateStore(false)),
testHarness.processElement(new StreamRecord<>(inputValue));
operator.waitInFlightEventsFinished();
}
+
+ for (Map<String, ActionState> states :
actionStateStore.getKeyedActionStates().values()) {
Review Comment:
This pre-null loop manufactures a state the runtime can no longer produce.
`setOutputEventLineage` now runs before `maybePersistTaskResult` (which is why
`outputEvents = actionTaskResult.getOutputEvents();` moved up), and
`testCompletedActionStatePersistsOutputEventLineage` asserts that persisted
states always carry lineage. So the only way to reach null lineage in a stored
state is to write it by hand here.
The assertion at line 827 is `isNotNull()`. What the re-stamp on the reuse
branch actually guards is that the replayed trigger event is a different object
with a different UUID, so a value check is what would catch a regression: as
written, the test still passes if the re-stamp writes the wrong id. Asserting
that the re-stamped `upstreamEventId` equals the id of the InputEvent processed
in the second harness would pin the behavior that matters.
This is also riding on `testActionStateStoreReplayIncurNoFunctionCall`,
whose stated concern is that functions are not re-executed. Would a separate
test for the replay re-stamp, without the pre-nulling, read more clearly?
##########
api/src/main/java/org/apache/flink/agents/api/Event.java:
##########
@@ -81,6 +87,30 @@ public Map<String, Object> getAttributes() {
return attributes;
}
+ /** Returns the ID of the Event consumed by the Action that emitted this
Event. */
+ @Nullable
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public UUID getUpstreamEventId() {
+ return upstreamEventId;
+ }
+
+ /** Sets the ID of the Event consumed by the Action that emitted this
Event. */
+ public void setUpstreamEventId(@Nullable UUID upstreamEventId) {
Review Comment:
Python declares both fields with `AliasChoices` (`event.py:89-98`), so they
are ordinary constructor kwargs, and the PR's own
`test_typed_from_event_preserves_lineage` builds a fully-formed Event in a
single `Event(type=..., attributes=..., upstreamEventId=...,
upstreamActionName=...)` call.
On the Java side the `@JsonCreator` constructor stays `(id, type,
attributes)`, so these setters are the only way in, which is why `EventTest`
has to do `new Event("ChildEvent")` followed by two `set...` calls. `AGENTS.md`
asks that public API changes keep Java and Python semantically aligned. This is
the construction surface specifically, separate from the reconstruction-helper
contract already under discussion on this PR.
Which way do you see this settling: a lineage-carrying constructor on the
Java side, or keeping both sides framework-write-only and treating the Python
kwargs as incidental to pydantic?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]