Skip to content

Commit

Permalink
[MM-60609][MM-60612] Include Desktop App metrics in PerformanceReport…
Browse files Browse the repository at this point in the history
…er, add metrics in Prometheus for CPU/Memory usage (mattermost#28825)

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

* Fix mocks

* PR feedback
  • Loading branch information
devinbinnie authored Oct 22, 2024
1 parent 2d96053 commit 0c90b03
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 22 deletions.
4 changes: 4 additions & 0 deletions server/channels/app/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ func (a *App) RegisterPerformanceReport(rctx request.CTX, report *model.Performa
a.Metrics().ObserveMobileClientChannelSwitchDuration(commonLabels["platform"], h.Value/1000)
case model.MobileClientTeamSwitchDuration:
a.Metrics().ObserveMobileClientTeamSwitchDuration(commonLabels["platform"], h.Value/1000)
case model.DesktopClientCPUUsage:
a.Metrics().ObserveDesktopCpuUsage(commonLabels["platform"], commonLabels["desktop_app_version"], h.Labels["process"], h.Value)
case model.DesktopClientMemoryUsage:
a.Metrics().ObserveDesktopMemoryUsage(commonLabels["platform"], commonLabels["desktop_app_version"], h.Labels["process"], h.Value/1000)
default:
// we intentionally skip unknown metrics
}
Expand Down
2 changes: 2 additions & 0 deletions server/einterfaces/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,6 @@ type MetricsInterface interface {
ObserveMobileClientTeamSwitchDuration(platform string, elapsed float64)
ClearMobileClientSessionMetadata()
ObserveMobileClientSessionMetadata(version string, platform string, value float64, notificationDisabled string)
ObserveDesktopCpuUsage(platform, version, process string, usage float64)
ObserveDesktopMemoryUsage(platform, version, process string, usage float64)
}
10 changes: 10 additions & 0 deletions server/einterfaces/mocks/MetricsInterface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions server/enterprise/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
MetricsSubsystemNotifications = "notifications"
MetricsSubsystemClientsMobileApp = "mobileapp"
MetricsSubsystemClientsWeb = "webapp"
MetricsSubsystemClientsDesktopApp = "desktopapp"
MetricsCloudInstallationLabel = "installationId"
MetricsCloudDatabaseClusterLabel = "databaseClusterName"
MetricsCloudInstallationGroupLabel = "installationGroupId"
Expand Down Expand Up @@ -216,6 +217,9 @@ type MetricsInterfaceImpl struct {
MobileClientChannelSwitchDuration *prometheus.HistogramVec
MobileClientTeamSwitchDuration *prometheus.HistogramVec
MobileClientSessionMetadataGauge *prometheus.GaugeVec

DesktopClientCPUUsage *prometheus.HistogramVec
DesktopClientMemoryUsage *prometheus.HistogramVec
}

func init() {
Expand Down Expand Up @@ -1347,6 +1351,30 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf
)
m.Registry.MustRegister(m.MobileClientSessionMetadataGauge)

m.DesktopClientCPUUsage = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemClientsDesktopApp,
Name: "cpu_usage",
Help: "Average CPU usage of a specific process over an interval",
Buckets: []float64{0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 80, 100},
},
[]string{"platform", "version", "processName"},
)
m.Registry.MustRegister(m.DesktopClientCPUUsage)

m.DesktopClientMemoryUsage = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemClientsDesktopApp,
Name: "memory_usage",
Help: "Memory usage in MB of a specific process",
Buckets: []float64{0, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 3000, 5000},
},
[]string{"platform", "version", "processName"},
)
m.Registry.MustRegister(m.DesktopClientMemoryUsage)

return m
}

Expand Down Expand Up @@ -1850,6 +1878,14 @@ func (mi *MetricsInterfaceImpl) ObserveGlobalThreadsLoadDuration(platform, agent
mi.ClientGlobalThreadsLoadDuration.With(prometheus.Labels{"platform": platform, "agent": agent}).Observe(elapsed)
}

func (mi *MetricsInterfaceImpl) ObserveDesktopCpuUsage(platform, version, process string, usage float64) {
mi.DesktopClientCPUUsage.With(prometheus.Labels{"platform": platform, "version": version, "processName": process}).Observe(usage)
}

func (mi *MetricsInterfaceImpl) ObserveDesktopMemoryUsage(platform, version, process string, usage float64) {
mi.DesktopClientMemoryUsage.With(prometheus.Labels{"platform": platform, "version": version, "processName": process}).Observe(usage)
}

func (mi *MetricsInterfaceImpl) ObserveMobileClientLoadDuration(platform string, elapsed float64) {
mi.MobileClientLoadDuration.With(prometheus.Labels{"platform": platform}).Observe(elapsed)
}
Expand Down
8 changes: 6 additions & 2 deletions server/public/model/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const (
MobileClientChannelSwitchDuration MetricType = "mobile_channel_switch"
MobileClientTeamSwitchDuration MetricType = "mobile_team_switch"

DesktopClientCPUUsage MetricType = "desktop_cpu"
DesktopClientMemoryUsage MetricType = "desktop_memory"

performanceReportTTLMilliseconds = 300 * 1000 // 300 seconds/5 minutes
)

Expand Down Expand Up @@ -104,8 +107,9 @@ func (r *PerformanceReport) IsValid() error {

func (r *PerformanceReport) ProcessLabels() map[string]string {
return map[string]string{
"platform": processLabel(r.Labels, "platform", acceptedPlatforms, "other"),
"agent": processLabel(r.Labels, "agent", acceptedAgents, "other"),
"platform": processLabel(r.Labels, "platform", acceptedPlatforms, "other"),
"agent": processLabel(r.Labels, "agent", acceptedAgents, "other"),
"desktop_app_version": r.Labels["desktop_app_version"],
}
}

Expand Down
2 changes: 1 addition & 1 deletion webapp/channels/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
Expand Down
12 changes: 11 additions & 1 deletion webapp/channels/src/utils/desktop_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ declare global {
}
}

class DesktopAppAPI {
export class DesktopAppAPI {
private name?: string;
private version?: string | null;
private prereleaseVersion?: string;
private dev?: boolean;

/**
Expand All @@ -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) {
Expand Down Expand Up @@ -63,6 +65,10 @@ class DesktopAppAPI {
return this.version;
};

getPrereleaseVersion = () => {
return this.prereleaseVersion;
};

isDev = () => {
return this.dev;
};
Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ export function getUserAgentLabel() {

return 'other';
}

export function getDesktopAppVersionLabel(appVersion?: string | null, prereleaseVersion?: string) {
return prereleaseVersion?.split('.')[0] ?? appVersion ?? 'unknown';
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -389,7 +390,7 @@ function newTestReporter(telemetryEnabled = true, loggedIn = true) {
currentUserId: loggedIn ? 'currentUserId' : '',
},
},
}));
}), new DesktopAppAPI());

return {
client,
Expand Down
51 changes: 49 additions & 2 deletions webapp/channels/src/utils/performance_telemetry/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 '.';

Expand Down Expand Up @@ -52,6 +54,7 @@ type PerformanceReport = {
labels: {
platform: PlatformLabel;
agent: UserAgentLabel;
desktop_app_version?: string;
};

start: number;
Expand All @@ -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[];
Expand All @@ -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 = [];

Expand Down Expand Up @@ -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() {
Expand All @@ -147,6 +162,8 @@ export default class PerformanceReporter {
this.reportTimeout = undefined;

this.observer.disconnect();

this.desktopOffListener?.();
}

protected handleObservations(list: PerformanceObserverEntryList) {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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 {
Expand Down
28 changes: 14 additions & 14 deletions webapp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0c90b03

Please sign in to comment.