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

RocMarshal pushed a commit to branch release-2.3
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/release-2.3 by this push:
     new 0e79fa52518 [FLINK-38900][runtime-web] Introduce the Rescales/Summary 
sub-page for streaming jobs with the adaptive scheduler enabled (#27988)
0e79fa52518 is described below

commit 0e79fa52518300cf253885bf795af7aafd9fa09f
Author: Yuepeng Pan <[email protected]>
AuthorDate: Wed Apr 22 09:41:53 2026 +0800

    [FLINK-38900][runtime-web] Introduce the Rescales/Summary sub-page for 
streaming jobs with the adaptive scheduler enabled (#27988)
    
    (cherry picked from commit fe8507c1eea5e2bf4cca4dc315e3fd8acaf187a0)
    
    Co-authored-by: och5351 <[email protected]>
    Co-authored-by: Matthias Pohl <[email protected]>
---
 .../src/app/interfaces/job-rescales.ts             |  25 ++
 .../pages/job/rescales/job-rescales.component.html | 280 +++++++++++++++++++++
 .../pages/job/rescales/job-rescales.component.ts   |  22 +-
 .../web-dashboard/src/app/services/job.service.ts  |   5 +
 4 files changed, 330 insertions(+), 2 deletions(-)

diff --git a/flink-runtime-web/web-dashboard/src/app/interfaces/job-rescales.ts 
b/flink-runtime-web/web-dashboard/src/app/interfaces/job-rescales.ts
index 0cf7f521c36..2407d8cc819 100644
--- a/flink-runtime-web/web-dashboard/src/app/interfaces/job-rescales.ts
+++ b/flink-runtime-web/web-dashboard/src/app/interfaces/job-rescales.ts
@@ -21,6 +21,31 @@ export interface RescalesOverview {
   latest: LatestRescales;
 }
 
+export interface RescalesSummary {
+  rescalesCounts: RescalesCounts;
+  rescalesDurationStatsInMillis: RescalesDurationStatsInMillis;
+  completedRescalesDurationStatsInMillis: 
DetailedRescalesDurationStatsInMillis;
+  ignoredRescalesDurationStatsInMillis: DetailedRescalesDurationStatsInMillis;
+  failedRescalesDurationStatsInMillis: DetailedRescalesDurationStatsInMillis;
+}
+
+export interface RescalesDurationStatsInMillis {
+  min: number;
+  max: number;
+  avg: number;
+}
+
+export interface DetailedRescalesDurationStatsInMillis {
+  min: number;
+  max: number;
+  avg: number;
+  p50: number | string;
+  p90: number | string;
+  p95: number | string;
+  p99: number | string;
+  p999: number | string;
+}
+
 export interface RescalesCounts {
   ignored: number;
   inProgress: number;
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/rescales/job-rescales.component.html
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/rescales/job-rescales.component.html
index 5bc5c64f5de..8f4a85d75c6 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/rescales/job-rescales.component.html
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/rescales/job-rescales.component.html
@@ -466,6 +466,286 @@
       </tbody>
     </nz-table>
   </nz-tab>
+  <nz-tab nzTitle="Summary">
+    <nz-table
+      class="no-border small"
+      [nzData]="rescalesSummary ? [''] : []"
+      [nzSize]="'small'"
+      [nzFrontPagination]="false"
+      [nzShowPagination]="false"
+    >
+      <thead>
+        <tr>
+          <th><strong>Item</strong></th>
+          <th><strong>Stat</strong></th>
+        </tr>
+      </thead>
+      <tbody>
+        <ng-container *ngIf="rescalesSummary">
+          <tr>
+            <td>Total Rescales</td>
+            <td>{{ getTotalRescaleCountFromSummary() }}</td>
+          </tr>
+          <tr>
+            <td>In Progress Rescales</td>
+            <td>{{ rescalesSummary['rescalesCounts'].inProgress }}</td>
+          </tr>
+          <tr>
+            <td>Ignored Rescales</td>
+            <td>{{ rescalesSummary['rescalesCounts'].ignored }}</td>
+          </tr>
+          <tr>
+            <td>Completed Rescales</td>
+            <td>{{ rescalesSummary['rescalesCounts'].completed }}</td>
+          </tr>
+          <tr>
+            <td>Failed Rescales</td>
+            <td>{{ rescalesSummary['rescalesCounts'].failed }}</td>
+          </tr>
+          <tr>
+            <td>Rescale Duration Max</td>
+            <td>{{ rescalesSummary['rescalesDurationStatsInMillis'].max | 
humanizeDuration }}</td>
+          </tr>
+          <tr>
+            <td>Rescale Duration Min</td>
+            <td>{{ rescalesSummary['rescalesDurationStatsInMillis'].min | 
humanizeDuration }}</td>
+          </tr>
+          <tr>
+            <td>Rescale Duration Average</td>
+            <td>{{ rescalesSummary['rescalesDurationStatsInMillis'].avg | 
humanizeDuration }}</td>
+          </tr>
+        </ng-container>
+      </tbody>
+    </nz-table>
+
+    <nz-collapse>
+      <nz-collapse-panel
+        [nzHeader]="'Rescale Duration Percentiles'"
+        [nzActive]="moreDetailsPanel.active"
+        [nzDisabled]="moreDetailsPanel.disabled"
+      >
+        <nz-table
+          *ngIf="rescalesSummary"
+          class="no-border small"
+          [nzData]="rescalesSummary ? [''] : []"
+          [nzSize]="'small'"
+          [nzFrontPagination]="false"
+          [nzShowPagination]="false"
+        >
+          <thead>
+            <tr>
+              <th></th>
+              <th><strong>Completed Rescale Duration</strong></th>
+              <th><strong>Ignored Rescale Duration</strong></th>
+              <th><strong>Failed Rescale Duration</strong></th>
+            </tr>
+          </thead>
+          <tbody>
+            <ng-container
+              *ngIf="
+                rescalesSummary['completedRescalesDurationStatsInMillis'] &&
+                rescalesSummary['ignoredRescalesDurationStatsInMillis'] &&
+                rescalesSummary['failedRescalesDurationStatsInMillis']
+              "
+            >
+              <tr>
+                <td><strong>Min</strong></td>
+                <td>
+                  {{
+                    
rescalesSummary['completedRescalesDurationStatsInMillis']?.min
+                      | humanizeDuration
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['ignoredRescalesDurationStatsInMillis']?.min | humanizeDuration
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['failedRescalesDurationStatsInMillis']?.min | humanizeDuration
+                  }}
+                </td>
+              </tr>
+              <tr>
+                <td><strong>Max</strong></td>
+                <td>
+                  {{
+                    
rescalesSummary['completedRescalesDurationStatsInMillis']?.max
+                      | humanizeDuration
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['ignoredRescalesDurationStatsInMillis']?.max | humanizeDuration
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['failedRescalesDurationStatsInMillis']?.max | humanizeDuration
+                  }}
+                </td>
+              </tr>
+              <tr>
+                <td><strong>Average</strong></td>
+                <td>
+                  {{
+                    
rescalesSummary['completedRescalesDurationStatsInMillis']?.avg
+                      | humanizeDuration
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['ignoredRescalesDurationStatsInMillis']?.avg | humanizeDuration
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['failedRescalesDurationStatsInMillis']?.avg | humanizeDuration
+                  }}
+                </td>
+              </tr>
+              <tr>
+                <td><strong>50% percentile</strong></td>
+                <td>
+                  {{
+                    
rescalesSummary['completedRescalesDurationStatsInMillis']?.p50 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['completedRescalesDurationStatsInMillis']?.p50
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p50 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p50
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['failedRescalesDurationStatsInMillis']?.p50 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['failedRescalesDurationStatsInMillis']?.p50
+                        | humanizeDuration)
+                  }}
+                </td>
+              </tr>
+              <tr>
+                <td><strong>90% percentile</strong></td>
+                <td>
+                  {{
+                    
rescalesSummary['completedRescalesDurationStatsInMillis']?.p90 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['completedRescalesDurationStatsInMillis']?.p90
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p90 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p90
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['failedRescalesDurationStatsInMillis']?.p90 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['failedRescalesDurationStatsInMillis']?.p90
+                        | humanizeDuration)
+                  }}
+                </td>
+              </tr>
+              <tr>
+                <td><strong>95% percentile</strong></td>
+                <td>
+                  {{
+                    
rescalesSummary['completedRescalesDurationStatsInMillis']?.p95 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['completedRescalesDurationStatsInMillis']?.p95
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p95 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p95
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['failedRescalesDurationStatsInMillis']?.p95 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['failedRescalesDurationStatsInMillis']?.p95
+                        | humanizeDuration)
+                  }}
+                </td>
+              </tr>
+              <tr>
+                <td><strong>99% percentile</strong></td>
+                <td>
+                  {{
+                    
rescalesSummary['completedRescalesDurationStatsInMillis']?.p99 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['completedRescalesDurationStatsInMillis']?.p99
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p99 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p99
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['failedRescalesDurationStatsInMillis']?.p99 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['failedRescalesDurationStatsInMillis']?.p99
+                        | humanizeDuration)
+                  }}
+                </td>
+              </tr>
+              <tr>
+                <td><strong>99.9% percentile</strong></td>
+                <td>
+                  {{
+                    
rescalesSummary['completedRescalesDurationStatsInMillis']?.p999 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['completedRescalesDurationStatsInMillis']?.p999
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p999 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['ignoredRescalesDurationStatsInMillis']?.p999
+                        | humanizeDuration)
+                  }}
+                </td>
+                <td>
+                  {{
+                    
rescalesSummary['failedRescalesDurationStatsInMillis']?.p999 === 'NaN'
+                      ? 'n/a'
+                      : 
(rescalesSummary['failedRescalesDurationStatsInMillis']?.p999
+                        | humanizeDuration)
+                  }}
+                </td>
+              </tr>
+            </ng-container>
+          </tbody>
+        </nz-table>
+      </nz-collapse-panel>
+    </nz-collapse>
+  </nz-tab>
   <nz-tab nzTitle="Configuration">
     <nz-table
       class="no-border small"
diff --git 
a/flink-runtime-web/web-dashboard/src/app/pages/job/rescales/job-rescales.component.ts
 
b/flink-runtime-web/web-dashboard/src/app/pages/job/rescales/job-rescales.component.ts
index e25d8747d44..977b5de36dd 100644
--- 
a/flink-runtime-web/web-dashboard/src/app/pages/job/rescales/job-rescales.component.ts
+++ 
b/flink-runtime-web/web-dashboard/src/app/pages/job/rescales/job-rescales.component.ts
@@ -29,7 +29,8 @@ import {
   JobRescaleConfigInfo,
   JobDetail,
   RescalesHistory,
-  RescalesOverview
+  RescalesOverview,
+  RescalesSummary
 } from '@flink-runtime-web/interfaces';
 import { JobService } from '@flink-runtime-web/services';
 import { NzButtonModule } from 'ng-zorro-antd/button';
@@ -69,6 +70,7 @@ import { JobLocalService } from '../job-local.service';
 })
 export class JobRescalesComponent implements OnInit, OnDestroy {
   public rescalesOverview?: RescalesOverview;
+  public rescalesSummary?: RescalesSummary;
   public rescalesHistory?: RescalesHistory;
   public rescaleDetailsMap = new Map<string, JobRescaleDetails>();
   public rescalesConfig?: JobRescaleConfigInfo;
@@ -95,6 +97,11 @@ export class JobRescalesComponent implements OnInit, 
OnDestroy {
                 return of(undefined);
               })
             ),
+            this.jobService.loadRescalesSummary(this.jobDetail.jid).pipe(
+              catchError(() => {
+                return of(undefined);
+              })
+            ),
             this.jobService.loadRescalesHistory(this.jobDetail.jid).pipe(
               catchError(() => {
                 return of(undefined);
@@ -109,8 +116,9 @@ export class JobRescalesComponent implements OnInit, 
OnDestroy {
         ),
         takeUntil(this.destroy$)
       )
-      .subscribe(([overview, history, config]) => {
+      .subscribe(([overview, summary, history, config]) => {
         this.rescalesOverview = overview;
+        this.rescalesSummary = summary;
         this.rescalesHistory = history;
         this.rescalesConfig = config;
 
@@ -149,6 +157,8 @@ export class JobRescalesComponent implements OnInit, 
OnDestroy {
     this.refresh$.complete();
   }
 
+  public moreDetailsPanel = { active: false, disabled: false };
+
   public refresh(): void {
     const expandedUuids = Array.from(this.expandedRowsSet);
 
@@ -210,6 +220,14 @@ export class JobRescalesComponent implements OnInit, 
OnDestroy {
     return counts.inProgress + counts.completed + counts.failed + 
counts.ignored;
   }
 
+  public getTotalRescaleCountFromSummary(): number {
+    if (!this.rescalesSummary?.rescalesCounts) {
+      return 0;
+    }
+    const counts = this.rescalesSummary.rescalesCounts;
+    return counts.inProgress + counts.completed + counts.failed + 
counts.ignored;
+  }
+
   private loadRescaleDetailIfNeeded(rescaleUuid: string): void {
     if (!this.rescaleDetailsMap.has(rescaleUuid)) {
       this.jobService
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 a5bb8252e0b..4bcabff12ac 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
@@ -27,6 +27,7 @@ import {
   CheckpointDetail,
   CheckpointSubTask,
   RescalesOverview,
+  RescalesSummary,
   RescalesHistory,
   JobRescaleDetails,
   JobRescaleConfigInfo,
@@ -186,6 +187,10 @@ export class JobService {
     return 
this.httpClient.get<RescalesOverview>(`${this.configService.BASE_URL}/jobs/${jobId}/rescales/overview`);
   }
 
+  public loadRescalesSummary(jobId: string): Observable<RescalesSummary> {
+    return 
this.httpClient.get<RescalesSummary>(`${this.configService.BASE_URL}/jobs/${jobId}/rescales/summary`);
+  }
+
   public loadRescalesHistory(jobId: string): Observable<RescalesHistory> {
     return 
this.httpClient.get<RescalesHistory>(`${this.configService.BASE_URL}/jobs/${jobId}/rescales/history`);
   }

Reply via email to