This is an automated email from the ASF dual-hosted git repository.

xtsong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new 35e557afe9e [FLINK-28315][runtime-web] introduce aggregate stats in 
tables of the subtasks and taskmanagers
35e557afe9e is described below

commit 35e557afe9e8392b1b1ebb115f0c7134504b584d
Author: yangjunhan <[email protected]>
AuthorDate: Wed Jul 13 09:51:43 2022 +0800

    [FLINK-28315][runtime-web] introduce aggregate stats in tables of the 
subtasks and taskmanagers
    
    This closes #20260
---
 .../src/app/interfaces/job-vertex-task-manager.ts  |  55 ------
 .../web-dashboard/src/app/interfaces/job-vertex.ts | 106 +++++++++++
 .../web-dashboard/src/app/interfaces/public-api.ts |   3 +-
 .../modules/completed-job/completed-job.module.ts  |  18 --
 .../app/pages/job/overview/job-overview.config.ts  |   3 +
 .../app/pages/job/overview/job-overview.module.ts  |   4 +-
 .../job-overview-drawer-subtasks.component.html    | 181 ++++++++++--------
 .../job-overview-drawer-subtasks.component.less    |  21 +++
 .../job-overview-drawer-subtasks.component.ts      |  59 +++---
 .../subtasks-table-action.component.ts             |   6 +-
 .../taskmanagers-table-action.component.html       |  31 ++-
 .../taskmanagers-table-action.component.less       |   5 +
 .../taskmanagers-table-action.component.ts         |  10 +-
 .../web-dashboard/src/app/services/job.service.ts  |  12 +-
 .../table-aggregated-metrics.component.html        | 207 +++++++++++++++++++++
 .../table-aggregated-metrics.component.less}       |  30 ++-
 .../table-aggregated-metrics.component.ts}         |  19 +-
 .../duration-badge/duration-badge.component.html}  |  32 +---
 .../duration-badge/duration-badge.component.less}  |  23 +--
 .../duration-badge/duration-badge.component.ts}    |  28 +--
 .../src/app/share/pipes/humanize-bytes.pipe.ts     |   2 +-
 .../web-dashboard/src/app/share/share.module.ts    |  10 +-
 22 files changed, 587 insertions(+), 278 deletions(-)

diff --git 
a/flink-runtime-web/web-dashboard/src/app/interfaces/job-vertex-task-manager.ts 
b/flink-runtime-web/web-dashboard/src/app/interfaces/job-vertex-task-manager.ts
deleted file mode 100644
index 804037b2943..00000000000
--- 
a/flink-runtime-web/web-dashboard/src/app/interfaces/job-vertex-task-manager.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.
- */
-
-export interface JobVertexTaskManager {
-  id: string;
-  name: string;
-  now: number;
-  taskmanagers: VertexTaskManagerDetail[];
-}
-
-export interface VertexTaskManagerDetail {
-  host: string;
-  status: string;
-  'start-time': number;
-  'end-time': number;
-  duration: number;
-  metrics: {
-    'read-bytes': number;
-    'read-bytes-complete': boolean;
-    'write-bytes': number;
-    'write-bytes-complete': boolean;
-    'read-records': number;
-    'read-records-complete': boolean;
-    'write-records': number;
-    'write-records-complete': boolean;
-  };
-  'status-counts': {
-    CANCELED: number;
-    CANCELING: number;
-    CREATED: number;
-    DEPLOYING: number;
-    FAILED: number;
-    FINISHED: number;
-    RECONCILING: number;
-    RUNNING: number;
-    SCHEDULED: number;
-    INITIALIZING: number;
-  };
-  'taskmanager-id': string;
-}
diff --git a/flink-runtime-web/web-dashboard/src/app/interfaces/job-vertex.ts 
b/flink-runtime-web/web-dashboard/src/app/interfaces/job-vertex.ts
new file mode 100644
index 00000000000..f4b635fa9dc
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/src/app/interfaces/job-vertex.ts
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+interface JobVertexMetricFlags {
+  'read-bytes-complete': boolean;
+  'read-records-complete': boolean;
+  'write-bytes-complete': boolean;
+  'write-records-complete': boolean;
+}
+
+export interface JobVertexMetricStatistics<T> {
+  'read-bytes': T;
+  'read-records': T;
+  'write-bytes': T;
+  'write-records': T;
+  'accumulated-backpressured-time': T;
+  'accumulated-busy-time': T;
+  'accumulated-idle-time': T;
+}
+
+export interface AggregatedStatistics {
+  min: number;
+  max: number;
+  avg: number;
+  sum: number;
+  median: number;
+  p25: number;
+  p75: number;
+  p95: number;
+}
+
+export interface JobVertexStatusDuration<T> {
+  CREATED: T;
+  INITIALIZING: T;
+  RUNNING: T;
+  SCHEDULED: T;
+  DEPLOYING: T;
+}
+
+export interface JobVertexAggregated {
+  metrics: JobVertexMetricStatistics<AggregatedStatistics>;
+  'status-duration': JobVertexStatusDuration<AggregatedStatistics>;
+}
+
+export interface JobVertexSubTask {
+  attempt: number;
+  duration: number;
+  'end-time': number;
+  host: string;
+  start_time: number;
+  status: string;
+  subtask: number;
+  metrics: JobVertexMetricStatistics<number> & JobVertexMetricFlags;
+  'taskmanager-id': string;
+  'status-duration': JobVertexStatusDuration<number>;
+}
+
+export interface JobVertexSubTaskDetail {
+  subtasks: JobVertexSubTask[];
+  aggregated: JobVertexAggregated;
+}
+
+export interface JobVertexTaskManager {
+  id: string;
+  name: string;
+  now: number;
+  taskmanagers: VertexTaskManagerDetail[];
+}
+
+export interface VertexTaskManagerDetail {
+  duration: number;
+  host: string;
+  status: string;
+  'start-time': number;
+  'end-time': number;
+  'taskmanager-id': string;
+  metrics: JobVertexMetricStatistics<number> & JobVertexMetricFlags;
+  'status-counts': {
+    CANCELED: number;
+    CANCELING: number;
+    CREATED: number;
+    DEPLOYING: number;
+    FAILED: number;
+    FINISHED: number;
+    RECONCILING: number;
+    RUNNING: number;
+    SCHEDULED: number;
+    INITIALIZING: number;
+  };
+  aggregated: JobVertexAggregated;
+}
diff --git a/flink-runtime-web/web-dashboard/src/app/interfaces/public-api.ts 
b/flink-runtime-web/web-dashboard/src/app/interfaces/public-api.ts
index 202c93fa219..46aa79f35b5 100644
--- a/flink-runtime-web/web-dashboard/src/app/interfaces/public-api.ts
+++ b/flink-runtime-web/web-dashboard/src/app/interfaces/public-api.ts
@@ -23,9 +23,8 @@ export * from './job-detail';
 export * from './job-exception';
 export * from './job-timeline';
 export * from './job-config';
-export * from './job-vertex-task-manager';
+export * from './job-vertex';
 export * from './job-checkpoint';
-export * from './job-subtask';
 export * from './job-backpressure';
 export * from './job-flamegraph';
 export * from './plan';
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/modules/completed-job/completed-job.module.ts
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/modules/completed-job/completed-job.module.ts
index 688e0862de8..bbd4687e457 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/modules/completed-job/completed-job.module.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/modules/completed-job/completed-job.module.ts
@@ -21,10 +21,6 @@ import { NgModule } from '@angular/core';
 
 import { JOB_MODULE_CONFIG, JOB_MODULE_DEFAULT_CONFIG, JobModuleConfig } from 
'@flink-runtime-web/pages/job/job.config';
 import { CompletedJobRoutingModule } from 
'@flink-runtime-web/pages/job/modules/completed-job/completed-job-routing.module';
-import {
-  JOB_OVERVIEW_MODULE_CONFIG,
-  JobOverviewModuleConfig
-} from '@flink-runtime-web/pages/job/overview/job-overview.config';
 import { StatusService } from '@flink-runtime-web/services';
 import { ShareModule } from '@flink-runtime-web/share/share.module';
 import { NzAlertModule } from 'ng-zorro-antd/alert';
@@ -37,16 +33,6 @@ import { NzTableModule } from 'ng-zorro-antd/table';
 
 import { ClusterConfigComponent } from 
'./cluster-config/cluster-config.component';
 
-const OVERRIDE_JOB_OVERVIEW_MODULE_CONFIG_FACTORY = (): 
JobOverviewModuleConfig => {
-  return {
-    routerTabs: [
-      { title: 'Detail', path: 'detail' },
-      { title: 'SubTasks', path: 'subtasks' },
-      { title: 'TaskManagers', path: 'taskmanagers' }
-    ]
-  };
-};
-
 const OVERRIDE_JOB_MODULE_CONFIG_FACTORY = (statusService: StatusService): 
JobModuleConfig => {
   const isHistoryServer = statusService.configuration.features['web-history'];
   return {
@@ -78,10 +64,6 @@ const OVERRIDE_JOB_MODULE_CONFIG_FACTORY = (statusService: 
StatusService): JobMo
     NzPipesModule
   ],
   providers: [
-    {
-      provide: JOB_OVERVIEW_MODULE_CONFIG,
-      useFactory: OVERRIDE_JOB_OVERVIEW_MODULE_CONFIG_FACTORY
-    },
     {
       provide: JOB_MODULE_CONFIG,
       useFactory: OVERRIDE_JOB_MODULE_CONFIG_FACTORY,
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/job-overview.config.ts
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/job-overview.config.ts
index aadd61455fd..73d50f3fa13 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/job-overview.config.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/job-overview.config.ts
@@ -22,12 +22,14 @@ import { ModuleConfig } from 
'@flink-runtime-web/core/module-config';
 import { SubtasksTableActionComponent } from 
'@flink-runtime-web/pages/job/overview/subtasks/table-action/subtasks-table-action.component';
 import { TaskmanagersTableActionComponent } from 
'@flink-runtime-web/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component';
 import { BackpressureBadgeComponent } from 
'@flink-runtime-web/share/customize/backpressure-badge/backpressure-badge.component';
+import { DurationBadgeComponent } from 
'@flink-runtime-web/share/customize/duration-badge/duration-badge.component';
 import { JobBadgeComponent } from 
'@flink-runtime-web/share/customize/job-badge/job-badge.component';
 import { TaskBadgeComponent } from 
'@flink-runtime-web/share/customize/task-badge/task-badge.component';
 
 type customComponentKeys =
   | 'taskManagerActionComponent'
   | 'subtaskActionComponent'
+  | 'durationBadgeComponent'
   | 'stateBadgeComponent'
   | 'taskCountBadgeComponent'
   | 'backpressureBadgeComponent';
@@ -51,6 +53,7 @@ export const JOB_OVERVIEW_MODULE_DEFAULT_CONFIG: 
Required<JobOverviewModuleConfi
   customComponents: {
     taskManagerActionComponent: TaskmanagersTableActionComponent,
     subtaskActionComponent: SubtasksTableActionComponent,
+    durationBadgeComponent: DurationBadgeComponent,
     stateBadgeComponent: JobBadgeComponent,
     taskCountBadgeComponent: TaskBadgeComponent,
     backpressureBadgeComponent: BackpressureBadgeComponent
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/job-overview.module.ts
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/job-overview.module.ts
index 08d1e4eac25..6957cd98441 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/job-overview.module.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/job-overview.module.ts
@@ -26,6 +26,7 @@ import { NzDividerModule } from 'ng-zorro-antd/divider';
 import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
 import { NzFormModule } from 'ng-zorro-antd/form';
 import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzModalModule } from 'ng-zorro-antd/modal';
 import { NzRadioModule } from 'ng-zorro-antd/radio';
 import { NzSelectModule } from 'ng-zorro-antd/select';
 import { NzSpinModule } from 'ng-zorro-antd/spin';
@@ -62,7 +63,8 @@ import { JobOverviewDrawerWatermarksComponent } from 
'./watermarks/job-overview-
     NzSelectModule,
     NzDividerModule,
     NzTabsModule,
-    NzDropDownModule
+    NzDropDownModule,
+    NzModalModule
   ],
   declarations: [
     JobOverviewComponent,
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.html
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.html
index 9dc5cabf439..861b9a17ad7 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.html
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.html
@@ -15,84 +15,113 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<nz-table
-  nzSize="small"
-  [nzLoading]="isLoading"
-  [nzData]="listOfTask"
-  [nzScroll]="{ x: '1530px', y: 'calc( 100% - 36px )' }"
-  [nzFrontPagination]="false"
-  [nzShowPagination]="false"
-  [nzVirtualItemSize]="virtualItemSize"
-  [nzVirtualMinBufferPx]="300"
-  [nzVirtualMaxBufferPx]="300"
-  [nzVirtualForTrackBy]="trackBySubtask"
->
-  <thead>
-    <tr>
-      <th nzWidth="80px" nzLeft>ID</th>
-      <th [nzSortFn]="sortReadBytesFn" nzWidth="140px">Bytes Received</th>
-      <th [nzSortFn]="sortReadRecordsFn" nzWidth="150px">Records Received</th>
-      <th [nzSortFn]="sortWriteBytesFn" nzWidth="120px">Bytes Sent</th>
-      <th [nzSortFn]="sortWriteRecordsFn" nzWidth="120px">Records Sent</th>
-      <th [nzSortFn]="sortAttemptFn" nzWidth="100px">Attempt</th>
-      <th [nzSortFn]="sortHostFn" nzWidth="200px">Host</th>
-      <th [nzSortFn]="sortStartTimeFn" nzWidth="150px">Start Time</th>
-      <th [nzSortFn]="sortDurationFn" nzWidth="150px">Duration</th>
-      <th [nzSortFn]="sortEndTimeFn" nzWidth="150px">End Time</th>
-      <th [nzSortFn]="sortStatusFn" nzWidth="120px" nzRight>Status</th>
-      <th nzWidth="50px" nzRight>More</th>
-    </tr>
-  </thead>
-  <tbody>
-    <ng-template nz-virtual-scroll let-data>
-      <ng-container *ngIf="narrowLogData(data) as subtask">
+<nz-tabset [nzTabBarGutter]="16" [nzSize]="'small'">
+  <nz-tab nzTitle="Aggregated Metrics">
+    <flink-table-aggregated-metrics
+      [isLoading]="isLoading"
+      [aggregated]="aggregated"
+    ></flink-table-aggregated-metrics>
+  </nz-tab>
+  <nz-tab nzTitle="Subtask Metrics">
+    <nz-table
+      nzSize="small"
+      [nzLoading]="isLoading"
+      [nzData]="listOfTask"
+      [nzScroll]="{ x: '2170px', y: 'calc(100% - 36px)' }"
+      [nzFrontPagination]="false"
+      [nzShowPagination]="false"
+      [nzVirtualItemSize]="virtualItemSize"
+      [nzVirtualMinBufferPx]="300"
+      [nzVirtualMaxBufferPx]="300"
+      [nzVirtualForTrackBy]="trackBySubtask"
+    >
+      <thead>
         <tr>
-          <td nzLeft>
-            {{ subtask['subtask'] }}
-          </td>
-          <td>
-            <span *ngIf="subtask['metrics']['read-bytes-complete']; else 
loadingTemplate">
-              {{ subtask['metrics']['read-bytes'] | humanizeBytes }}
-            </span>
-          </td>
-          <td>
-            <span *ngIf="subtask['metrics']['read-records-complete']; else 
loadingTemplate">
-              {{ subtask['metrics']['read-records'] | number: '1.0-0' }}
-            </span>
-          </td>
-          <td>
-            <span *ngIf="subtask['metrics']['write-bytes-complete']; else 
loadingTemplate">
-              {{ subtask['metrics']['write-bytes'] | humanizeBytes }}
-            </span>
-          </td>
-          <td>
-            <span *ngIf="subtask['metrics']['write-records-complete']; else 
loadingTemplate">
-              {{ subtask['metrics']['write-records'] | number: '1.0-0' }}
-            </span>
-          </td>
-          <td>{{ subtask.attempt + 1 }}</td>
-          <td>{{ subtask.host }}</td>
-          <td>{{ subtask['start_time'] | humanizeDate: 'yyyy-MM-dd HH:mm:ss' 
}}</td>
-          <td>{{ subtask.duration | humanizeDuration }}</td>
-          <td>{{ subtask['end-time'] | humanizeDate: 'yyyy-MM-dd HH:mm:ss' 
}}</td>
-          <td nzRight>
-            <flink-dynamic-host
-              [data]="{ state: subtask['status'] }"
-              [component]="stateBadgeComponent"
-            ></flink-dynamic-host>
-          </td>
-          <td nzRight>
-            <flink-dynamic-host
-              [data]="{ subtask }"
-              [component]="actionComponent"
-            ></flink-dynamic-host>
-          </td>
+          <th nzWidth="80px" nzLeft>ID</th>
+          <th [nzSortFn]="sortReadBytesFn" nzWidth="140px">Bytes Received</th>
+          <th [nzSortFn]="sortReadRecordsFn" nzWidth="150px">Records 
Received</th>
+          <th [nzSortFn]="sortWriteBytesFn" nzWidth="120px">Bytes Sent</th>
+          <th [nzSortFn]="sortWriteRecordsFn" nzWidth="120px">Records Sent</th>
+          <th [nzSortFn]="sortAttemptFn" nzWidth="100px">Attempt</th>
+          <th [nzSortFn]="sortHostFn" nzWidth="200px">Host</th>
+          <th [nzSortFn]="sortStartTimeFn" nzWidth="150px">Start Time</th>
+          <th [nzSortFn]="sortDurationFn" nzWidth="150px">Duration</th>
+          <th [nzSortFn]="sortEndTimeFn" nzWidth="150px">End Time</th>
+          <th nzWidth="320px">Accumulated Time (Backpressured/Idle/Busy)</th>
+          <th nzWidth="320px">Status Durations</th>
+          <th [nzSortFn]="sortStatusFn" nzWidth="120px" nzRight>Status</th>
+          <th nzWidth="50px" nzRight>More</th>
         </tr>
-      </ng-container>
-    </ng-template>
-  </tbody>
-</nz-table>
+      </thead>
+      <tbody>
+        <ng-template nz-virtual-scroll let-data>
+          <ng-container *ngIf="narrowLogData(data) as subtask">
+            <tr>
+              <td nzLeft>
+                {{ subtask['subtask'] }}
+              </td>
+              <td>
+                <span *ngIf="subtask['metrics']['read-bytes-complete']; else 
loadingTemplate">
+                  {{ subtask['metrics']['read-bytes'] | humanizeBytes }}
+                </span>
+              </td>
+              <td>
+                <span *ngIf="subtask['metrics']['read-records-complete']; else 
loadingTemplate">
+                  {{ subtask['metrics']['read-records'] | number: '1.0-0' }}
+                </span>
+              </td>
+              <td>
+                <span *ngIf="subtask['metrics']['write-bytes-complete']; else 
loadingTemplate">
+                  {{ subtask['metrics']['write-bytes'] | humanizeBytes }}
+                </span>
+              </td>
+              <td>
+                <span *ngIf="subtask['metrics']['write-records-complete']; 
else loadingTemplate">
+                  {{ subtask['metrics']['write-records'] | number: '1.0-0' }}
+                </span>
+              </td>
+              <td>{{ subtask.attempt + 1 }}</td>
+              <td>{{ subtask.host }}</td>
+              <td>{{ subtask['start_time'] | humanizeDate: 'yyyy-MM-dd 
HH:mm:ss' }}</td>
+              <td>{{ subtask.duration | humanizeDuration }}</td>
+              <td>{{ subtask['end-time'] | humanizeDate: 'yyyy-MM-dd HH:mm:ss' 
}}</td>
+              <td>
+                {{ subtask['metrics']['accumulated-backpressured-time'] | 
humanizeDuration }}
+                /
+                {{ subtask['metrics']['accumulated-idle-time'] | 
humanizeDuration }}
+                /
+                {{ subtask['metrics']['accumulated-busy-time'] | 
humanizeDuration }}
+              </td>
+              <td>
+                <ng-container *ngIf="!subtask['status-duration']; else 
badges">-</ng-container>
+                <ng-template #badges>
+                  <flink-dynamic-host
+                    *ngFor="let duration of 
convertStatusDuration(subtask['status-duration'])"
+                    [data]="{ state: duration.key, duration: duration.value }"
+                    [component]="durationBadgeComponent"
+                  ></flink-dynamic-host>
+                </ng-template>
+              </td>
+              <td nzRight>
+                <flink-dynamic-host
+                  [data]="{ state: subtask['status'] }"
+                  [component]="stateBadgeComponent"
+                ></flink-dynamic-host>
+              </td>
+              <td nzRight>
+                <flink-dynamic-host
+                  [data]="{ subtask }"
+                  [component]="actionComponent"
+                ></flink-dynamic-host>
+              </td>
+            </tr>
+          </ng-container>
+        </ng-template>
+      </tbody>
+    </nz-table>
+  </nz-tab>
+</nz-tabset>
+
 <ng-template #loadingTemplate>
   <i nz-icon nzType="loading"></i>
 </ng-template>
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.less
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.less
index 00e8cda7c6c..d17ec2fae0a 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.less
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.less
@@ -22,11 +22,32 @@
   width: 100%;
   height: 100%;
 
+  nz-tabset {
+    height: 100%;
+  }
+
   ::ng-deep {
     .ant-table-cell {
       font-size: @font-size-sm;
     }
 
+    .ant-tabs-content {
+      height: 100%;
+
+      .ant-tabs-tabpane {
+        height: 100%;
+      }
+    }
+
+    .ant-tabs-nav {
+      margin-bottom: 0;
+    }
+
+    .ant-tabs-small > .ant-tabs-nav .ant-tabs-tab {
+      margin: 0 0 0 @margin-md;
+      font-size: @font-size-sm;
+    }
+
     ::-webkit-scrollbar {
       display: none;
     }
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.ts
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.ts
index bedba3c8cf8..21d4db49aaa 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/job-overview-drawer-subtasks.component.ts
@@ -20,7 +20,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, 
Component, Inject, OnDestro
 import { of, Subject } from 'rxjs';
 import { catchError, mergeMap, takeUntil } from 'rxjs/operators';
 
-import { JobSubTask } from '@flink-runtime-web/interfaces';
+import { JobVertexAggregated, JobVertexStatusDuration, JobVertexSubTask } from 
'@flink-runtime-web/interfaces';
 import {
   JOB_OVERVIEW_MODULE_CONFIG,
   JOB_OVERVIEW_MODULE_DEFAULT_CONFIG,
@@ -32,7 +32,7 @@ import { NzTableSortFn } from 
'ng-zorro-antd/table/src/table.types';
 
 import { JobLocalService } from '../../job-local.service';
 
-function createSortFn(selector: (item: JobSubTask) => number | string): 
NzTableSortFn<JobSubTask> {
+function createSortFn(selector: (item: JobVertexSubTask) => number | string): 
NzTableSortFn<JobVertexSubTask> {
   return (pre, next) => (selector(pre) > selector(next) ? 1 : -1);
 }
 
@@ -43,25 +43,27 @@ function createSortFn(selector: (item: JobSubTask) => 
number | string): NzTableS
   changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class JobOverviewDrawerSubtasksComponent implements OnInit, OnDestroy {
-  public readonly trackBySubtask = (_: number, node: JobSubTask): number => 
node.subtask;
+  readonly trackBySubtask = (_: number, node: JobVertexSubTask): number => 
node.subtask;
 
-  public readonly sortReadBytesFn = createSortFn(item => 
item.metrics?.['read-bytes']);
-  public readonly sortReadRecordsFn = createSortFn(item => 
item.metrics?.['read-records']);
-  public readonly sortWriteBytesFn = createSortFn(item => 
item.metrics?.['write-bytes']);
-  public readonly sortWriteRecordsFn = createSortFn(item => 
item.metrics?.['write-records']);
-  public readonly sortAttemptFn = createSortFn(item => item.attempt);
-  public readonly sortHostFn = createSortFn(item => item.host);
-  public readonly sortStartTimeFn = createSortFn(item => item['start_time']);
-  public readonly sortDurationFn = createSortFn(item => item.duration);
-  public readonly sortEndTimeFn = createSortFn(item => item['end-time']);
-  public readonly sortStatusFn = createSortFn(item => item.status);
+  readonly sortReadBytesFn = createSortFn(item => 
item.metrics?.['read-bytes']);
+  readonly sortReadRecordsFn = createSortFn(item => 
item.metrics?.['read-records']);
+  readonly sortWriteBytesFn = createSortFn(item => 
item.metrics?.['write-bytes']);
+  readonly sortWriteRecordsFn = createSortFn(item => 
item.metrics?.['write-records']);
+  readonly sortAttemptFn = createSortFn(item => item.attempt);
+  readonly sortHostFn = createSortFn(item => item.host);
+  readonly sortStartTimeFn = createSortFn(item => item['start_time']);
+  readonly sortDurationFn = createSortFn(item => item.duration);
+  readonly sortEndTimeFn = createSortFn(item => item['end-time']);
+  readonly sortStatusFn = createSortFn(item => item.status);
 
-  public listOfTask: JobSubTask[] = [];
-  public isLoading = true;
-  public virtualItemSize = 36;
-  public actionComponent: Type<unknown>;
-  public stateBadgeComponent: Type<unknown>;
-  public readonly narrowLogData = typeDefinition<JobSubTask>();
+  listOfTask: JobVertexSubTask[] = [];
+  aggregated?: JobVertexAggregated;
+  isLoading = true;
+  virtualItemSize = 36;
+  actionComponent: Type<unknown>;
+  durationBadgeComponent: Type<unknown>;
+  stateBadgeComponent: Type<unknown>;
+  readonly narrowLogData = typeDefinition<JobVertexSubTask>();
 
   private readonly destroy$ = new Subject<void>();
 
@@ -74,29 +76,40 @@ export class JobOverviewDrawerSubtasksComponent implements 
OnInit, OnDestroy {
     this.actionComponent =
       moduleConfig.customComponents?.subtaskActionComponent ||
       
JOB_OVERVIEW_MODULE_DEFAULT_CONFIG.customComponents.subtaskActionComponent;
+    this.durationBadgeComponent =
+      moduleConfig.customComponents?.durationBadgeComponent ||
+      
JOB_OVERVIEW_MODULE_DEFAULT_CONFIG.customComponents.durationBadgeComponent;
     this.stateBadgeComponent =
       moduleConfig.customComponents?.stateBadgeComponent ||
       JOB_OVERVIEW_MODULE_DEFAULT_CONFIG.customComponents.stateBadgeComponent;
   }
 
-  public ngOnInit(): void {
+  ngOnInit(): void {
     this.jobLocalService
       .jobWithVertexChanges()
       .pipe(
         mergeMap(data =>
-          this.jobService.loadSubTasks(data.job.jid, 
data.vertex!.id).pipe(catchError(() => of([] as JobSubTask[])))
+          this.jobService.loadSubTasks(data.job.jid, 
data.vertex!.id).pipe(catchError(() => of(undefined)))
         ),
         takeUntil(this.destroy$)
       )
       .subscribe(data => {
-        this.listOfTask = data;
+        this.listOfTask = data?.subtasks || [];
+        this.aggregated = data?.aggregated;
         this.isLoading = false;
         this.cdr.markForCheck();
       });
   }
 
-  public ngOnDestroy(): void {
+  ngOnDestroy(): void {
     this.destroy$.next();
     this.destroy$.complete();
   }
+
+  convertStatusDuration(duration: JobVertexStatusDuration<number>): Array<{ 
key: string; value: number }> {
+    return Object.keys(duration || {}).map(key => ({
+      key,
+      value: duration[key as keyof JobVertexStatusDuration<number>]
+    }));
+  }
 }
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
index 4db3407ed0a..069687b25c0 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
@@ -18,10 +18,10 @@
 
 import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
 
-import { JobSubTask } from '@flink-runtime-web/interfaces';
+import { JobVertexSubTask } from '@flink-runtime-web/interfaces';
 
 export interface JobOverviewSubtasksTableAction {
-  subtask?: JobSubTask;
+  subtask?: JobVertexSubTask;
 }
 
 @Component({
@@ -31,5 +31,5 @@ export interface JobOverviewSubtasksTableAction {
   changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class SubtasksTableActionComponent implements 
JobOverviewSubtasksTableAction {
-  @Input() subtask?: JobSubTask;
+  @Input() subtask?: JobVertexSubTask;
 }
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.html
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.html
index f2c24eb4072..66b93bc6efb 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.html
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.html
@@ -16,16 +16,7 @@
   ~ limitations under the License.
   -->
 
-<span
-  *ngIf="
-    !taskManager ||
-      !taskManager['taskmanager-id'] ||
-      taskManager['taskmanager-id'] === '(unassigned)';
-    else hrefTpl
-  "
->
-  -
-</span>
+<span *ngIf="!taskManager; else hrefTpl">-</span>
 <ng-template #hrefTpl>
   <a nz-dropdown nzPlacement="bottomRight" [nzDropdownMenu]="menu">
     <i nz-icon nzType="ellipsis" nzTheme="outline"></i>
@@ -33,6 +24,26 @@
   <nz-dropdown-menu #menu="nzDropdownMenu">
     <ul nz-menu>
       <li nz-menu-item>
+        <a (click)="setModalVisible(true)">View Aggregated Metrics</a>
+        <nz-modal
+          [nzTitle]="taskManager['taskmanager-id']"
+          [(nzVisible)]="visible"
+          [nzFooter]="null"
+          [nzStyle]="{ top: '40px' }"
+          [nzWidth]="900"
+          (nzOnCancel)="setModalVisible(false)"
+        >
+          <ng-container *nzModalContent>
+            <flink-table-aggregated-metrics
+              [aggregated]="taskManager.aggregated"
+            ></flink-table-aggregated-metrics>
+          </ng-container>
+        </nz-modal>
+      </li>
+      <li
+        nz-menu-item
+        *ngIf="taskManager['taskmanager-id'] && taskManager['taskmanager-id'] 
!== '(unassigned)'"
+      >
         <a
           [routerLink]="['/task-manager', taskManager['taskmanager-id'], 
'logs']"
           [queryParamsHandling]="'preserve'"
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.less
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.less
index 2379ddac12c..c1887ad4f71 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.less
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.less
@@ -15,3 +15,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@import "theme";
+
+:host {
+  display: block;
+}
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.ts
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.ts
index e004b7f6a1e..c807b3778ce 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.ts
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
+import { Component, ChangeDetectionStrategy, Input, ChangeDetectorRef } from 
'@angular/core';
 
 import { VertexTaskManagerDetail } from '@flink-runtime-web/interfaces';
 
@@ -32,4 +32,12 @@ export interface JobOverviewTaskManagersTableAction {
 })
 export class TaskmanagersTableActionComponent implements 
JobOverviewTaskManagersTableAction {
   @Input() taskManager?: VertexTaskManagerDetail;
+  visible = false;
+
+  constructor(private cdr: ChangeDetectorRef) {}
+
+  setModalVisible(visible: boolean): void {
+    this.visible = visible;
+    this.cdr.markForCheck();
+  }
 }
diff --git a/flink-runtime-web/web-dashboard/src/app/services/job.service.ts 
b/flink-runtime-web/web-dashboard/src/app/services/job.service.ts
index 0a460e78602..e8266198b85 100644
--- a/flink-runtime-web/web-dashboard/src/app/services/job.service.ts
+++ b/flink-runtime-web/web-dashboard/src/app/services/job.service.ts
@@ -35,14 +35,14 @@ import {
   JobFlameGraph,
   JobOverview,
   JobsItem,
-  JobSubTask,
   JobSubTaskTime,
   JobVertexTaskManager,
   NodesItemCorrect,
   SubTaskAccumulators,
   TaskStatus,
   UserAccumulators,
-  VerticesLink
+  VerticesLink,
+  JobVertexSubTaskDetail
 } from '@flink-runtime-web/interfaces';
 
 import { ConfigService } from './config.service';
@@ -122,10 +122,10 @@ export class JobService {
     );
   }
 
-  public loadSubTasks(jobId: string, vertexId: string): 
Observable<JobSubTask[]> {
-    return this.httpClient
-      .get<{ subtasks: JobSubTask[] 
}>(`${this.configService.BASE_URL}/jobs/${jobId}/vertices/${vertexId}`)
-      .pipe(map(data => (data && data.subtasks) || []));
+  public loadSubTasks(jobId: string, vertexId: string): 
Observable<JobVertexSubTaskDetail> {
+    return this.httpClient.get<JobVertexSubTaskDetail>(
+      `${this.configService.BASE_URL}/jobs/${jobId}/vertices/${vertexId}`
+    );
   }
 
   public loadSubTaskTimes(jobId: string, vertexId: string): 
Observable<JobSubTaskTime> {
diff --git 
a/flink-runtime-web/web-dashboard/src/app/share/common/table-aggregated-metrics/table-aggregated-metrics.component.html
 
b/flink-runtime-web/web-dashboard/src/app/share/common/table-aggregated-metrics/table-aggregated-metrics.component.html
new file mode 100644
index 00000000000..5b2964ad4f6
--- /dev/null
+++ 
b/flink-runtime-web/web-dashboard/src/app/share/common/table-aggregated-metrics/table-aggregated-metrics.component.html
@@ -0,0 +1,207 @@
+<!--
+  ~ 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.
+  -->
+
+<nz-table
+  class="small"
+  nzTemplateMode
+  nzSize="small"
+  [nzLoading]="isLoading"
+  [nzScroll]="{ x: '1180px', y: 'calc(100% - 36px)' }"
+  [nzFrontPagination]="false"
+  [nzShowPagination]="false"
+>
+  <thead>
+    <tr>
+      <th nzWidth="220px" nzLeft>Metric</th>
+      <th nzWidth="120px">Min</th>
+      <th nzWidth="120px">Max</th>
+      <th nzWidth="120px">Avg</th>
+      <th nzWidth="120px">Sum</th>
+      <th nzWidth="120px">Median</th>
+      <th nzWidth="120px">25th Percentile</th>
+      <th nzWidth="120px">75th Percentile</th>
+      <th nzWidth="120px">95th Percentile</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td nzLeft>INITIALIZING Duration</td>
+      <td>{{ aggregated?.['status-duration'].INITIALIZING.min | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.['status-duration'].INITIALIZING.max | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.['status-duration'].INITIALIZING.avg | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.['status-duration'].INITIALIZING.sum | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.['status-duration'].INITIALIZING.median | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.['status-duration'].INITIALIZING.p25 | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.['status-duration'].INITIALIZING.p75 | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.['status-duration'].INITIALIZING.p95 | 
humanizeDuration }}</td>
+    </tr>
+    <tr>
+      <td nzLeft>CREATED Duration</td>
+      <td>{{ aggregated?.['status-duration'].CREATED.min | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].CREATED.max | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].CREATED.avg | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].CREATED.sum | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].CREATED.median | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].CREATED.p25 | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].CREATED.p75 | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].CREATED.p95 | humanizeDuration 
}}</td>
+    </tr>
+    <tr>
+      <td nzLeft>SCHEDULED Duration</td>
+      <td>{{ aggregated?.['status-duration'].SCHEDULED.min | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].SCHEDULED.max | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].SCHEDULED.avg | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].SCHEDULED.sum | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].SCHEDULED.median | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.['status-duration'].SCHEDULED.p25 | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].SCHEDULED.p75 | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].SCHEDULED.p95 | humanizeDuration 
}}</td>
+    </tr>
+    <tr>
+      <td nzLeft>RUNNING Duration</td>
+      <td>{{ aggregated?.['status-duration'].RUNNING.min | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].RUNNING.max | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].RUNNING.avg | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].RUNNING.sum | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].RUNNING.median | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].RUNNING.p25 | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].RUNNING.p75 | humanizeDuration 
}}</td>
+      <td>{{ aggregated?.['status-duration'].RUNNING.p95 | humanizeDuration 
}}</td>
+    </tr>
+    <tr>
+      <td nzLeft>DEPLOYING Duration</td>
+      <td>
+        {{ aggregated?.['status-duration'].DEPLOYING.min | humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.['status-duration'].DEPLOYING.max | humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.['status-duration'].DEPLOYING.avg | humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.['status-duration'].DEPLOYING.sum | humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.['status-duration'].DEPLOYING.median | humanizeDuration 
}}
+      </td>
+      <td>
+        {{ aggregated?.['status-duration'].DEPLOYING.p25 | humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.['status-duration'].DEPLOYING.p75 | humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.['status-duration'].DEPLOYING.p95 | humanizeDuration }}
+      </td>
+    </tr>
+    <tr>
+      <td nzLeft>Read Records</td>
+      <td>{{ (aggregated?.metrics['read-records'].min | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['read-records'].max | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['read-records'].avg | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['read-records'].sum | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['read-records'].median | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['read-records'].p25 | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['read-records'].p75 | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['read-records'].p95 | number: '1.0-0') || 
'-' }}</td>
+    </tr>
+    <tr>
+      <td nzLeft>Write Records</td>
+      <td>{{ (aggregated?.metrics['write-records'].min | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['write-records'].max | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['write-records'].avg | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['write-records'].sum | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['write-records'].median | number: '1.0-0') 
|| '-' }}</td>
+      <td>{{ (aggregated?.metrics['write-records'].p25 | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['write-records'].p75 | number: '1.0-0') || 
'-' }}</td>
+      <td>{{ (aggregated?.metrics['write-records'].p95 | number: '1.0-0') || 
'-' }}</td>
+    </tr>
+    <tr>
+      <td nzLeft>Read Bytes</td>
+      <td>{{ aggregated?.metrics['read-bytes'].min | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['read-bytes'].max | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['read-bytes'].avg | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['read-bytes'].sum | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['read-bytes'].median | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['read-bytes'].p25 | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['read-bytes'].p75 | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['read-bytes'].p95 | humanizeBytes }}</td>
+    </tr>
+    <tr>
+      <td nzLeft>Write Bytes</td>
+      <td>{{ aggregated?.metrics['write-bytes'].min | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['write-bytes'].max | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['write-bytes'].avg | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['write-bytes'].sum | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['write-bytes'].median | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['write-bytes'].p25 | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['write-bytes'].p75 | humanizeBytes }}</td>
+      <td>{{ aggregated?.metrics['write-bytes'].p95 | humanizeBytes }}</td>
+    </tr>
+    <tr>
+      <td nzLeft>Accumulated Backpressured Time</td>
+      <td>
+        {{ aggregated?.metrics['accumulated-backpressured-time'].min | 
humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.metrics['accumulated-backpressured-time'].max | 
humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.metrics['accumulated-backpressured-time'].avg | 
humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.metrics['accumulated-backpressured-time'].sum | 
humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.metrics['accumulated-backpressured-time'].median | 
humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.metrics['accumulated-backpressured-time'].p25 | 
humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.metrics['accumulated-backpressured-time'].p75 | 
humanizeDuration }}
+      </td>
+      <td>
+        {{ aggregated?.metrics['accumulated-backpressured-time'].p95 | 
humanizeDuration }}
+      </td>
+    </tr>
+    <tr>
+      <td nzLeft>Accumulated Idle Time</td>
+      <td>{{ aggregated?.metrics['accumulated-idle-time'].min | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-idle-time'].max | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-idle-time'].avg | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-idle-time'].sum | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-idle-time'].median | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-idle-time'].p25 | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-idle-time'].p75 | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-idle-time'].p95 | 
humanizeDuration }}</td>
+    </tr>
+    <tr>
+      <td nzLeft>Accumulated Busy Time</td>
+      <td>{{ aggregated?.metrics['accumulated-busy-time'].min | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-busy-time'].max | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-busy-time'].avg | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-busy-time'].sum | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-busy-time'].median | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-busy-time'].p25 | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-busy-time'].p75 | 
humanizeDuration }}</td>
+      <td>{{ aggregated?.metrics['accumulated-busy-time'].p95 | 
humanizeDuration }}</td>
+    </tr>
+  </tbody>
+</nz-table>
diff --git a/flink-runtime-web/web-dashboard/src/app/interfaces/job-subtask.ts 
b/flink-runtime-web/web-dashboard/src/app/share/common/table-aggregated-metrics/table-aggregated-metrics.component.less
similarity index 63%
rename from flink-runtime-web/web-dashboard/src/app/interfaces/job-subtask.ts
rename to 
flink-runtime-web/web-dashboard/src/app/share/common/table-aggregated-metrics/table-aggregated-metrics.component.less
index 1c8e0fcc514..08aa17292af 100644
--- a/flink-runtime-web/web-dashboard/src/app/interfaces/job-subtask.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/share/common/table-aggregated-metrics/table-aggregated-metrics.component.less
@@ -15,24 +15,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@import "theme";
 
-export interface JobSubTask {
-  attempt: number;
-  duration: number;
-  'end-time': number;
-  host: string;
-  start_time: number;
-  status: string;
-  subtask: number;
-  metrics: {
-    'read-bytes': number;
-    'read-bytes-complete': boolean;
-    'read-records': number;
-    'read-records-complete': boolean;
-    'write-bytes': number;
-    'write-bytes-complete': boolean;
-    'write-records': number;
-    'write-records-complete': boolean;
-  };
-  'taskmanager-id': string;
+:host {
+  display: block;
+  height: 100%;
+
+  nz-table,
+  nz-spin,
+  .ant-spin-container,
+  .ant-table {
+    height: 100%;
+  }
 }
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
 
b/flink-runtime-web/web-dashboard/src/app/share/common/table-aggregated-metrics/table-aggregated-metrics.component.ts
similarity index 70%
copy from 
flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
copy to 
flink-runtime-web/web-dashboard/src/app/share/common/table-aggregated-metrics/table-aggregated-metrics.component.ts
index 4db3407ed0a..1b61eef5490 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/share/common/table-aggregated-metrics/table-aggregated-metrics.component.ts
@@ -18,18 +18,17 @@
 
 import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
 
-import { JobSubTask } from '@flink-runtime-web/interfaces';
-
-export interface JobOverviewSubtasksTableAction {
-  subtask?: JobSubTask;
-}
+import { JobVertexAggregated } from '@flink-runtime-web/interfaces';
 
 @Component({
-  selector: 'flink-table-actions',
-  templateUrl: './subtasks-table-action.component.html',
-  styleUrls: ['./subtasks-table-action.component.less'],
+  selector: 'flink-table-aggregated-metrics',
+  templateUrl: './table-aggregated-metrics.component.html',
+  styleUrls: ['./table-aggregated-metrics.component.less'],
   changeDetection: ChangeDetectionStrategy.OnPush
 })
-export class SubtasksTableActionComponent implements 
JobOverviewSubtasksTableAction {
-  @Input() subtask?: JobSubTask;
+export class TableAggregatedMetricsComponent {
+  @Input() isLoading = false;
+  @Input() aggregated?: JobVertexAggregated;
+
+  constructor() {}
 }
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.html
 
b/flink-runtime-web/web-dashboard/src/app/share/customize/duration-badge/duration-badge.component.html
similarity index 56%
copy from 
flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.html
copy to 
flink-runtime-web/web-dashboard/src/app/share/customize/duration-badge/duration-badge.component.html
index f2c24eb4072..1ec91e59253 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/taskmanagers/table-action/taskmanagers-table-action.component.html
+++ 
b/flink-runtime-web/web-dashboard/src/app/share/customize/duration-badge/duration-badge.component.html
@@ -15,32 +15,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<span
-  *ngIf="
-    !taskManager ||
-      !taskManager['taskmanager-id'] ||
-      taskManager['taskmanager-id'] === '(unassigned)';
-    else hrefTpl
-  "
->
-  -
+<span nz-tooltip [nzTooltipTitle]="tooltip">
+  {{ duration | humanizeDuration }}
 </span>
-<ng-template #hrefTpl>
-  <a nz-dropdown nzPlacement="bottomRight" [nzDropdownMenu]="menu">
-    <i nz-icon nzType="ellipsis" nzTheme="outline"></i>
-  </a>
-  <nz-dropdown-menu #menu="nzDropdownMenu">
-    <ul nz-menu>
-      <li nz-menu-item>
-        <a
-          [routerLink]="['/task-manager', taskManager['taskmanager-id'], 
'logs']"
-          [queryParamsHandling]="'preserve'"
-          target="_blank"
-        >
-          View Taskmanager Log
-        </a>
-      </li>
-    </ul>
-  </nz-dropdown-menu>
+
+<ng-template #tooltip>
+  <span>{{ state }}: {{ duration | humanizeDuration }}</span>
 </ng-template>
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/task-manager/task-manager-local.service.ts
 
b/flink-runtime-web/web-dashboard/src/app/share/customize/duration-badge/duration-badge.component.less
similarity index 60%
rename from 
flink-runtime-web/web-dashboard/src/app/pages/task-manager/task-manager-local.service.ts
rename to 
flink-runtime-web/web-dashboard/src/app/share/customize/duration-badge/duration-badge.component.less
index 458bc3602fe..c2d8a536f6a 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/task-manager/task-manager-local.service.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/share/customize/duration-badge/duration-badge.component.less
@@ -16,20 +16,17 @@
  * limitations under the License.
  */
 
-import { Injectable } from '@angular/core';
-import { Observable, ReplaySubject } from 'rxjs';
+@import "theme";
 
-import { TaskManagerDetail } from '@flink-runtime-web/interfaces';
+:host {
+  display: inline-block;
+  margin: 0 1px;
 
-@Injectable()
-export class TaskManagerLocalService {
-  private readonly taskManagerDetail$ = new 
ReplaySubject<TaskManagerDetail>(1);
-
-  public taskManagerDetailChanges(): Observable<TaskManagerDetail> {
-    return this.taskManagerDetail$.asObservable();
-  }
-
-  public setTaskManagerDetail(detail: TaskManagerDetail): void {
-    this.taskManagerDetail$.next(detail);
+  span {
+    padding: 1px @padding-xss;
+    color: @text-color-inverse;
+    font-weight: 500;
+    text-align: center;
+    cursor: default;
   }
 }
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
 
b/flink-runtime-web/web-dashboard/src/app/share/customize/duration-badge/duration-badge.component.ts
similarity index 60%
copy from 
flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
copy to 
flink-runtime-web/web-dashboard/src/app/share/customize/duration-badge/duration-badge.component.ts
index 4db3407ed0a..d35568caefc 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/overview/subtasks/table-action/subtasks-table-action.component.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/share/customize/duration-badge/duration-badge.component.ts
@@ -18,18 +18,24 @@
 
 import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
 
-import { JobSubTask } from '@flink-runtime-web/interfaces';
-
-export interface JobOverviewSubtasksTableAction {
-  subtask?: JobSubTask;
-}
+import { ColorKey, ConfigService } from '@flink-runtime-web/services';
 
 @Component({
-  selector: 'flink-table-actions',
-  templateUrl: './subtasks-table-action.component.html',
-  styleUrls: ['./subtasks-table-action.component.less'],
-  changeDetection: ChangeDetectionStrategy.OnPush
+  selector: 'flink-duration-badge',
+  templateUrl: './duration-badge.component.html',
+  styleUrls: ['./duration-badge.component.less'],
+  changeDetection: ChangeDetectionStrategy.OnPush,
+  host: {
+    '[style.backgroundColor]': 'backgroundColor'
+  }
 })
-export class SubtasksTableActionComponent implements 
JobOverviewSubtasksTableAction {
-  @Input() subtask?: JobSubTask;
+export class DurationBadgeComponent {
+  @Input() public state: string;
+  @Input() public duration: number;
+
+  constructor(private readonly configService: ConfigService) {}
+
+  get backgroundColor(): string {
+    return this.configService.COLOR_MAP[this.state as ColorKey];
+  }
 }
diff --git 
a/flink-runtime-web/web-dashboard/src/app/share/pipes/humanize-bytes.pipe.ts 
b/flink-runtime-web/web-dashboard/src/app/share/pipes/humanize-bytes.pipe.ts
index cbaf6222630..da91323b328 100644
--- a/flink-runtime-web/web-dashboard/src/app/share/pipes/humanize-bytes.pipe.ts
+++ b/flink-runtime-web/web-dashboard/src/app/share/pipes/humanize-bytes.pipe.ts
@@ -26,7 +26,7 @@ import { isNil } from '@flink-runtime-web/utils';
 export class HumanizeBytesPipe implements PipeTransform {
   public transform(value: number): string {
     if (isNil(value) || isNaN(value) || value < 0) {
-      return '–';
+      return '-';
     }
 
     const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
diff --git a/flink-runtime-web/web-dashboard/src/app/share/share.module.ts 
b/flink-runtime-web/web-dashboard/src/app/share/share.module.ts
index 17288e4f9df..dfcf5f36954 100644
--- a/flink-runtime-web/web-dashboard/src/app/share/share.module.ts
+++ b/flink-runtime-web/web-dashboard/src/app/share/share.module.ts
@@ -28,6 +28,7 @@ import { FileReadDirective } from 
'@flink-runtime-web/share/common/file-read/fil
 import { NavigationComponent } from 
'@flink-runtime-web/share/common/navigation/navigation.component';
 import { ResizeComponent } from 
'@flink-runtime-web/share/common/resize/resize.component';
 import { JobStatusComponent } from 
'@flink-runtime-web/share/common/status/job-status.component';
+import { TableAggregatedMetricsComponent } from 
'@flink-runtime-web/share/common/table-aggregated-metrics/table-aggregated-metrics.component';
 import { BackpressureBadgeComponent } from 
'@flink-runtime-web/share/customize/backpressure-badge/backpressure-badge.component';
 import { CheckpointBadgeComponent } from 
'@flink-runtime-web/share/customize/checkpoint-badge/checkpoint-badge.component';
 import { JobBadgeComponent } from 
'@flink-runtime-web/share/customize/job-badge/job-badge.component';
@@ -49,6 +50,7 @@ import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
 
 import { ConfigurationCardsComponent } from 
'./common/configuration-cards/configuration-cards.component';
 import { TableDisplayComponent } from 
'./common/configuration-cards/table-display/table-display.component';
+import { DurationBadgeComponent } from 
'./customize/duration-badge/duration-badge.component';
 import { FlameGraphComponent } from 
'./customize/flame-graph/flame-graph.component';
 
 @NgModule({
@@ -85,7 +87,9 @@ import { FlameGraphComponent } from 
'./customize/flame-graph/flame-graph.compone
     FlameGraphComponent,
     JobStatusComponent,
     TableDisplayComponent,
-    ConfigurationCardsComponent
+    ConfigurationCardsComponent,
+    TableAggregatedMetricsComponent,
+    DurationBadgeComponent
   ],
   exports: [
     JobListComponent,
@@ -105,7 +109,9 @@ import { FlameGraphComponent } from 
'./customize/flame-graph/flame-graph.compone
     FlameGraphComponent,
     JobStatusComponent,
     TableDisplayComponent,
-    ConfigurationCardsComponent
+    ConfigurationCardsComponent,
+    TableAggregatedMetricsComponent,
+    DurationBadgeComponent
   ]
 })
 export class ShareModule {}

Reply via email to