diff --git a/flink-runtime-web/web-dashboard/src/app/interfaces/job-exception.ts b/flink-runtime-web/web-dashboard/src/app/interfaces/job-exception.ts index 79a86c31e705d..16b5b8bc22904 100644 --- a/flink-runtime-web/web-dashboard/src/app/interfaces/job-exception.ts +++ b/flink-runtime-web/web-dashboard/src/app/interfaces/job-exception.ts @@ -35,7 +35,7 @@ export interface JobExceptionItemInterface { } export interface JobExceptionHistoryInterface { - entries: ExceptionInfoInterface[]; + entries: RootExceptionInfoInterface[]; truncated: boolean; } @@ -46,3 +46,7 @@ export interface ExceptionInfoInterface { taskName: string; location: string; } + +export interface RootExceptionInfoInterface extends ExceptionInfoInterface { + concurrentExceptions: ExceptionInfoInterface[]; +} diff --git a/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.html b/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.html index 061adf47e08b1..2d3f36f728c84 100644 --- a/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.html +++ b/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.html @@ -24,7 +24,7 @@ @@ -32,31 +32,33 @@ Time Exception - Name + Task Name Location - + - - {{exception.timestamp | date:'yyyy-MM-dd HH:mm:ss'}} -
{{exception.exceptionName}}
- -
- {{exception.taskName || "(global failure)"}} -
+ + {{item.selected.timestamp | date:'yyyy-MM-dd HH:mm:ss'}} +
{{item.selected.exceptionName}}
+ + + + {{ ex.taskName }} + + - {{exception.location || "(unassigned)"}} + {{item.selected.location || "(unassigned)"}} - + - +
- - + +   The exception history is limited to the most recent failures that caused parts of the job or the entire job to restart. The maximum history size can be configured via the Flink configuration property web.exception-history-size. diff --git a/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.less b/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.less index 6865d3ad554a9..a44e8958ccfa2 100644 --- a/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.less +++ b/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.less @@ -40,3 +40,7 @@ flink-monaco-editor { .expand-td { padding: 0 !important; } + +.exception-select { + width: 300px; +} diff --git a/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.ts b/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.ts index 0513993390e8a..228d2690d9f73 100644 --- a/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.ts +++ b/flink-runtime-web/web-dashboard/src/app/pages/job/exceptions/job-exceptions.component.ts @@ -18,10 +18,40 @@ import { formatDate } from '@angular/common'; import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; -import { ExceptionInfoInterface } from 'interfaces'; +import { ExceptionInfoInterface, RootExceptionInfoInterface } from 'interfaces'; import { distinctUntilChanged, flatMap, tap } from 'rxjs/operators'; import { JobService } from 'services'; +interface ExceptionHistoryItem { + + /** + * List of concurrent exceptions that caused this failure. + */ + exceptions: ExceptionInfoInterface[]; + + /** + * An exception from the list, that is currently selected for rendering. + */ + selected: ExceptionInfoInterface; + + /** + * Should this failure be expanded in UI? + */ + expand: boolean; +} + +const stripConcurrentExceptions = function (rootException: RootExceptionInfoInterface): ExceptionInfoInterface { + const {concurrentExceptions, ...mainException} = rootException; + return mainException; +} + +const markGlobalFailure = function (exception: ExceptionInfoInterface) { + if (exception.taskName == null) { + exception.taskName = '(global failure)'; + } + return exception; +} + @Component({ selector: 'flink-job-exceptions', templateUrl: './job-exceptions.component.html', @@ -30,7 +60,7 @@ import { JobService } from 'services'; }) export class JobExceptionsComponent implements OnInit { rootException = ''; - listOfException: ExceptionInfoInterface[] = []; + exceptionHistory: ExceptionHistoryItem[] = []; truncated = false; isLoading = false; maxExceptions = 0; @@ -53,15 +83,22 @@ export class JobExceptionsComponent implements OnInit { ) .subscribe(data => { // @ts-ignore - var exceptionHistory = data.exceptionHistory + const exceptionHistory = data.exceptionHistory if (exceptionHistory.entries.length > 0) { - var mostRecentException = exceptionHistory.entries[0] + const mostRecentException = exceptionHistory.entries[0] this.rootException = formatDate(mostRecentException.timestamp, 'yyyy-MM-dd HH:mm:ss', 'en') + '\n' + mostRecentException.stacktrace; } else { this.rootException = 'No Root Exception'; } this.truncated = exceptionHistory.truncated; - this.listOfException = exceptionHistory.entries; + this.exceptionHistory = exceptionHistory.entries.map(entry => { + const values = [stripConcurrentExceptions(entry)].concat(entry.concurrentExceptions).map(markGlobalFailure); + return { + selected: values[0], + exceptions: values, + expand: false + }; + }); }); }