Files
team/server/channels/app/metrics_test.go
b4975f4527 [MM-69590] Remove NotificationMonitoring feature flag (#37386)
* [MM-69590] Remove NotificationMonitoring feature flag

The NotificationMonitoring feature flag shipped and defaulted true in
v9.9. Remove the flag and all conditional gating so notification
delivery metrics collection is permanently enabled, gated only by the
MetricsSettings.EnableNotificationMetrics admin setting.

Co-authored-by: mattermost-code <[email protected]>

* [MM-69590] Cover notification metrics client config

Assert EnableNotificationMetrics client config prop tracks the
MetricsSettings.EnableNotificationMetrics admin setting now that the
NotificationMonitoring feature flag gate is removed.

Co-authored-by: mattermost-code <[email protected]>

* [MM-69590] Cover notification counter gating

Add app-layer coverage asserting CountNotification increments the
notification counter only when MetricsSettings.EnableNotificationMetrics
is set, exercising the un-gated notificationMetricsDisabled path.

Co-authored-by: mattermost-code <[email protected]>

* [MM-69590] Cover websocket notification counter gating

Assert the websocket notification counter increments via the posted-ack
broadcast hook only when MetricsSettings.EnableNotificationMetrics is
set, exercising the un-gated incrementWebsocketCounter path.

Co-authored-by: mattermost-code <[email protected]>

* Address PR feedback: 0 answered, 1 resolved, 0 declined

Document that MetricsSettings.EnableNotificationMetrics must be set to
true for notification monitoring, matching the code gating and the
push-notification-health-targets doc.

---------

Co-authored-by: mattermost-code <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-08 16:00:49 -03:00

210 lines
6.3 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"fmt"
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/app/platform"
"github.com/mattermost/mattermost/server/v8/enterprise/metrics"
"github.com/prometheus/client_golang/prometheus"
prometheusModels "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/require"
)
func configureMetrics(th *TestHelper) {
th.App.Srv().SetLicense(nil) // clear license
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.MetricsSettings.Enable = true
*cfg.MetricsSettings.ListenAddress = ":0"
})
th.App.Srv().SetLicense(model.NewTestLicense("metrics"))
}
func TestMobileMetrics(t *testing.T) {
mainHelper.Parallel(t)
th := SetupEnterprise(t, StartMetrics)
configureMetrics(th)
mi := th.App.Metrics()
miImpl, ok := mi.(*metrics.MetricsInterfaceImpl)
require.True(t, ok, fmt.Sprintf("App.Metrics is not *MetricsInterfaceImpl, but %T", mi))
m := &prometheusModels.Metric{}
for _, platform := range []string{"ios", "android"} {
ttcc := []struct {
name string
histogramVec *prometheus.HistogramVec
elapsed float64
metricName model.MetricType
}{
{
name: "load duration",
histogramVec: miImpl.MobileClientLoadDuration,
elapsed: 5001,
metricName: model.MobileClientLoadDuration,
},
{
name: "channel switch duration",
histogramVec: miImpl.MobileClientChannelSwitchDuration,
elapsed: 501,
metricName: model.MobileClientChannelSwitchDuration,
},
{
name: "team switch duration",
histogramVec: miImpl.MobileClientTeamSwitchDuration,
elapsed: 301,
metricName: model.MobileClientTeamSwitchDuration,
},
}
histograms := []*model.MetricSample{}
var start float64 = 125
end := start + float64(len(ttcc))
// Precheck
for _, tc := range ttcc {
actualMetric, err := tc.histogramVec.GetMetricWith(prometheus.Labels{"platform": platform})
require.NoError(t, err)
require.NoError(t, actualMetric.(prometheus.Histogram).Write(m))
require.Equal(t, uint64(0), m.Histogram.GetSampleCount())
require.Equal(t, 0.0, m.Histogram.GetSampleSum())
histograms = append(histograms, &model.MetricSample{
Metric: tc.metricName,
Value: tc.elapsed,
})
}
appErr := th.App.RegisterPerformanceReport(th.Context, &model.PerformanceReport{
Version: "0.1.0",
ClientID: "",
Labels: map[string]string{
"platform": platform,
},
Start: start,
End: end,
Counters: []*model.MetricSample{},
Histograms: histograms,
})
require.Nil(t, appErr)
// After check
for _, tc := range ttcc {
actualMetric, err := tc.histogramVec.GetMetricWith(prometheus.Labels{"platform": platform})
require.NoError(t, err)
require.NoError(t, actualMetric.(prometheus.Histogram).Write(m))
require.Equal(t, uint64(1), m.Histogram.GetSampleCount())
require.InDelta(t, tc.elapsed/1000, m.Histogram.GetSampleSum(), 0.001, "not equal value in %s", tc.name)
}
}
}
func TestCountNotificationMetrics(t *testing.T) {
mainHelper.Parallel(t)
th := SetupEnterprise(t, StartMetrics)
configureMetrics(th)
mi := th.App.Metrics()
miImpl, ok := mi.(*metrics.MetricsInterfaceImpl)
require.True(t, ok, fmt.Sprintf("App.Metrics is not *MetricsInterfaceImpl, but %T", mi))
counterValue := func() float64 {
counter, err := miImpl.NotificationTotalCounters.GetMetricWith(prometheus.Labels{
"type": string(model.NotificationTypePush),
"platform": "ios",
})
require.NoError(t, err)
m := &prometheusModels.Metric{}
require.NoError(t, counter.Write(m))
return m.Counter.GetValue()
}
t.Run("counts when notification metrics are enabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.MetricsSettings.EnableNotificationMetrics = true
})
before := counterValue()
th.App.CountNotification(model.NotificationTypePush, "ios")
require.Equal(t, before+1, counterValue())
})
t.Run("does not count when notification metrics are disabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.MetricsSettings.EnableNotificationMetrics = false
})
before := counterValue()
th.App.CountNotification(model.NotificationTypePush, "ios")
require.Equal(t, before, counterValue())
})
}
func TestWebsocketNotificationCounter(t *testing.T) {
mainHelper.Parallel(t)
th := SetupEnterprise(t, StartMetrics)
configureMetrics(th)
mi := th.App.Metrics()
miImpl, ok := mi.(*metrics.MetricsInterfaceImpl)
require.True(t, ok, fmt.Sprintf("App.Metrics is not *MetricsInterfaceImpl, but %T", mi))
counterValue := func() float64 {
counter, err := miImpl.NotificationTotalCounters.GetMetricWith(prometheus.Labels{
"type": string(model.NotificationTypeWebsocket),
"platform": model.NotificationNoPlatform,
})
require.NoError(t, err)
m := &prometheusModels.Metric{}
require.NoError(t, counter.Write(m))
return m.Counter.GetValue()
}
hook := &postedAckBroadcastHook{}
userID := model.NewId()
webConn := &platform.WebConn{
UserId: userID,
Platform: th.Server.Platform(),
PostedAck: true,
}
webConn.Active.Store(true)
webConn.SetSession(&model.Session{})
// Process an acked broadcast that reaches incrementWebsocketCounter.
ackPostedBroadcast := func() {
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
err := hook.Process(msg, webConn, map[string]any{
"posted_user_id": model.NewId(),
"channel_type": model.ChannelTypeOpen,
"users": []string{userID},
})
require.NoError(t, err)
require.True(t, msg.Event().GetData()["should_ack"].(bool))
}
t.Run("counts when notification metrics are enabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.MetricsSettings.EnableNotificationMetrics = true
})
before := counterValue()
ackPostedBroadcast()
require.Equal(t, before+1, counterValue())
})
t.Run("does not count when notification metrics are disabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.MetricsSettings.EnableNotificationMetrics = false
})
before := counterValue()
ackPostedBroadcast()
require.Equal(t, before, counterValue())
})
}