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

Feature - stdout live reporting #16975

Merged
merged 36 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
58d24dc
Add option to load stdout and stderr when requesting job status
gecage952 Oct 17, 2023
89cc78a
Update api endpoint for jobs
gecage952 Nov 3, 2023
870917b
Add missing variables and import
gecage952 Nov 3, 2023
6dfbc7a
Add capability for UI to load live job stdout and stderr
gecage952 Nov 3, 2023
fba01ad
Merge pull request #1 from gecage952/dev
gecage952 Nov 3, 2023
de7cadf
Linting / format.
dannon Nov 6, 2023
31065a6
Fix jest test
dannon Nov 6, 2023
24e7d15
Update API schema
dannon Nov 6, 2023
d336cad
Configure pulsar runner to read stdout from file if finish message st…
gecage952 Nov 17, 2023
afa0b25
XMerge branch 'feature_stdout_live_reporting' of github.com:gecage952…
gecage952 Nov 17, 2023
d2be71f
Merge branch 'dev' into feature_stdout_live_reporting
gecage952 Nov 17, 2023
fd0237f
Add new api endpoint to fetch job stdout and stderr
gecage952 Dec 15, 2023
70bfcc4
Use stdout api endpoint in job info ui while job is running
gecage952 Dec 15, 2023
7516d6a
Merge upstream into branch
gecage952 Dec 15, 2023
7052e90
Merge dev into branch and fix conflicts
gecage952 Mar 21, 2024
89113d0
Update stdout reporting code to vue3
gecage952 Mar 22, 2024
791348a
Fix linting errors
gecage952 Mar 27, 2024
87f5126
Fix more formatting and schema issues
gecage952 Mar 27, 2024
1b2ba86
Merge branch 'dev' into feature_stdout_live_reporting
gecage952 Apr 19, 2024
f1f8eec
format imports
martenson May 3, 2024
6faf683
Fix issue where only one of stdout or stderr is present
gecage952 May 9, 2024
beb5d5b
Merge branch 'feature_stdout_live_reporting' of github.com:gecage952/…
gecage952 May 13, 2024
63ede70
Fix docstrings that refer to tool stdout as job stdout
gecage952 May 29, 2024
735d5ef
Restrict tool stdout updating to properly configured job destinations
gecage952 Jun 14, 2024
deaba39
update client api schema
gecage952 Jun 14, 2024
907328e
Fix merge conflicts
gecage952 Jul 12, 2024
2e472a9
Update client api schema
gecage952 Jul 12, 2024
811a897
Update client api schema
gecage952 Sep 11, 2024
53051e7
Update client api schema
gecage952 Sep 23, 2024
858a245
Merge remote-tracking branch 'origin/dev' into feature_stdout_live_re…
jmchilton Nov 12, 2024
a2bffb8
Update JobInformation.test.js for live console output.
jmchilton Nov 12, 2024
8063632
Improved error handling for live console for tool execution.
jmchilton Nov 12, 2024
be11412
xfail the test for the freeze.
jmchilton Nov 18, 2024
a69031b
Merge remote-tracking branch 'origin/dev' into feature_stdout_live_re…
jmchilton Nov 18, 2024
f0aa89a
linting fix
jmchilton Nov 18, 2024
394fb16
Ahhh... the set metadata file is JSON and cannot be appended.
jmchilton Nov 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,10 @@ export interface paths {
* Parameters
* - id: ID of job to return
* - full: Return extra information ?
* - stdout_position: The index of the character to begin reading stdout from
* - stdout_length: How many characters of stdout to read
* - stderr_position: The index of the character to begin reading stderr from
* - stderr_length: How many characters of stderr to read
*/
get: operations["show_api_jobs__id__get"];
};
Expand Down Expand Up @@ -14596,10 +14600,18 @@ export interface operations {
* Parameters
* - id: ID of job to return
* - full: Return extra information ?
* - stdout_position: The index of the character to begin reading stdout from
* - stdout_length: How many characters of stdout to read
* - stderr_position: The index of the character to begin reading stderr from
* - stderr_length: How many characters of stderr to read
*/
parameters: {
query?: {
full?: boolean;
stdout_position?: number;
stdout_length?: number;
stderr_position?: number;
stderr_length?: number;
};
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
Expand Down
32 changes: 25 additions & 7 deletions client/src/components/JobInformation/CodeRow.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
<template>
<tr
v-b-tooltip.hover
:title="`click to ${action}`"
@mousedown="mouseIsDown = true"
@mousemove="mouseIsDown ? (mouseMoved = true) : (mouseMoved = false)"
@mouseup="toggleExpanded()">
<tr>
<td>
{{ codeLabel }}
</td>
Expand All @@ -13,7 +8,13 @@
<b-col cols="11">
<pre :class="codeClass">{{ codeItem }}</pre>
</b-col>
<b-col class="nopadding pointer">
<b-col
v-b-tooltip.hover
class="nopadding pointer"
:title="`click to ${action}`"
@mousedown="mouseIsDown = true"
@mousemove="mouseIsDown ? (mouseMoved = true) : (mouseMoved = false)"
@mouseup="toggleExpanded()">
<FontAwesomeIcon :icon="iconClass" />
</b-col>
</b-row>
Expand All @@ -40,6 +41,7 @@ export default {
mouseIsDown: false,
mouseMoved: false,
expanded: false,
lastPos: 0,
};
},
computed: {
Expand All @@ -53,6 +55,18 @@ export default {
return this.expanded ? ["fas", "compress-alt"] : ["fas", "expand-alt"];
},
},
updated() {
try {
var codeDiv = this.$el.querySelector(".code");
if (codeDiv.scrollTop + codeDiv.offsetHeight >= this.lastPos - 5) {
// scroll is at the bottom
codeDiv.scrollTop = codeDiv.scrollHeight;
}
this.lastPos = codeDiv.scrollHeight;
} catch (exception) {
console.debug("Code div is not present");
}
},
methods: {
toggleExpanded() {
this.mouseIsDown = false;
Expand All @@ -72,4 +86,8 @@ export default {
padding: 0;
margin: 0;
}
.code {
max-height: 50em;
overflow: auto;
}
</style>
6 changes: 5 additions & 1 deletion client/src/components/JobInformation/JobInformation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ describe("JobInformation/JobInformation.vue", () => {
beforeEach(() => {
axiosMock = new MockAdapter(axios);
axiosMock.onGet(new RegExp(`api/configuration/decode/*`)).reply(200, { decoded_id: 123 });
axiosMock.onGet("/api/jobs/test_id?full=True").reply(200, jobResponse);
axiosMock
.onGet(
"/api/jobs/test_id?full=True&stdout_position=0&stdout_length=50000&stderr_position=0&stderr_length=50000"
)
.reply(200, jobResponse);
});

afterEach(() => {
Expand Down
28 changes: 25 additions & 3 deletions client/src/components/JobInformation/JobInformation.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
<template>
<div>
<JobDetailsProvider auto-refresh :job-id="job_id" @update:result="updateJob" />
<JobDetailsProvider
auto-refresh
:job-id="job_id"
:stdout_position="stdout_position"
:stdout_length="stdout_length"
:stderr_position="stderr_position"
:stderr_length="stderr_length"
@update:result="updateJob" />
<h2 class="h-md">Job Information</h2>
<table id="job-information" class="tabletip info_data_table">
<tbody>
Expand Down Expand Up @@ -41,8 +48,8 @@
</td>
</tr>
<CodeRow v-if="job" id="command-line" :code-label="'Command Line'" :code-item="job.command_line" />
<CodeRow v-if="job" id="stdout" :code-label="'Tool Standard Output'" :code-item="job.tool_stdout" />
<CodeRow v-if="job" id="stderr" :code-label="'Tool Standard Error'" :code-item="job.tool_stderr" />
<CodeRow v-if="job" id="stdout" :code-label="'Tool Standard Output'" :code-item="stdout_text" />
<CodeRow v-if="job" id="stderr" :code-label="'Tool Standard Error'" :code-item="stderr_text" />
<CodeRow
v-if="job && job.traceback"
id="traceback"
Expand Down Expand Up @@ -108,6 +115,12 @@ export default {
data() {
return {
job: null,
stdout_position: 0,
stdout_length: 50000,
stdout_text: "",
stderr_position: 0,
stderr_length: 50000,
stderr_text: "",
};
},
computed: {
Expand All @@ -126,6 +139,15 @@ export default {
},
updateJob(job) {
this.job = job;
// Keep stdout in memory and only fetch new text via JobProvider
if (job.tool_stdout != null) {
this.stdout_text += job.tool_stdout;
this.stdout_position += job.tool_stdout.length;
}
if (job.tool_stderr != null) {
this.stderr_text += job.tool_stderr;
this.stderr_position += job.tool_stderr.length;
}
},
},
};
Expand Down
6 changes: 4 additions & 2 deletions client/src/components/providers/JobProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { rethrowSimple } from "utils/simple-error";

import { cleanPaginationParameters, stateIsTerminal } from "./utils";

async function jobDetails({ jobId }) {
const url = `${getAppRoot()}api/jobs/${jobId}?full=True`;
async function jobDetails({ jobId, stdout_position = 0, stdout_length = 0, stderr_position = 0, stderr_length = 0 }) {
const url =
`${getAppRoot()}api/jobs/${jobId}?full=True&stdout_position=${stdout_position}` +
`&stdout_length=${stdout_length}&stderr_position=${stderr_position}&stderr_length=${stderr_length}`;
try {
const { data } = await axios.get(url);
return data;
Expand Down
33 changes: 32 additions & 1 deletion lib/galaxy/managers/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
date,
datetime,
)
from pathlib import Path

from boltons.iterutils import remap
from pydantic import (
Expand Down Expand Up @@ -72,6 +73,10 @@
log = logging.getLogger(__name__)


STDOUT_LOCATION = "outputs/tool_stdout"
STDERR_LOCATION = "outputs/tool_stderr"


class JobLock(BaseModel):
active: bool = Field(title="Job lock status", description="If active, jobs will not dispatch")

Expand Down Expand Up @@ -231,7 +236,9 @@ def update_job_lock(self, job_lock: JobLock):
)
return self.job_lock()

def get_accessible_job(self, trans, decoded_job_id):
def get_accessible_job(
self, trans, decoded_job_id, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0
):
job = trans.sa_session.query(trans.app.model.Job).filter(trans.app.model.Job.id == decoded_job_id).first()
if job is None:
raise ObjectNotFound()
Expand All @@ -248,6 +255,30 @@ def get_accessible_job(self, trans, decoded_job_id):
if not self.dataset_manager.is_accessible(data_assoc.dataset.dataset, trans.user):
raise ItemAccessibilityException("You are not allowed to rerun this job.")
trans.sa_session.refresh(job)

# If stdout and stderr parameters are legitimate values, then return with status.
if job.state == job.states.RUNNING:
working_directory = trans.app.object_store.get_filename(
job, base_dir="job_work", dir_only=True, obj_dir=True
)
if stdout_length > 0 and stdout_position > -1:
try:
stdout_path = Path(working_directory) / STDOUT_LOCATION
stdout_file = open(stdout_path)
stdout_file.seek(stdout_position)
job.job_stdout = stdout_file.read(stdout_length)
job.tool_stdout = job.job_stdout
except Exception as e:
log.error("Could not read STDOUT: %s", e)
if stderr_length > 0 and stderr_position > -1:
try:
stderr_path = Path(working_directory) / STDERR_LOCATION
stderr_file = open(stderr_path)
stderr_file.seek(stderr_position)
job.job_stderr = stderr_file.read(stderr_length)
job.tool_stderr = job.job_stderr
except Exception as e:
log.error("Could not read STDERR: %s", e)
return job

def stop(self, job, message=None):
Expand Down
6 changes: 5 additions & 1 deletion lib/galaxy/webapps/galaxy/api/job_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ def create(self, trans, job_id, payload, **kwargs):
target_dir = os.path.dirname(path)
util.safe_makedirs(target_dir)
try:
shutil.move(input_file.name, path)
if os.path.exists(path):
with open(path, "ab") as destination:
shutil.copyfileobj(open(input_file.name, "rb"), destination)
else:
shutil.move(input_file.name, path)
finally:
try:
input_file.close()
Expand Down
18 changes: 17 additions & 1 deletion lib/galaxy/webapps/galaxy/api/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,31 @@ def show(
id: DecodedDatabaseIdField,
trans: ProvidesUserContext = DependsOnTrans,
full: Optional[bool] = False,
stdout_position: Optional[int] = None,
stdout_length: Optional[int] = None,
stderr_position: Optional[int] = None,
stderr_length: Optional[int] = None,
) -> Dict[str, Any]:
"""
Return dictionary containing description of job data

Parameters
- id: ID of job to return
- full: Return extra information ?
- stdout_position: The index of the character to begin reading stdout from
- stdout_length: How many characters of stdout to read
- stderr_position: The index of the character to begin reading stderr from
- stderr_length: How many characters of stderr to read
Copy link
Member

Choose a reason for hiding this comment

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

Can you add two separate endpoints job for the jobs' stdout and stderr ?

"""
return self.service.show(trans, id, bool(full))
return self.service.show(
trans,
id,
bool(full),
int(stdout_position) if stdout_position else 0,
int(stdout_length) if stdout_length else 0,
int(stderr_position) if stderr_position else 0,
int(stderr_length) if stderr_length else 0,
)

@router.get("/api/jobs")
def index(
Expand Down
8 changes: 7 additions & 1 deletion lib/galaxy/webapps/galaxy/services/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,14 @@ def show(
trans: ProvidesUserContext,
id: DecodedDatabaseIdField,
full: bool = False,
stdout_position: int = 0,
stdout_length: int = 0,
stderr_position: int = 0,
stderr_length: int = 0,
) -> Dict[str, Any]:
job = self.job_manager.get_accessible_job(trans, id)
job = self.job_manager.get_accessible_job(
trans, id, stdout_position, stdout_length, stderr_position, stderr_length
)
return view_show_job(trans, job, bool(full))

def index(
Expand Down
Loading