[MM-60609][MM-60612] Include Desktop App metrics in PerformanceReporter, add metrics in Prometheus for CPU/Memory usage (#28825)

* [MM-60609][MM-60612] Include Desktop App metrics in PerformanceReporter, add metrics in Prometheus for CPU/Memory usage

* Fix mocks

* PR feedback
Этот коммит содержится в:
Devin Binnie
2024-10-22 12:16:20 -04:00
коммит произвёл GitHub
родитель 2d96053012
Коммит 0c90b0363b
12 изменённых файлов: 141 добавлений и 22 удалений

Просмотреть файл

@@ -13,7 +13,7 @@
"@mattermost/client": "*",
"@mattermost/compass-components": "^0.2.12",
"@mattermost/compass-icons": "0.1.39",
"@mattermost/desktop-api": "5.8.0-5",
"@mattermost/desktop-api": "5.10.0-2",
"@mattermost/types": "*",
"@mui/base": "5.0.0-alpha.127",
"@mui/material": "5.11.16",

Просмотреть файл

@@ -6,6 +6,7 @@ import {useStore} from 'react-redux';
import {Client4} from 'mattermost-redux/client';
import DesktopAppAPI from 'utils/desktop_api';
import PerformanceReporter from 'utils/performance_telemetry/reporter';
export default function PerformanceReporterController() {
@@ -14,7 +15,7 @@ export default function PerformanceReporterController() {
const reporter = useRef<PerformanceReporter>();
useEffect(() => {
reporter.current = new PerformanceReporter(Client4, store);
reporter.current = new PerformanceReporter(Client4, store, DesktopAppAPI);
reporter.current.observe();
// There's no way to clean up web-vitals, so continue to assume that this component won't ever be unmounted

Просмотреть файл

@@ -13,9 +13,10 @@ declare global {
}
}
class DesktopAppAPI {
export class DesktopAppAPI {
private name?: string;
private version?: string | null;
private prereleaseVersion?: string;
private dev?: boolean;
/**
@@ -32,6 +33,7 @@ class DesktopAppAPI {
this.getDesktopAppInfo().then(({name, version}) => {
this.name = name;
this.version = semver.valid(semver.coerce(version));
this.prereleaseVersion = version?.split('-')?.[1];
// Legacy Desktop App version, used by some plugins
if (!window.desktop) {
@@ -63,6 +65,10 @@ class DesktopAppAPI {
return this.version;
};
getPrereleaseVersion = () => {
return this.prereleaseVersion;
};
isDev = () => {
return this.dev;
};
@@ -152,6 +158,10 @@ class DesktopAppAPI {
return () => this.removePostMessageListener('history-button-return', legacyListener);
};
onReceiveMetrics = (listener: (metricsMap: Map<string, {cpu?: number; memory?: number}>) => void) => {
return window.desktopAPI?.onSendMetrics?.(listener);
};
/**
* One-ways
*/

Просмотреть файл

@@ -37,3 +37,7 @@ export function getUserAgentLabel() {
return 'other';
}
export function getDesktopAppVersionLabel(appVersion?: string | null, prereleaseVersion?: string) {
return prereleaseVersion?.split('.')[0] ?? appVersion ?? 'unknown';
}

Просмотреть файл

@@ -10,6 +10,7 @@ import configureStore from 'store';
import {reset as resetUserAgent, setPlatform, set as setUserAgent} from 'tests/helpers/user_agent_mocks';
import {waitForObservations} from 'tests/performance_mock';
import {DesktopAppAPI} from 'utils/desktop_api';
import PerformanceReporter from './reporter';
@@ -389,7 +390,7 @@ function newTestReporter(telemetryEnabled = true, loggedIn = true) {
currentUserId: loggedIn ? 'currentUserId' : '',
},
},
}));
}), new DesktopAppAPI());
return {
client,

Просмотреть файл

@@ -10,12 +10,14 @@ import type {Client4} from '@mattermost/client';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import type {DesktopAppAPI} from 'utils/desktop_api';
import type {GlobalState} from 'types/store';
import {identifyElementRegion} from './element_identification';
import type {PerformanceLongTaskTiming} from './long_task';
import type {PlatformLabel, UserAgentLabel} from './platform_detection';
import {getPlatformLabel, getUserAgentLabel} from './platform_detection';
import {getDesktopAppVersionLabel, getPlatformLabel, getUserAgentLabel} from './platform_detection';
import {Measure} from '.';
@@ -52,6 +54,7 @@ type PerformanceReport = {
labels: {
platform: PlatformLabel;
agent: UserAgentLabel;
desktop_app_version?: string;
};
start: number;
@@ -65,8 +68,12 @@ export default class PerformanceReporter {
private client: Client4;
private store: Store<GlobalState>;
private desktopAPI: DesktopAppAPI;
private desktopOffListener?: () => void;
private platformLabel: PlatformLabel;
private userAgentLabel: UserAgentLabel;
private desktopAppVersion?: string;
private counters: Map<string, number>;
private histogramMeasures: PerformanceReportMeasure[];
@@ -78,13 +85,17 @@ export default class PerformanceReporter {
protected reportPeriodBase = 60 * 1000;
protected reportPeriodJitter = 15 * 1000;
constructor(client: Client4, store: Store<GlobalState>) {
constructor(client: Client4, store: Store<GlobalState>, desktopAPI: DesktopAppAPI) {
this.client = client;
this.store = store;
this.desktopAPI = desktopAPI;
this.platformLabel = getPlatformLabel();
this.userAgentLabel = getUserAgentLabel();
// We want to submit by prerelease version if it exists, so we don't muddy up the metrics for the release builds
this.desktopAppVersion = getDesktopAppVersionLabel(desktopAPI.getAppVersion(), desktopAPI.getPrereleaseVersion());
this.counters = new Map();
this.histogramMeasures = [];
@@ -121,6 +132,10 @@ export default class PerformanceReporter {
// Send any remaining metrics when the page becomes hidden rather than when it's unloaded because that's
// what's recommended by various sites due to unload handlers being unreliable, particularly on mobile.
addEventListener('visibilitychange', this.handleVisibilityChange);
if (!this.desktopAPI.isDev()) {
this.desktopOffListener = this.desktopAPI.onReceiveMetrics((metrics) => this.collectDesktopAppMetrics(metrics));
}
}
private measurePageLoad() {
@@ -147,6 +162,8 @@ export default class PerformanceReporter {
this.reportTimeout = undefined;
this.observer.disconnect();
this.desktopOffListener?.();
}
protected handleObservations(list: PerformanceObserverEntryList) {
@@ -279,6 +296,7 @@ export default class PerformanceReporter {
labels: {
platform: this.platformLabel,
agent: this.userAgentLabel,
desktop_app_version: this.desktopAppVersion,
},
...this.getReportStartEnd(now, histogramMeasures, counterMeasures),
@@ -341,6 +359,35 @@ export default class PerformanceReporter {
return navigator.sendBeacon(url, data);
}
protected collectDesktopAppMetrics(metricsMap: Map<string, {cpu?: number; memory?: number}>) {
const now = Date.now();
for (const [processName, metrics] of metricsMap.entries()) {
let process = processName;
if (process.startsWith('Server ')) {
process = 'Server';
}
if (metrics.cpu) {
this.histogramMeasures.push({
metric: 'desktop_cpu',
timestamp: now,
labels: {process},
value: metrics.cpu,
});
}
if (metrics.memory) {
this.histogramMeasures.push({
metric: 'desktop_memory',
timestamp: now,
labels: {process},
value: metrics.memory,
});
}
}
}
}
function isPerformanceLongTask(entry: PerformanceEntry): entry is PerformanceLongTaskTiming {

28
webapp/package-lock.json сгенерированный
Просмотреть файл

@@ -62,7 +62,7 @@
"@mattermost/client": "*",
"@mattermost/compass-components": "^0.2.12",
"@mattermost/compass-icons": "0.1.39",
"@mattermost/desktop-api": "5.8.0-5",
"@mattermost/desktop-api": "5.10.0-2",
"@mattermost/types": "*",
"@mui/base": "5.0.0-alpha.127",
"@mui/material": "5.11.16",
@@ -227,6 +227,19 @@
"yargs": "16.2.0"
}
},
"channels/node_modules/@mattermost/desktop-api": {
"version": "5.10.0-2",
"resolved": "https://registry.npmjs.org/@mattermost/desktop-api/-/desktop-api-5.10.0-2.tgz",
"integrity": "sha512-Okb+VP6gdwEBnSthzxiU3+oO5XLTocDvWlzoGYjBYrT4DN2/xxAzRYgp1VB6skQxEwVhbP/zaQvWbRtZrp8org==",
"peerDependencies": {
"typescript": "^4.3.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"channels/node_modules/semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
@@ -4310,19 +4323,6 @@
"resolved": "platform/components",
"link": true
},
"node_modules/@mattermost/desktop-api": {
"version": "5.8.0-5",
"resolved": "https://registry.npmjs.org/@mattermost/desktop-api/-/desktop-api-5.8.0-5.tgz",
"integrity": "sha512-YPtFRnduVFOXyK25GedJA+PkAKmFpLDKqfxvV/IIS+SMypv6BD17LJ8AkdBsguFFa6ZWLlZU40M5grKYBbjOuA==",
"peerDependencies": {
"typescript": "^4.3.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@mattermost/eslint-plugin": {
"resolved": "platform/eslint-plugin",
"link": true