Source available metrics (#24879)
* Expose metrics under a source available license * do not assume Cluster() * allow metrics if licensed or dev * temporary vet override * simplify BULID_TAGS handling * auto clean old imports.go * fix license listener * e2e test metrics & license semantics * update from enterprise * switch back to mattermost-govet/v2@new now * update metrics from upstream * Update license_spec.js Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com> * Update license_spec.js Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com> * Update e2e-tests/cypress/tests/integration/channels/enterprise/metrics/license_spec.js Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com> * Update e2e-tests/cypress/tests/integration/channels/enterprise/metrics/license_spec.js * split up specs * require/delete license earlier in e2e test * expanded expect to debug failures * more logging * Revert "more logging" This reverts commit 0bc513fd926c07a0c6050f4b4c742e856d8dc435. * e2e: try deleting license first * update from enterprise * toggleMetricsOn to work around license delete * eslint * ensure admin before deleting license * update from enterprise * updates from enterprise * fix cypress logging * temp: log at DEBUG for Cypress tests --------- Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com> Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
6184c36e0b
Коммит
b05093d508
81
server/enterprise/metrics/dynamic.go
Обычный файл
81
server/enterprise/metrics/dynamic.go
Обычный файл
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.enterprise for license information.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// DynamicCounter provides a CounterVec that can create new counters via label values at runtime.
|
||||
// This allows applications to create counters at runtime without locking into Prometheus as the
|
||||
// metrics manager.
|
||||
type DynamicCounter struct {
|
||||
counter *prometheus.CounterVec
|
||||
}
|
||||
|
||||
// NewDynamicCounter creates a new dynamic counter with corresponding labels.
|
||||
func NewDynamicCounter(opts prometheus.CounterOpts, labels ...string) *DynamicCounter {
|
||||
return &DynamicCounter{
|
||||
counter: prometheus.NewCounterVec(opts, labels),
|
||||
}
|
||||
}
|
||||
|
||||
// GetCounter fetches a counter associated with the label values provided. An error is
|
||||
// returned if the number of values differs from the number of labels provided when
|
||||
// creating the DynamicCounter
|
||||
func (dc *DynamicCounter) GetCounter(values ...string) (prometheus.Counter, error) {
|
||||
return dc.counter.GetMetricWithLabelValues(values...)
|
||||
}
|
||||
|
||||
// DynamicGauge provides a GaugeVec that can create new gauges via label values at runtime.
|
||||
// This allows applications to create gauges at runtime without locking into Prometheus as the
|
||||
// metrics manager.
|
||||
type DynamicGauge struct {
|
||||
gauge *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// NewDynamicGauge creates a new dynamic gauge with corresponding labels.
|
||||
func NewDynamicGauge(opts prometheus.GaugeOpts, labels ...string) *DynamicGauge {
|
||||
return &DynamicGauge{
|
||||
gauge: prometheus.NewGaugeVec(opts, labels),
|
||||
}
|
||||
}
|
||||
|
||||
// GetGauge fetches a gauge associated with the label values provided. An error is
|
||||
// returned if the number of values differs from the number of labels provided when
|
||||
// creating the DynamicGauge
|
||||
func (dg *DynamicGauge) GetGauge(values ...string) (prometheus.Gauge, error) {
|
||||
return dg.gauge.GetMetricWithLabelValues(values...)
|
||||
}
|
||||
|
||||
// LoggerMetricsCollector provides counters for server logging.
|
||||
// Implements Logr.MetricsCollector
|
||||
type LoggerMetricsCollector struct {
|
||||
queueGauge *DynamicGauge
|
||||
loggedCounters *DynamicCounter
|
||||
errorCounters *DynamicCounter
|
||||
droppedCounters *DynamicCounter
|
||||
blockedCounters *DynamicCounter
|
||||
}
|
||||
|
||||
func (c *LoggerMetricsCollector) QueueSizeGauge(target string) (mlog.Gauge, error) {
|
||||
return c.queueGauge.GetGauge(target)
|
||||
}
|
||||
|
||||
func (c *LoggerMetricsCollector) LoggedCounter(target string) (mlog.Counter, error) {
|
||||
return c.loggedCounters.GetCounter(target)
|
||||
}
|
||||
|
||||
func (c *LoggerMetricsCollector) ErrorCounter(target string) (mlog.Counter, error) {
|
||||
return c.errorCounters.GetCounter(target)
|
||||
}
|
||||
|
||||
func (c *LoggerMetricsCollector) DroppedCounter(target string) (mlog.Counter, error) {
|
||||
return c.droppedCounters.GetCounter(target)
|
||||
}
|
||||
|
||||
func (c *LoggerMetricsCollector) BlockedCounter(target string) (mlog.Counter, error) {
|
||||
return c.blockedCounters.GetCounter(target)
|
||||
}
|
||||
21
server/enterprise/metrics/main_test.go
Обычный файл
21
server/enterprise/metrics/main_test.go
Обычный файл
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.enterprise for license information.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/api4"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
||||
)
|
||||
|
||||
var mainHelper *testlib.MainHelper
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
mainHelper = testlib.NewMainHelper()
|
||||
defer mainHelper.Close()
|
||||
api4.SetMainHelper(mainHelper)
|
||||
|
||||
mainHelper.Main(m)
|
||||
}
|
||||
1369
server/enterprise/metrics/metrics.go
Обычный файл
1369
server/enterprise/metrics/metrics.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
220
server/enterprise/metrics/metrics_test.go
Обычный файл
220
server/enterprise/metrics/metrics_test.go
Обычный файл
@@ -0,0 +1,220 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.enterprise for license information.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/api4"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
prometheusModels "github.com/prometheus/client_model/go"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func configureMetrics(th *api4.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 TestMetrics(t *testing.T) {
|
||||
th := api4.SetupEnterpriseWithStoreMock(t, app.StartMetrics)
|
||||
defer th.TearDown()
|
||||
|
||||
mockStore := th.App.Srv().Platform().Store.(*mocks.Store)
|
||||
mockUserStore := mocks.UserStore{}
|
||||
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockStore.On("User").Return(&mockUserStore)
|
||||
mockStore.On("Post").Return(&mockPostStore)
|
||||
mockStore.On("System").Return(&mockSystemStore)
|
||||
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
||||
|
||||
configureMetrics(th)
|
||||
mi := th.App.Metrics()
|
||||
|
||||
_, ok := mi.(*MetricsInterfaceImpl)
|
||||
require.True(t, ok, fmt.Sprintf("App.Metrics is not *MetricsInterfaceImpl, but %T", mi))
|
||||
|
||||
mi.IncrementHTTPRequest()
|
||||
mi.IncrementHTTPError()
|
||||
|
||||
mi.IncrementPostFileAttachment(5)
|
||||
mi.IncrementPostCreate()
|
||||
mi.IncrementPostSentEmail()
|
||||
mi.IncrementPostSentPush()
|
||||
mi.IncrementPostBroadcast()
|
||||
|
||||
mi.IncrementLogin()
|
||||
mi.IncrementLoginFail()
|
||||
|
||||
mi.IncrementClusterRequest()
|
||||
mi.ObserveClusterRequestDuration(2.0)
|
||||
mi.IncrementClusterEventType(model.ClusterEventPublish)
|
||||
|
||||
loggerCollector := mi.GetLoggerMetricsCollector()
|
||||
g, err := loggerCollector.QueueSizeGauge("_logr")
|
||||
require.NoError(t, err)
|
||||
g.Set(59)
|
||||
|
||||
c, err := loggerCollector.LoggedCounter("_logr")
|
||||
require.NoError(t, err)
|
||||
c.Inc()
|
||||
|
||||
c, err = loggerCollector.ErrorCounter("_logr")
|
||||
require.NoError(t, err)
|
||||
c.Inc()
|
||||
|
||||
c, err = loggerCollector.DroppedCounter("_logr")
|
||||
require.NoError(t, err)
|
||||
c.Inc()
|
||||
|
||||
c, err = loggerCollector.BlockedCounter("_logr")
|
||||
require.NoError(t, err)
|
||||
c.Inc()
|
||||
}
|
||||
|
||||
func TestPluginMetrics(t *testing.T) {
|
||||
th := api4.SetupEnterprise(t, app.StartMetrics)
|
||||
defer th.TearDown()
|
||||
|
||||
configureMetrics(th)
|
||||
mi := th.App.Metrics()
|
||||
|
||||
miImpl, ok := mi.(*MetricsInterfaceImpl)
|
||||
require.True(t, ok, fmt.Sprintf("App.Metrics is not *MetricsInterfaceImpl, but %T", mi))
|
||||
|
||||
t.Run("test ObservePluginHookDuration", func(t *testing.T) {
|
||||
pluginID := "id_"
|
||||
hookName := "hook_"
|
||||
elapsed := 999.1
|
||||
m := &prometheusModels.Metric{}
|
||||
|
||||
for _, success := range []bool{true, false} {
|
||||
actualMetric, err := miImpl.PluginHookTimeHistogram.GetMetricWith(prometheus.Labels{"plugin_id": pluginID, "hook_name": hookName, "success": strconv.FormatBool(success)})
|
||||
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())
|
||||
|
||||
mi.ObservePluginHookDuration(pluginID, hookName, success, elapsed)
|
||||
actualMetric, err = miImpl.PluginHookTimeHistogram.GetMetricWith(prometheus.Labels{"plugin_id": pluginID, "hook_name": hookName, "success": strconv.FormatBool(success)})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, actualMetric.(prometheus.Histogram).Write(m))
|
||||
require.Equal(t, uint64(1), m.Histogram.GetSampleCount())
|
||||
require.InDelta(t, elapsed, m.Histogram.GetSampleSum(), 0.001)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test ObservePluginAPIDuration", func(t *testing.T) {
|
||||
pluginID := "id_"
|
||||
apiName := "api_"
|
||||
elapsed := 999.1
|
||||
m := &prometheusModels.Metric{}
|
||||
|
||||
for _, success := range []bool{true, false} {
|
||||
actualMetric, err := miImpl.PluginAPITimeHistogram.GetMetricWith(prometheus.Labels{"plugin_id": pluginID, "api_name": apiName, "success": strconv.FormatBool(success)})
|
||||
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())
|
||||
|
||||
mi.ObservePluginAPIDuration(pluginID, apiName, success, elapsed)
|
||||
actualMetric, err = miImpl.PluginAPITimeHistogram.GetMetricWith(prometheus.Labels{"plugin_id": pluginID, "api_name": apiName, "success": strconv.FormatBool(success)})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, actualMetric.(prometheus.Histogram).Write(m))
|
||||
require.Equal(t, uint64(1), m.Histogram.GetSampleCount())
|
||||
require.InDelta(t, elapsed, m.Histogram.GetSampleSum(), 0.001)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test ObservePluginMultiHookIterationDuration", func(t *testing.T) {
|
||||
pluginID := "id_"
|
||||
elapsed := 999.1
|
||||
m := &prometheusModels.Metric{}
|
||||
|
||||
actualMetric, err := miImpl.PluginMultiHookTimeHistogram.GetMetricWith(prometheus.Labels{"plugin_id": pluginID})
|
||||
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())
|
||||
|
||||
mi.ObservePluginMultiHookIterationDuration(pluginID, elapsed)
|
||||
actualMetric, err = miImpl.PluginMultiHookTimeHistogram.GetMetricWith(prometheus.Labels{"plugin_id": pluginID})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, actualMetric.(prometheus.Histogram).Write(m))
|
||||
require.Equal(t, uint64(1), m.Histogram.GetSampleCount())
|
||||
require.InDelta(t, elapsed, m.Histogram.GetSampleSum(), 0.001)
|
||||
})
|
||||
|
||||
t.Run("test ObservePluginMultiHookDuration", func(t *testing.T) {
|
||||
elapsed := 50.0
|
||||
m := &prometheusModels.Metric{}
|
||||
|
||||
require.NoError(t, miImpl.PluginMultiHookServerTimeHistogram.Write(m))
|
||||
require.InDelta(t, 0.0, m.Histogram.GetSampleSum(), 0.001)
|
||||
|
||||
mi.ObservePluginMultiHookDuration(elapsed)
|
||||
require.NoError(t, miImpl.PluginMultiHookServerTimeHistogram.Write(m))
|
||||
require.InDelta(t, elapsed, m.Histogram.GetSampleSum(), 0.001)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtractDBCluster(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
driver string
|
||||
connectionStr string
|
||||
expectedClusterName string
|
||||
}{
|
||||
{
|
||||
description: "postgres full",
|
||||
driver: "postgres",
|
||||
connectionStr: "postgres://user1234:password1234@rds-cluster-multitenant-1234-postgres.cluster-abcd.us-east-1.rds.amazonaws.com:5432/cloud?connect_timeout=10",
|
||||
expectedClusterName: "rds-cluster-multitenant-1234-postgres",
|
||||
},
|
||||
{
|
||||
description: "postgres no credentials",
|
||||
driver: "postgres",
|
||||
connectionStr: "postgres://rds-cluster-multitenant-1234-postgres.cluster-abcd.us-east-1.rds.amazonaws.com:5432/cloud?connect_timeout=10",
|
||||
expectedClusterName: "rds-cluster-multitenant-1234-postgres",
|
||||
},
|
||||
{
|
||||
description: "mysql full",
|
||||
driver: "mysql",
|
||||
connectionStr: "mysql://user1234:password1234@tcp(rds-cluster-multitenant-1234-mysql.cluster-abcd.us-east-1.rds.amazonaws.com:3306)/cloud?charset=utf8mb4%2Cutf8&readTimeout=30s&writeTimeout=30s&tls=skip-verify",
|
||||
expectedClusterName: "rds-cluster-multitenant-1234-mysql",
|
||||
},
|
||||
{
|
||||
description: "mysql no credentials",
|
||||
driver: "mysql",
|
||||
connectionStr: "mysql://tcp(rds-cluster-multitenant-1234-mysql.cluster-abcd.us-east-1.rds.amazonaws.com:3306)/cloud?charset=utf8mb4%2Cutf8&readTimeout=30s&writeTimeout=30s&tls=skip-verify",
|
||||
expectedClusterName: "rds-cluster-multitenant-1234-mysql",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
host, err := extractDBCluster(tc.driver, tc.connectionStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.expectedClusterName, host)
|
||||
})
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user