Report a couple common errors in dataset/job report.

Report if empty inputs were used or if the same input was used more than once, these were identified as common sources of potential errors across many tools. Neither of these are definite problems, but both could be depending on the tool and both happen frequently. Outline a few more ideas as TODOs in the code.
This commit is contained in:
John Chilton
2019-04-16 10:24:28 -04:00
parent 4315f34586
commit 44856a3f75
4 changed files with 101 additions and 6 deletions
@@ -32,6 +32,7 @@ var View = Backbone.View.extend({
url: job_url,
success: job => {
this.render_error_page(dataset, job);
this.find_common_problems(job);
},
error: response => {
var error_response = {
@@ -56,6 +57,46 @@ var View = Backbone.View.extend({
});
},
find_common_problems: function(job) {
var job_url = `${getAppRoot()}api/jobs/${job.id}/common_problems`;
Utils.get({
url: job_url,
success: common_problems => {
this.render_common_problems(job, common_problems);
},
error: response => {
console.log("error");
console.log(response);
}
});
return;
},
render_common_problems: function(job, common_problems) {
const has_duplicate_inputs = common_problems.has_duplicate_inputs;
const has_empty_inputs = common_problems.has_empty_inputs;
if (has_duplicate_inputs || has_empty_inputs) {
const reportEl = this.$el.find(".common_problems");
reportEl.text("Detected Common Potential Problems");
if (has_empty_inputs) {
reportEl.after(`
<p>
The tool was executed with one or more empty input datasets, this isn't
always a problem and may not have been but frequently this is a source
of error.
<p>`);
}
if (has_duplicate_inputs) {
reportEl.after(`
<p>
The tool was executed with one or more duplicate input datasets, this isn't
always a problem and may not have been but frequently this is a source
of error.
<p>`);
}
}
},
/** Render the view */
render_error_page: function(dataset, job) {
this.$el.empty().append(`
@@ -88,23 +129,24 @@ var View = Backbone.View.extend({
const job_stderr = job.job_stderr;
const job_messages = job.job_messages;
if (!tool_stderr && !job_stderr && !job_messages) {
return '';
return '<h3 class="common_problems"></h3>';
}
var message = '<h3>Error Details</h3>';
var message = "<h3>Error Details</h3>";
if (job_messages) {
message += '<p>Execution resulted in the following messages:</p>';
message += "<p>Execution resulted in the following messages:</p>";
for (let job_message of job_messages) {
message += `<p><pre>${_.escape(job_message['desc'])}</pre></p>`;
message += `<p><pre>${_.escape(job_message["desc"])}</pre></p>`;
}
}
if (tool_stderr) {
message += '<p>Tool generated the following standard error:</p>';
message += "<p>Tool generated the following standard error:</p>";
message += `<pre class="code">${_.escape(tool_stderr)}</pre>`;
}
if (job_stderr) {
message += '<p>Galaxy job runner generated the following standard error:</p>';
message += "<p>Galaxy job runner generated the following standard error:</p>";
message += `<pre class="code">${_.escape(job_stderr)}</pre>`;
}
message += `<h3 class="common_problems"></h3>`;
return message;
},
+24
View File
@@ -167,6 +167,30 @@ class JobController(BaseAPIController, UsesLibraryMixinItems):
job_dict['job_metrics'] = [metric_to_dict(metric) for metric in job.metrics]
return job_dict
@expose_api
def common_problems(self, trans, id, **kwd):
"""
* GET /api/jobs/{id}/common_problems
check inputs and job for common potential problems to aid in error reporting
"""
job = self.__get_job(trans, id)
seen_ids = set()
has_empty_inputs = False
has_duplicate_inputs = False
for job_input_assoc in job.input_datasets:
input_dataset_instance = job_input_assoc.dataset
if input_dataset_instance.get_total_size() == 0:
has_empty_inputs = True
input_instance_id = input_dataset_instance.id
if input_instance_id in seen_ids:
has_duplicate_inputs = True
else:
seen_ids.add(input_instance_id)
# TODO: check percent of failing jobs around a window on job.update_time for handler - report if high.
# TODO: check percent of failing jobs around a window on job.update_time for destination_id - report if high.
# TODO: sniff inputs (add flag to allow checking files?)
return {"has_empty_inputs": has_empty_inputs, "has_duplicate_inputs": has_duplicate_inputs}
@expose_api
def inputs(self, trans, id, **kwd):
"""
+1
View File
@@ -888,6 +888,7 @@ def populate_api_routes(webapp, app):
webapp.mapper.connect('build_for_rerun', '/api/jobs/{id}/build_for_rerun', controller='jobs', action='build_for_rerun', conditions=dict(method=['GET']))
webapp.mapper.connect('resume', '/api/jobs/{id}/resume', controller='jobs', action='resume', conditions=dict(method=['PUT']))
webapp.mapper.connect('job_error', '/api/jobs/{id}/error', controller='jobs', action='error', conditions=dict(method=['POST']))
webapp.mapper.connect('common_problems', '/api/jobs/{id}/common_problems', controller='jobs', action='common_problems', conditions=dict(method=['GET']))
# Job files controllers. Only for consumption by remote job runners.
webapp.mapper.resource('file',
+28
View File
@@ -190,6 +190,34 @@ class JobsApiTestCase(api.ApiTestCase):
assert_ok=False)
assert not dataset['visible']
@skip_without_tool('empty_output')
def test_common_problems(self):
with self.dataset_populator.test_history() as history_id:
empty_run_response = self.dataset_populator.run_tool(
tool_id='empty_output',
inputs={},
history_id=history_id,
)
empty_hda = empty_run_response["outputs"][0]
cat_empty_twice_run_response = self.dataset_populator.run_tool(
tool_id='cat1',
inputs={
'input1': {'src': 'hda', 'id': empty_hda['id']},
'queries_0|input2': {'src': 'hda', 'id': empty_hda['id']}
},
history_id=history_id,
)
empty_output_job = empty_run_response["jobs"][0]
cat_empty_job = cat_empty_twice_run_response["jobs"][0]
empty_output_common_problems_response = self._get('jobs/%s/common_problems' % empty_output_job["id"]).json()
cat_empty_common_problems_response = self._get('jobs/%s/common_problems' % cat_empty_job["id"]).json()
self._assert_has_keys(empty_output_common_problems_response, "has_empty_inputs", "has_duplicate_inputs")
self._assert_has_keys(cat_empty_common_problems_response, "has_empty_inputs", "has_duplicate_inputs")
assert not empty_output_common_problems_response["has_empty_inputs"]
assert cat_empty_common_problems_response["has_empty_inputs"]
assert not empty_output_common_problems_response["has_duplicate_inputs"]
assert cat_empty_common_problems_response["has_duplicate_inputs"]
@skip_without_tool('detect_errors_aggressive')
def test_report_error(self):
with self.dataset_populator.test_history() as history_id: