From 26765720b972453bc69f23cd5644366bba998391 Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Fri, 8 Aug 2025 12:12:20 +0530 Subject: [PATCH] mgr/dashboard: fix memory leak in prometheus service Prometheus API calls in the Cluster Utilization call is subscribed in the for loop multiple times but this is not properly unsubscribed. As we stay in the dashboard page for longer time, it produces a significant memory leak which eventually lags the UI. Attempting to fix it by properly handling the subscription Fixes: https://tracker.ceph.com/issues/72511 Signed-off-by: Nizamudeen A --- .../src/app/shared/api/prometheus.service.ts | 72 +++++++++++-------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts index fedc7b8de0f..8e1151da13d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts @@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable, Subscription, forkJoin, timer } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { AlertmanagerSilence } from '../models/alertmanager-silence'; import { @@ -141,37 +141,47 @@ export class PrometheusService { if (this.timerGetPrometheusDataSub) { this.timerGetPrometheusDataSub.unsubscribe(); } - this.timerGetPrometheusDataSub = timer(0, this.timerTime).subscribe(() => { - selectedTime = this.updateTimeStamp(selectedTime); - for (const queryName in queries) { - if (queries.hasOwnProperty(queryName)) { - const query = queries[queryName]; - this.getPrometheusData({ - params: encodeURIComponent(query), - start: selectedTime['start'], - end: selectedTime['end'], - step: selectedTime['step'] - }).subscribe((data: any) => { - if (data.result.length) { - queriesResults[queryName] = data.result[0].values; - } else { - queriesResults[queryName] = []; - } - if ( - queriesResults[queryName] !== undefined && - queriesResults[queryName] !== '' && - checkNan - ) { - queriesResults[queryName].forEach((valueArray: any[]) => { - if (isNaN(parseFloat(valueArray[1]))) { - valueArray[1] = '0'; - } - }); + this.timerGetPrometheusDataSub = timer(0, this.timerTime) + .pipe( + switchMap(() => { + selectedTime = this.updateTimeStamp(selectedTime); + const observables = []; + for (const queryName in queries) { + if (queries.hasOwnProperty(queryName)) { + const query = queries[queryName]; + observables.push( + this.getPrometheusData({ + params: encodeURIComponent(query), + start: selectedTime['start'], + end: selectedTime['end'], + step: selectedTime['step'] + }).pipe(map((data: any) => ({ queryName, data }))) + ); } - }); - } - } - }); + } + return forkJoin(observables); + }) + ) + .subscribe((results: any) => { + results.forEach(({ queryName, data }: any) => { + if (data.result.length) { + queriesResults[queryName] = data.result[0].values; + } else { + queriesResults[queryName] = []; + } + if ( + queriesResults[queryName] !== undefined && + queriesResults[queryName] !== '' && + checkNan + ) { + queriesResults[queryName].forEach((valueArray: any[]) => { + if (isNaN(parseFloat(valueArray[1]))) { + valueArray[1] = '0'; + } + }); + } + }); + }); }); return queriesResults; } -- 2.39.5