Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2024-05-09 20:49:02 +02:00
коммит произвёл GitHub
родитель 099f704d4f
Коммит 5590e1604a
15 изменённых файлов: 714 добавлений и 0 удалений

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

@@ -331,6 +331,7 @@ func Init(srv *app.Server) (*API, error) {
api.InitReports()
api.InitLimits()
api.InitOutgoingOAuthConnection()
api.InitClientPerformanceMetrics()
srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))

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

@@ -359,6 +359,25 @@ func SetupWithServerOptions(tb testing.TB, options []app.Option) *TestHelper {
return th
}
func SetupEnterpriseWithServerOptions(tb testing.TB, options []app.Option) *TestHelper {
if testing.Short() {
tb.SkipNow()
}
if mainHelper == nil {
tb.SkipNow()
}
dbStore := mainHelper.GetStore()
dbStore.DropAllTables()
dbStore.MarkSystemRanUnitTests()
mainHelper.PreloadMigrations()
searchEngine := mainHelper.GetSearchEngine()
th := setupTestHelper(dbStore, searchEngine, true, true, nil, options)
th.InitLogin()
return th
}
func (th *TestHelper) ShutdownApp() {
done := make(chan bool)
go func() {

38
server/channels/api4/metrics.go Обычный файл
Просмотреть файл

@@ -0,0 +1,38 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"encoding/json"
"net/http"
"github.com/mattermost/mattermost/server/public/model"
)
func (api *API) InitClientPerformanceMetrics() {
api.BaseRoutes.APIRoot.Handle("/perf", api.APISessionRequired(submitPerformanceReport)).Methods("POST")
}
func submitPerformanceReport(c *Context, w http.ResponseWriter, r *http.Request) {
// we return early if server does not have any metrics infra available
if c.App.Metrics() == nil || !*c.App.Config().MetricsSettings.EnableClientMetrics {
return
}
var report model.PerformanceReport
if jsonErr := json.NewDecoder(r.Body).Decode(&report); jsonErr != nil {
c.SetInvalidParamWithErr("submitPerformanceReport", jsonErr)
return
}
if err := report.IsValid(); err != nil {
c.SetInvalidParamWithErr("submitPerformanceReport", err)
return
}
if appErr := c.App.RegisterPerformanceReport(c.AppContext, &report); appErr != nil {
c.Err = appErr
return
}
}

158
server/channels/api4/metrics_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,158 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"net/http"
"testing"
"time"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
"github.com/mattermost/mattermost/server/v8/channels/app"
"github.com/mattermost/mattermost/server/v8/channels/app/platform"
"github.com/mattermost/mattermost/server/v8/einterfaces"
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
"github.com/stretchr/testify/require"
)
func setupMetricsMock() *mocks.MetricsInterface {
metricsMock := &mocks.MetricsInterface{}
metricsMock.On("IncrementWebsocketEvent", mock.AnythingOfType("model.WebsocketEventType")).Return()
metricsMock.On("IncrementWebSocketBroadcastBufferSize", mock.AnythingOfType("string"), mock.AnythingOfType("float64")).Return()
metricsMock.On("DecrementWebSocketBroadcastBufferSize", mock.AnythingOfType("string"), mock.AnythingOfType("float64")).Return()
metricsMock.On("IncrementMemCacheInvalidationCounter", mock.AnythingOfType("string")).Return()
metricsMock.On("IncrementMemCacheMissCounter", mock.AnythingOfType("string")).Return()
metricsMock.On("IncrementMemCacheHitCounter", mock.AnythingOfType("string")).Return()
metricsMock.On("GetLoggerMetricsCollector").Return(nil)
metricsMock.On("IncrementMemCacheHitCounterSession").Return()
metricsMock.On("IncrementHTTPError").Return()
metricsMock.On("IncrementHTTPRequest").Return()
metricsMock.On("ObserveAPIEndpointDuration", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("float64")).Return()
metricsMock.On("Register").Return()
return metricsMock
}
func TestSubmitMetrics(t *testing.T) {
t.Run("unauthenticated user should not submit metrics", func(t *testing.T) {
th := Setup(t)
defer th.TearDown()
_, err := th.Client.Logout(th.Context.Context())
require.NoError(t, err)
resp, err := th.Client.SubmitClientMetrics(th.Context.Context(), nil)
require.Error(t, err)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
// if the metrics is not enabled on server, we don't want to return
// an error code.
t.Run("metrics not enabled", func(t *testing.T) {
th := Setup(t)
defer th.TearDown()
resp, err := th.Client.SubmitClientMetrics(th.Context.Context(), nil)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
})
t.Run("metrics enabled but invalid version", func(t *testing.T) {
metricsMock := setupMetricsMock()
platform.RegisterMetricsInterface(func(_ *platform.PlatformService, _, _ string) einterfaces.MetricsInterface {
return metricsMock
})
t.Cleanup(func() {
platform.RegisterMetricsInterface(func(_ *platform.PlatformService, _, _ string) einterfaces.MetricsInterface {
return nil
})
})
th := SetupEnterpriseWithServerOptions(t, []app.Option{app.StartMetrics})
defer th.TearDown()
// enable metrics and add the license
th.App.Srv().SetLicense(model.NewTestLicense())
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.MetricsSettings.Enable = true })
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.MetricsSettings.ListenAddress = ":0" })
resp, err := th.Client.SubmitClientMetrics(th.Context.Context(), &model.PerformanceReport{
Version: "0.1",
})
require.Error(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
t.Run("metrics enabled and valid", func(t *testing.T) {
metricsMock := setupMetricsMock()
metricsMock.On("IncrementClientLongTasks", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("float64")).Return()
platform.RegisterMetricsInterface(func(_ *platform.PlatformService, _, _ string) einterfaces.MetricsInterface {
return metricsMock
})
t.Cleanup(func() {
platform.RegisterMetricsInterface(func(_ *platform.PlatformService, _, _ string) einterfaces.MetricsInterface {
return nil
})
})
th := SetupEnterpriseWithServerOptions(t, []app.Option{app.StartMetrics})
defer th.TearDown()
// enable metrics and add the license
th.App.Srv().SetLicense(model.NewTestLicense())
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.MetricsSettings.Enable = true })
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.MetricsSettings.ListenAddress = ":0" })
resp, err := th.Client.SubmitClientMetrics(th.Context.Context(), &model.PerformanceReport{
Version: "0.1",
Start: time.Now().Add(-1 * time.Minute).UnixMilli(),
End: time.Now().UnixMilli(),
Counters: []*model.MetricSample{
{Metric: model.ClientLongTasks, Value: 1},
},
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
})
t.Run("metrics enabled but client metrics are disabled", func(t *testing.T) {
metricsMock := setupMetricsMock()
platform.RegisterMetricsInterface(func(_ *platform.PlatformService, _, _ string) einterfaces.MetricsInterface {
return metricsMock
})
t.Cleanup(func() {
platform.RegisterMetricsInterface(func(_ *platform.PlatformService, _, _ string) einterfaces.MetricsInterface {
return nil
})
})
th := SetupEnterpriseWithServerOptions(t, []app.Option{app.StartMetrics})
defer th.TearDown()
// enable metrics and add the license
th.App.Srv().SetLicense(model.NewTestLicense())
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.MetricsSettings.Enable = true })
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.MetricsSettings.EnableClientMetrics = false })
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.MetricsSettings.ListenAddress = ":0" })
resp, err := th.Client.SubmitClientMetrics(th.Context.Context(), &model.PerformanceReport{
Version: "0.1",
Start: time.Now().Add(-1 * time.Minute).UnixMilli(),
End: time.Now().UnixMilli(),
Counters: []*model.MetricSample{
{Metric: model.ClientLongTasks, Value: 1},
},
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
})
}

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

@@ -1004,6 +1004,7 @@ type AppIface interface {
RegenOutgoingWebhookToken(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError)
RegenerateOAuthAppSecret(app *model.OAuthApp) (*model.OAuthApp, *model.AppError)
RegenerateTeamInviteId(teamID string) (*model.Team, *model.AppError)
RegisterPerformanceReport(rctx request.CTX, report *model.PerformanceReport) *model.AppError
RegisterPluginCommand(pluginID string, command *model.Command) error
RegisterPluginForSharedChannels(rctx request.CTX, opts model.RegisterPluginOpts) (remoteID string, err error)
ReloadConfig() error

51
server/channels/app/metrics.go Обычный файл
Просмотреть файл

@@ -0,0 +1,51 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
)
func (a *App) RegisterPerformanceReport(rctx request.CTX, report *model.PerformanceReport) *model.AppError {
if a.Metrics() == nil {
return nil
}
commonLabels := report.ProcessLabels()
for _, c := range report.Counters {
switch c.Metric {
case model.ClientLongTasks:
a.Metrics().IncrementClientLongTasks(commonLabels["platform"], commonLabels["agent"], float64(c.Value))
default:
// we intentionally skip unknown metrics
}
}
for _, h := range report.Histograms {
switch h.Metric {
case model.ClientTimeToFirstByte:
a.Metrics().ObserveClientTimeToFirstByte(commonLabels["platform"], commonLabels["agent"], float64(h.Value))
case model.ClientFirstContentfulPaint:
a.Metrics().ObserveClientFirstContentfulPaint(commonLabels["platform"], commonLabels["agent"], float64(h.Value))
case model.ClientLargestContentfulPaint:
a.Metrics().ObserveClientLargestContentfulPaint(commonLabels["platform"], commonLabels["agent"], float64(h.Value))
case model.ClientInteractionToNextPaint:
a.Metrics().ObserveClientInteractionToNextPaint(commonLabels["platform"], commonLabels["agent"], float64(h.Value))
case model.ClientCumulativeLayoutShift:
a.Metrics().ObserveClientCumulativeLayoutShift(commonLabels["platform"], commonLabels["agent"], float64(h.Value))
case model.ClientChannelSwitchDuration:
a.Metrics().ObserveClientChannelSwitchDuration(commonLabels["platform"], commonLabels["agent"], float64(h.Value))
case model.ClientTeamSwitchDuration:
a.Metrics().ObserveClientTeamSwitchDuration(commonLabels["platform"], commonLabels["agent"], float64(h.Value))
case model.ClientRHSLoadDuration:
a.Metrics().ObserveClientRHSLoadDuration(commonLabels["platform"], commonLabels["agent"], float64(h.Value))
default:
// we intentionally skip unknown metrics
}
}
return nil
}

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

@@ -14095,6 +14095,28 @@ func (a *OpenTracingAppLayer) RegenerateTeamInviteId(teamID string) (*model.Team
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) RegisterPerformanceReport(rctx request.CTX, report *model.PerformanceReport) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RegisterPerformanceReport")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0 := a.app.RegisterPerformanceReport(rctx, report)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))
ext.Error.Set(span, true)
}
return resultVar0
}
func (a *OpenTracingAppLayer) RegisterPluginCommand(pluginID string, command *model.Command) error {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RegisterPluginCommand")