[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
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
2d96053012
Коммит
0c90b0363b
@@ -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
сгенерированный
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
|
||||
|
||||
Ссылка в новой задаче
Block a user