From a6d37fa14c194263b78774bf62ac49ce1cd23033 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Thu, 5 Dec 2024 09:12:54 +0530 Subject: [PATCH] MM-61887: Log the userID if a metric exceeds the last histogram bucket (#29448) We create a custom histogram metric that logs the userID when the observed value is greater or equal to the last bucket value. This allows us to start tracking the slowest users of a system while at the same time not polluting the Prometheus metrics by storing a userID for every observation. https://mattermost.atlassian.net/browse/MM-61887 ```release-note NONE ``` --- server/channels/app/metrics.go | 59 +++++++++------ server/channels/testlib/assertions.go | 4 +- server/einterfaces/metrics.go | 10 +-- server/einterfaces/mocks/MetricsInterface.go | 30 ++++---- server/enterprise/metrics/histogram.go | 76 ++++++++++++++++++++ server/enterprise/metrics/histogram_test.go | 35 +++++++++ server/enterprise/metrics/metrics.go | 52 ++++++++------ 7 files changed, 203 insertions(+), 63 deletions(-) create mode 100644 server/enterprise/metrics/histogram.go create mode 100644 server/enterprise/metrics/histogram_test.go diff --git a/server/channels/app/metrics.go b/server/channels/app/metrics.go index 1372f490f7..998413cdab 100644 --- a/server/channels/app/metrics.go +++ b/server/channels/app/metrics.go @@ -14,6 +14,7 @@ func (a *App) RegisterPerformanceReport(rctx request.CTX, report *model.Performa } commonLabels := report.ProcessLabels() + userID := rctx.Session().UserId for _, c := range report.Counters { switch c.Metric { @@ -27,60 +28,78 @@ func (a *App) RegisterPerformanceReport(rctx request.CTX, report *model.Performa for _, h := range report.Histograms { switch h.Metric { case model.ClientTimeToFirstByte: - a.Metrics().ObserveClientTimeToFirstByte(commonLabels["platform"], commonLabels["agent"], h.Value/1000) + a.Metrics().ObserveClientTimeToFirstByte( + commonLabels["platform"], + commonLabels["agent"], + userID, h.Value/1000) case model.ClientTimeToLastByte: - a.Metrics().ObserveClientTimeToLastByte(commonLabels["platform"], commonLabels["agent"], h.Value/1000) + a.Metrics().ObserveClientTimeToLastByte( + commonLabels["platform"], + commonLabels["agent"], + userID, h.Value/1000) case model.ClientTimeToDOMInteractive: - a.Metrics().ObserveClientTimeToDomInteractive(commonLabels["platform"], commonLabels["agent"], h.Value/1000) + a.Metrics().ObserveClientTimeToDomInteractive( + commonLabels["platform"], + commonLabels["agent"], + userID, h.Value/1000) case model.ClientSplashScreenEnd: a.Metrics().ObserveClientSplashScreenEnd(commonLabels["platform"], commonLabels["agent"], h.GetLabelValue("page_type", model.AcceptedSplashScreenOrigins, "team_controller"), - h.Value/1000) + userID, h.Value/1000) case model.ClientFirstContentfulPaint: - a.Metrics().ObserveClientFirstContentfulPaint(commonLabels["platform"], commonLabels["agent"], h.Value/1000) + a.Metrics().ObserveClientFirstContentfulPaint(commonLabels["platform"], + commonLabels["agent"], + h.Value/1000) case model.ClientLargestContentfulPaint: a.Metrics().ObserveClientLargestContentfulPaint( commonLabels["platform"], commonLabels["agent"], h.GetLabelValue("region", model.AcceptedLCPRegions, "other"), - h.Value/1000, - ) + h.Value/1000) case model.ClientInteractionToNextPaint: a.Metrics().ObserveClientInteractionToNextPaint( commonLabels["platform"], commonLabels["agent"], h.GetLabelValue("interaction", model.AcceptedInteractions, "other"), - h.Value/1000, - ) + h.Value/1000) case model.ClientCumulativeLayoutShift: - a.Metrics().ObserveClientCumulativeLayoutShift(commonLabels["platform"], commonLabels["agent"], h.Value) + a.Metrics().ObserveClientCumulativeLayoutShift(commonLabels["platform"], + commonLabels["agent"], + h.Value) case model.ClientPageLoadDuration: - a.Metrics().ObserveClientPageLoadDuration(commonLabels["platform"], commonLabels["agent"], h.Value/1000) + a.Metrics().ObserveClientPageLoadDuration(commonLabels["platform"], + commonLabels["agent"], + userID, h.Value/1000) case model.ClientChannelSwitchDuration: a.Metrics().ObserveClientChannelSwitchDuration( commonLabels["platform"], commonLabels["agent"], h.GetLabelValue("fresh", model.AcceptedTrueFalseLabels, ""), - h.Value/1000, - ) + h.Value/1000) case model.ClientTeamSwitchDuration: a.Metrics().ObserveClientTeamSwitchDuration( commonLabels["platform"], commonLabels["agent"], h.GetLabelValue("fresh", model.AcceptedTrueFalseLabels, ""), - h.Value/1000, - ) + h.Value/1000) case model.ClientRHSLoadDuration: - a.Metrics().ObserveClientRHSLoadDuration(commonLabels["platform"], commonLabels["agent"], h.Value/1000) + a.Metrics().ObserveClientRHSLoadDuration(commonLabels["platform"], + commonLabels["agent"], + h.Value/1000) case model.ClientGlobalThreadsLoadDuration: - a.Metrics().ObserveGlobalThreadsLoadDuration(commonLabels["platform"], commonLabels["agent"], h.Value/1000) + a.Metrics().ObserveGlobalThreadsLoadDuration(commonLabels["platform"], + commonLabels["agent"], + h.Value/1000) case model.MobileClientLoadDuration: - a.Metrics().ObserveMobileClientLoadDuration(commonLabels["platform"], h.Value/1000) + a.Metrics().ObserveMobileClientLoadDuration(commonLabels["platform"], + h.Value/1000) case model.MobileClientChannelSwitchDuration: - a.Metrics().ObserveMobileClientChannelSwitchDuration(commonLabels["platform"], h.Value/1000) + a.Metrics().ObserveMobileClientChannelSwitchDuration(commonLabels["platform"], + h.Value/1000) case model.MobileClientTeamSwitchDuration: - a.Metrics().ObserveMobileClientTeamSwitchDuration(commonLabels["platform"], h.Value/1000) + 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: diff --git a/server/channels/testlib/assertions.go b/server/channels/testlib/assertions.go index 1e7cf3c764..2f66a73795 100644 --- a/server/channels/testlib/assertions.go +++ b/server/channels/testlib/assertions.go @@ -15,7 +15,7 @@ import ( func AssertLog(t *testing.T, logs io.Reader, level, message string) { t.Helper() if !hasMsg(t, logs, level, message) { - assert.Failf(t, "failed to find %s log message: %s", level, message) + assert.Failf(t, "failed to find", "Expected log_level: %s, log_message: %s", level, message) } } @@ -23,7 +23,7 @@ func AssertLog(t *testing.T, logs io.Reader, level, message string) { func AssertNoLog(t *testing.T, logs io.Reader, level, message string) { t.Helper() if hasMsg(t, logs, level, message) { - assert.Failf(t, "found %s log message: %s", level, message) + assert.Failf(t, "found", "Not expected log_level: %s log_message: %s", level, message) } } diff --git a/server/einterfaces/metrics.go b/server/einterfaces/metrics.go index 98ca099d11..3ad5807088 100644 --- a/server/einterfaces/metrics.go +++ b/server/einterfaces/metrics.go @@ -104,16 +104,16 @@ type MetricsInterface interface { IncrementNotificationNotSentCounter(notificationType model.NotificationType, notSentReason model.NotificationReason, platform string) IncrementNotificationUnsupportedCounter(notificationType model.NotificationType, notSentReason model.NotificationReason, platform string) - ObserveClientTimeToFirstByte(platform, agent string, elapsed float64) - ObserveClientTimeToLastByte(platform, agent string, elapsed float64) - ObserveClientTimeToDomInteractive(platform, agent string, elapsed float64) - ObserveClientSplashScreenEnd(platform, agent, pageType string, elapsed float64) + ObserveClientTimeToFirstByte(platform, agent, userID string, elapsed float64) + ObserveClientTimeToLastByte(platform, agent, userID string, elapsed float64) + ObserveClientTimeToDomInteractive(platform, agent, userID string, elapsed float64) + ObserveClientSplashScreenEnd(platform, agent, pageType, userID string, elapsed float64) ObserveClientFirstContentfulPaint(platform, agent string, elapsed float64) ObserveClientLargestContentfulPaint(platform, agent, region string, elapsed float64) ObserveClientInteractionToNextPaint(platform, agent, interaction string, elapsed float64) ObserveClientCumulativeLayoutShift(platform, agent string, elapsed float64) IncrementClientLongTasks(platform, agent string, inc float64) - ObserveClientPageLoadDuration(platform, agent string, elapsed float64) + ObserveClientPageLoadDuration(platform, agent, userID string, elapsed float64) ObserveClientChannelSwitchDuration(platform, agent, fresh string, elapsed float64) ObserveClientTeamSwitchDuration(platform, agent, fresh string, elapsed float64) ObserveClientRHSLoadDuration(platform, agent string, elapsed float64) diff --git a/server/einterfaces/mocks/MetricsInterface.go b/server/einterfaces/mocks/MetricsInterface.go index f8e80cec3e..ced067962f 100644 --- a/server/einterfaces/mocks/MetricsInterface.go +++ b/server/einterfaces/mocks/MetricsInterface.go @@ -328,9 +328,9 @@ func (_m *MetricsInterface) ObserveClientLargestContentfulPaint(platform string, _m.Called(platform, agent, region, elapsed) } -// ObserveClientPageLoadDuration provides a mock function with given fields: platform, agent, elapsed -func (_m *MetricsInterface) ObserveClientPageLoadDuration(platform string, agent string, elapsed float64) { - _m.Called(platform, agent, elapsed) +// ObserveClientPageLoadDuration provides a mock function with given fields: platform, agent, userID, elapsed +func (_m *MetricsInterface) ObserveClientPageLoadDuration(platform string, agent string, userID string, elapsed float64) { + _m.Called(platform, agent, userID, elapsed) } // ObserveClientRHSLoadDuration provides a mock function with given fields: platform, agent, elapsed @@ -338,9 +338,9 @@ func (_m *MetricsInterface) ObserveClientRHSLoadDuration(platform string, agent _m.Called(platform, agent, elapsed) } -// ObserveClientSplashScreenEnd provides a mock function with given fields: platform, agent, pageType, elapsed -func (_m *MetricsInterface) ObserveClientSplashScreenEnd(platform string, agent string, pageType string, elapsed float64) { - _m.Called(platform, agent, pageType, elapsed) +// ObserveClientSplashScreenEnd provides a mock function with given fields: platform, agent, pageType, userID, elapsed +func (_m *MetricsInterface) ObserveClientSplashScreenEnd(platform string, agent string, pageType string, userID string, elapsed float64) { + _m.Called(platform, agent, pageType, userID, elapsed) } // ObserveClientTeamSwitchDuration provides a mock function with given fields: platform, agent, fresh, elapsed @@ -348,19 +348,19 @@ func (_m *MetricsInterface) ObserveClientTeamSwitchDuration(platform string, age _m.Called(platform, agent, fresh, elapsed) } -// ObserveClientTimeToDomInteractive provides a mock function with given fields: platform, agent, elapsed -func (_m *MetricsInterface) ObserveClientTimeToDomInteractive(platform string, agent string, elapsed float64) { - _m.Called(platform, agent, elapsed) +// ObserveClientTimeToDomInteractive provides a mock function with given fields: platform, agent, userID, elapsed +func (_m *MetricsInterface) ObserveClientTimeToDomInteractive(platform string, agent string, userID string, elapsed float64) { + _m.Called(platform, agent, userID, elapsed) } -// ObserveClientTimeToFirstByte provides a mock function with given fields: platform, agent, elapsed -func (_m *MetricsInterface) ObserveClientTimeToFirstByte(platform string, agent string, elapsed float64) { - _m.Called(platform, agent, elapsed) +// ObserveClientTimeToFirstByte provides a mock function with given fields: platform, agent, userID, elapsed +func (_m *MetricsInterface) ObserveClientTimeToFirstByte(platform string, agent string, userID string, elapsed float64) { + _m.Called(platform, agent, userID, elapsed) } -// ObserveClientTimeToLastByte provides a mock function with given fields: platform, agent, elapsed -func (_m *MetricsInterface) ObserveClientTimeToLastByte(platform string, agent string, elapsed float64) { - _m.Called(platform, agent, elapsed) +// ObserveClientTimeToLastByte provides a mock function with given fields: platform, agent, userID, elapsed +func (_m *MetricsInterface) ObserveClientTimeToLastByte(platform string, agent string, userID string, elapsed float64) { + _m.Called(platform, agent, userID, elapsed) } // ObserveClusterRequestDuration provides a mock function with given fields: elapsed diff --git a/server/enterprise/metrics/histogram.go b/server/enterprise/metrics/histogram.go new file mode 100644 index 0000000000..bc79966135 --- /dev/null +++ b/server/enterprise/metrics/histogram.go @@ -0,0 +1,76 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.enterprise for license information. + +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + + "github.com/mattermost/mattermost/server/public/shared/mlog" +) + +// HistogramVec is a wrapper of prometheus.HistogramVec that stores the buckets +// which are later passed down to WrappedObserver. +type HistogramVec struct { + *prometheus.HistogramVec + buckets []float64 + fqName string + logger mlog.LoggerIFace +} + +// WrappedObserver is a wrapper of prometheus.Observer which addtionally +// logs userIDs when the value exceeds or equals to the one in the highest bucket. +type WrappedObserver struct { + prometheus.Observer + labels prometheus.Labels + buckets []float64 + logger mlog.LoggerIFace + metricName string + userID string +} + +// NewHistogramVec returns a custom-purpose HistogramVec. +func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string, logger mlog.LoggerIFace) *HistogramVec { + if len(opts.Buckets) == 0 { + opts.Buckets = prometheus.DefBuckets + } + return &HistogramVec{ + HistogramVec: prometheus.NewHistogramVec(opts, labelNames), + buckets: opts.Buckets, + fqName: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), + logger: logger, + } +} + +func (v *HistogramVec) With(labels prometheus.Labels, userID string) prometheus.Observer { + h, err := v.GetMetricWith(labels) + if err != nil { + panic(err) + } + return &WrappedObserver{ + Observer: h, + labels: labels, + buckets: v.buckets, + logger: v.logger, + metricName: v.fqName, + userID: userID, + } +} + +func (o *WrappedObserver) Observe(v float64) { + if v >= o.buckets[len(o.buckets)-1] { + fields := []mlog.Field{ + mlog.String("metric_name", o.metricName), + mlog.String("user_id", o.userID), + mlog.Float("observed_value", v), + mlog.Float("highest_bucket_value", o.buckets[len(o.buckets)-1]), + } + for k, v := range o.labels { + fields = append(fields, + mlog.String("label_name", k), + mlog.String("label_value", v)) + } + o.logger.Warn("Metric observation exceeded.", fields...) + } + o.Observer.Observe(v) +} diff --git a/server/enterprise/metrics/histogram_test.go b/server/enterprise/metrics/histogram_test.go new file mode 100644 index 0000000000..38cc880db0 --- /dev/null +++ b/server/enterprise/metrics/histogram_test.go @@ -0,0 +1,35 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.enterprise for license information. + +package metrics + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost/server/public/shared/mlog" + "github.com/mattermost/mattermost/server/v8/channels/api4" + "github.com/mattermost/mattermost/server/v8/channels/testlib" +) + +func TestWrappedObserver(t *testing.T) { + th := api4.Setup(t) + defer th.TearDown() + + h := NewHistogramVec(prometheus.HistogramOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystemClientsWeb, + Name: "test", + Buckets: []float64{0, 5, 10}, + }, []string{"l1"}, th.TestLogger) + + h.With(prometheus.Labels{"l1": "hello"}, th.BasicUser.Id).Observe(6) + require.NoError(t, th.TestLogger.Flush()) + testlib.AssertNoLog(t, th.LogBuffer, mlog.LvlWarn.Name, "Metric observation exceeded.") + + h.With(prometheus.Labels{"l1": "hello"}, th.BasicUser.Id).Observe(10) + require.NoError(t, th.TestLogger.Flush()) + testlib.AssertLog(t, th.LogBuffer, mlog.LvlWarn.Name, "Metric observation exceeded.") +} diff --git a/server/enterprise/metrics/metrics.go b/server/enterprise/metrics/metrics.go index 4e79279cef..309c601bcc 100644 --- a/server/enterprise/metrics/metrics.go +++ b/server/enterprise/metrics/metrics.go @@ -201,16 +201,16 @@ type MetricsInterfaceImpl struct { NotificationNotSentCounters *prometheus.CounterVec NotificationUnsupportedCounters *prometheus.CounterVec - ClientTimeToFirstByte *prometheus.HistogramVec - ClientTimeToLastByte *prometheus.HistogramVec - ClientTimeToDOMInteractive *prometheus.HistogramVec - ClientSplashScreenEnd *prometheus.HistogramVec + ClientTimeToFirstByte *HistogramVec + ClientTimeToLastByte *HistogramVec + ClientTimeToDOMInteractive *HistogramVec + ClientSplashScreenEnd *HistogramVec ClientFirstContentfulPaint *prometheus.HistogramVec ClientLargestContentfulPaint *prometheus.HistogramVec ClientInteractionToNextPaint *prometheus.HistogramVec ClientCumulativeLayoutShift *prometheus.HistogramVec ClientLongTasks *prometheus.CounterVec - ClientPageLoadDuration *prometheus.HistogramVec + ClientPageLoadDuration *HistogramVec ClientChannelSwitchDuration *prometheus.HistogramVec ClientTeamSwitchDuration *prometheus.HistogramVec ClientRHSLoadDuration *prometheus.HistogramVec @@ -1179,7 +1179,7 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf ) m.Registry.MustRegister(m.NotificationUnsupportedCounters) - m.ClientTimeToFirstByte = prometheus.NewHistogramVec( + m.ClientTimeToFirstByte = NewHistogramVec( prometheus.HistogramOpts{ Namespace: MetricsNamespace, Subsystem: MetricsSubsystemClientsWeb, @@ -1187,10 +1187,11 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf Help: "Duration from when a browser starts to request a page from a server until when it starts to receive data in response (seconds)", }, []string{"platform", "agent"}, + m.Platform.Log(), ) m.Registry.MustRegister(m.ClientTimeToFirstByte) - m.ClientTimeToLastByte = prometheus.NewHistogramVec( + m.ClientTimeToLastByte = NewHistogramVec( prometheus.HistogramOpts{ Namespace: MetricsNamespace, Subsystem: MetricsSubsystemClientsWeb, @@ -1198,28 +1199,33 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf Help: "Duration from when a browser starts to request a page from a server until when it receives the last byte of the resource or immediately before the transport connection is closed, whichever comes first. (seconds)", }, []string{"platform", "agent"}, + m.Platform.Log(), ) m.Registry.MustRegister(m.ClientTimeToLastByte) - m.ClientTimeToDOMInteractive = prometheus.NewHistogramVec( + m.ClientTimeToDOMInteractive = NewHistogramVec( prometheus.HistogramOpts{ Namespace: MetricsNamespace, Subsystem: MetricsSubsystemClientsWeb, Name: "dom_interactive", Help: "Duration from when a browser starts to request a page from a server until when it sets the document's readyState to interactive. (seconds)", + Buckets: []float64{.1, .25, .5, 1, 2.5, 5, 7.5, 10, 12.5, 15}, }, []string{"platform", "agent"}, + m.Platform.Log(), ) m.Registry.MustRegister(m.ClientTimeToDOMInteractive) - m.ClientSplashScreenEnd = prometheus.NewHistogramVec( + m.ClientSplashScreenEnd = NewHistogramVec( prometheus.HistogramOpts{ Namespace: MetricsNamespace, Subsystem: MetricsSubsystemClientsWeb, Name: "splash_screen", Help: "Duration from when a browser starts to request a page from a server until when the splash screen ends. (seconds)", + Buckets: []float64{.1, .25, .5, 1, 2.5, 5, 7.5, 10, 12.5, 15}, }, []string{"platform", "agent", "page_type"}, + m.Platform.Log(), ) m.Registry.MustRegister(m.ClientSplashScreenEnd) @@ -1284,7 +1290,7 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf ) m.Registry.MustRegister(m.ClientLongTasks) - m.ClientPageLoadDuration = prometheus.NewHistogramVec( + m.ClientPageLoadDuration = NewHistogramVec( prometheus.HistogramOpts{ Namespace: MetricsNamespace, Subsystem: MetricsSubsystemClientsWeb, @@ -1293,6 +1299,7 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 20, 40}, }, []string{"platform", "agent"}, + m.Platform.Log(), ) m.Registry.MustRegister(m.ClientPageLoadDuration) @@ -1870,20 +1877,20 @@ func (mi *MetricsInterfaceImpl) DecrementHTTPWebSockets(originClient string) { mi.HTTPWebsocketsGauge.With(prometheus.Labels{"origin_client": originClient}).Dec() } -func (mi *MetricsInterfaceImpl) ObserveClientTimeToFirstByte(platform, agent string, elapsed float64) { - mi.ClientTimeToFirstByte.With(prometheus.Labels{"platform": platform, "agent": agent}).Observe(elapsed) +func (mi *MetricsInterfaceImpl) ObserveClientTimeToFirstByte(platform, agent, userID string, elapsed float64) { + mi.ClientTimeToFirstByte.With(prometheus.Labels{"platform": platform, "agent": agent}, userID).Observe(elapsed) } -func (mi *MetricsInterfaceImpl) ObserveClientTimeToLastByte(platform, agent string, elapsed float64) { - mi.ClientTimeToLastByte.With(prometheus.Labels{"platform": platform, "agent": agent}).Observe(elapsed) +func (mi *MetricsInterfaceImpl) ObserveClientTimeToLastByte(platform, agent, userID string, elapsed float64) { + mi.ClientTimeToLastByte.With(prometheus.Labels{"platform": platform, "agent": agent}, userID).Observe(elapsed) } -func (mi *MetricsInterfaceImpl) ObserveClientTimeToDomInteractive(platform, agent string, elapsed float64) { - mi.ClientTimeToDOMInteractive.With(prometheus.Labels{"platform": platform, "agent": agent}).Observe(elapsed) +func (mi *MetricsInterfaceImpl) ObserveClientTimeToDomInteractive(platform, agent, userID string, elapsed float64) { + mi.ClientTimeToDOMInteractive.With(prometheus.Labels{"platform": platform, "agent": agent}, userID).Observe(elapsed) } -func (mi *MetricsInterfaceImpl) ObserveClientSplashScreenEnd(platform, agent, pageType string, elapsed float64) { - mi.ClientSplashScreenEnd.With(prometheus.Labels{"platform": platform, "agent": agent, "page_type": pageType}).Observe(elapsed) +func (mi *MetricsInterfaceImpl) ObserveClientSplashScreenEnd(platform, agent, pageType, userID string, elapsed float64) { + mi.ClientSplashScreenEnd.With(prometheus.Labels{"platform": platform, "agent": agent, "page_type": pageType}, userID).Observe(elapsed) } func (mi *MetricsInterfaceImpl) ObserveClientFirstContentfulPaint(platform, agent string, elapsed float64) { @@ -1906,8 +1913,11 @@ func (mi *MetricsInterfaceImpl) IncrementClientLongTasks(platform, agent string, mi.ClientLongTasks.With(prometheus.Labels{"platform": platform, "agent": agent}).Add(inc) } -func (mi *MetricsInterfaceImpl) ObserveClientPageLoadDuration(platform, agent string, elapsed float64) { - mi.ClientPageLoadDuration.With(prometheus.Labels{"platform": platform, "agent": agent}).Observe(elapsed) +func (mi *MetricsInterfaceImpl) ObserveClientPageLoadDuration(platform, agent, userID string, elapsed float64) { + mi.ClientPageLoadDuration.With(prometheus.Labels{ + "platform": platform, + "agent": agent, + }, userID).Observe(elapsed) } func (mi *MetricsInterfaceImpl) ObserveClientChannelSwitchDuration(platform, agent, fresh string, elapsed float64) { @@ -1946,7 +1956,7 @@ func (mi *MetricsInterfaceImpl) ObserveMobileClientTeamSwitchDuration(platform s mi.MobileClientTeamSwitchDuration.With(prometheus.Labels{"platform": platform}).Observe(elapsed) } -func (mi *MetricsInterfaceImpl) ObserveMobileClientSessionMetadata(version string, platform string, value float64, notificationDisabled string) { +func (mi *MetricsInterfaceImpl) ObserveMobileClientSessionMetadata(version, platform string, value float64, notificationDisabled string) { mi.MobileClientSessionMetadataGauge.With(prometheus.Labels{"version": version, "platform": platform, "notifications_disabled": notificationDisabled}).Set(value) }