Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions server/einterfaces/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,13 @@ type MetricsInterface interface {
ObserveAccessControlExpressionCompileDuration(value float64)
ObserveAccessControlEvaluateDuration(value float64)
IncrementAccessControlCacheInvalidation()

// Auto-translation metrics
ObserveAutoTranslateTranslateDuration(objectType string, elapsed float64)
ObserveAutoTranslateLinguaDetectionDuration(elapsed float64)
ObserveAutoTranslateProviderCallDuration(provider, result string, elapsed float64)
SetAutoTranslateQueueDepth(depth float64)
ObserveAutoTranslateWorkerTaskDuration(elapsed float64)
AddAutoTranslateRecoveryStuckFound(count float64)
IncrementAutoTranslateNormHash(result string)
}
35 changes: 35 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.

114 changes: 114 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 (
MetricsSubsystemClientsWeb = "webapp"
MetricsSubsystemClientsDesktopApp = "desktopapp"
MetricsSubsystemAccessControl = "access_control"
MetricsSubsystemAutoTranslation = "autotranslation"
MetricsCloudInstallationLabel = "installationId"
MetricsCloudDatabaseClusterLabel = "databaseClusterName"
MetricsCloudInstallationGroupLabel = "installationGroupId"
Expand Down Expand Up @@ -238,6 +239,15 @@ type MetricsInterfaceImpl struct {
AccessControlEvaluateDuration prometheus.Histogram
AccessControlSearchQueryDuration prometheus.Histogram
AccessControlCacheInvalidation prometheus.Counter

// Auto-translation metrics
AutoTranslateTranslateDuration *prometheus.HistogramVec
AutoTranslateLinguaDetectionDuration prometheus.Histogram
AutoTranslateProviderCallDuration *prometheus.HistogramVec
AutoTranslateQueueDepth prometheus.Gauge
AutoTranslateWorkerTaskDuration prometheus.Histogram
AutoTranslateRecoveryStuckFound prometheus.Counter
AutoTranslateNormHashCounter *prometheus.CounterVec
}

func init() {
Expand Down Expand Up @@ -1577,6 +1587,79 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf
})
m.Registry.MustRegister(m.AccessControlCacheInvalidation)

// Auto-translation Subsystem
m.AutoTranslateTranslateDuration = prometheus.NewHistogramVec(
withLabels(prometheus.HistogramOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemAutoTranslation,
Name: "translate_duration_seconds",
Help: "Duration of the Translate() function (latency impact on post create/edit)",
Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1.0},
}),
[]string{"object_type"},
)
m.Registry.MustRegister(m.AutoTranslateTranslateDuration)

m.AutoTranslateLinguaDetectionDuration = prometheus.NewHistogram(withLabels(prometheus.HistogramOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemAutoTranslation,
Name: "lingua_detection_duration_seconds",
Help: "Duration of lingua-go language detection",
Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1.0},
}))
m.Registry.MustRegister(m.AutoTranslateLinguaDetectionDuration)

m.AutoTranslateProviderCallDuration = prometheus.NewHistogramVec(
withLabels(prometheus.HistogramOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemAutoTranslation,
Name: "provider_call_duration_seconds",
Help: "Duration of translation provider API calls",
Buckets: []float64{0.1, 0.25, 0.5, 1, 2, 4, 8, 15, 30, 60},
}),
[]string{"provider", "result"},
)
m.Registry.MustRegister(m.AutoTranslateProviderCallDuration)

m.AutoTranslateQueueDepth = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemAutoTranslation,
Name: "queue_depth_total",
Help: "Current number of translation tasks waiting in worker queue",
ConstLabels: additionalLabels,
})
m.Registry.MustRegister(m.AutoTranslateQueueDepth)

m.AutoTranslateWorkerTaskDuration = prometheus.NewHistogram(withLabels(prometheus.HistogramOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemAutoTranslation,
Name: "worker_task_duration_seconds",
Help: "Duration for workers to process individual translation tasks",
Buckets: []float64{0.1, 0.5, 1, 2, 5, 10, 30, 60},
}))
m.Registry.MustRegister(m.AutoTranslateWorkerTaskDuration)

m.AutoTranslateRecoveryStuckFound = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemAutoTranslation,
Name: "recovery_stuck_found_total",
Help: "Total number of stuck translations found by recovery sweep",
ConstLabels: additionalLabels,
})
m.Registry.MustRegister(m.AutoTranslateRecoveryStuckFound)

m.AutoTranslateNormHashCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemAutoTranslation,
Name: "normhash_total",
Help: "Translation reuse via normhash (hit=reused, miss=retranslated)",
ConstLabels: additionalLabels,
},
[]string{"result"},
)
m.Registry.MustRegister(m.AutoTranslateNormHashCounter)

return m
}

Expand Down Expand Up @@ -2195,6 +2278,37 @@ func (mi *MetricsInterfaceImpl) IncrementAccessControlCacheInvalidation() {
mi.AccessControlCacheInvalidation.Inc()
}

func (mi *MetricsInterfaceImpl) ObserveAutoTranslateTranslateDuration(objectType string, elapsed float64) {
mi.AutoTranslateTranslateDuration.With(prometheus.Labels{"object_type": objectType}).Observe(elapsed)
}

func (mi *MetricsInterfaceImpl) ObserveAutoTranslateLinguaDetectionDuration(elapsed float64) {
mi.AutoTranslateLinguaDetectionDuration.Observe(elapsed)
}

func (mi *MetricsInterfaceImpl) ObserveAutoTranslateProviderCallDuration(provider, result string, elapsed float64) {
mi.AutoTranslateProviderCallDuration.With(prometheus.Labels{
"provider": provider,
"result": result,
}).Observe(elapsed)
}

func (mi *MetricsInterfaceImpl) SetAutoTranslateQueueDepth(depth float64) {
mi.AutoTranslateQueueDepth.Set(depth)
}

func (mi *MetricsInterfaceImpl) ObserveAutoTranslateWorkerTaskDuration(elapsed float64) {
mi.AutoTranslateWorkerTaskDuration.Observe(elapsed)
}

func (mi *MetricsInterfaceImpl) AddAutoTranslateRecoveryStuckFound(count float64) {
mi.AutoTranslateRecoveryStuckFound.Add(count)
}

func (mi *MetricsInterfaceImpl) IncrementAutoTranslateNormHash(result string) {
mi.AutoTranslateNormHashCounter.With(prometheus.Labels{"result": result}).Inc()
}

func (mi *MetricsInterfaceImpl) ClearMobileClientSessionMetadata() {
mi.MobileClientSessionMetadataGauge.Reset()
}
Expand Down
5 changes: 5 additions & 0 deletions server/public/shared/httpservice/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ var reservedIPRanges []*net.IPNet
// IsReservedIP checks whether the target IP belongs to reserved IP address ranges to avoid SSRF attacks to the internal
// network of the Mattermost server
func IsReservedIP(ip net.IP) bool {
// Canonicalize IPv4-mapped IPv6 addresses (e.g., ::ffff:127.0.0.1) to their
// native IPv4 form so that IPv4 CIDR ranges match correctly.
if ip4 := ip.To4(); ip4 != nil {
ip = ip4
}
for _, ipRange := range reservedIPRanges {
if ipRange.Contains(ip) {
return true
Expand Down
11 changes: 11 additions & 0 deletions server/public/shared/httpservice/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ func TestIsReservedIP(t *testing.T) {
{"127.120.6.3", net.IPv4(127, 120, 6, 3), true},
{"8.8.8.8", net.IPv4(8, 8, 8, 8), false},
{"9.9.9.9", net.IPv4(9, 9, 9, 8), false},
// IPv4-mapped IPv6 addresses should be detected as reserved
{"::ffff:127.0.0.1", net.ParseIP("::ffff:127.0.0.1"), true},
{"::ffff:192.168.1.1", net.ParseIP("::ffff:192.168.1.1"), true},
{"::ffff:10.0.0.1", net.ParseIP("::ffff:10.0.0.1"), true},
{"::ffff:169.254.169.254", net.ParseIP("::ffff:169.254.169.254"), true},
{"::ffff:8.8.8.8", net.ParseIP("::ffff:8.8.8.8"), false},
// Pure IPv6 reserved addresses
{"::1", net.ParseIP("::1"), true},
{"fe80::1", net.ParseIP("fe80::1"), true},
// Public IPv6
{"2607:f8b0:4004:800::200e", net.ParseIP("2607:f8b0:4004:800::200e"), false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading