mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-27 04:10:28 +01:00
Compare commits
9 Commits
metricsExp
...
host-url
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7ffbfe063 | ||
|
|
96a83707c1 | ||
|
|
829ecd01a0 | ||
|
|
93e92be231 | ||
|
|
5011246bc9 | ||
|
|
f88af61a7c | ||
|
|
6935748fd3 | ||
|
|
16fc710518 | ||
|
|
745d3bc772 |
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -47,13 +48,14 @@ func NewAnomalyRule(
|
||||
p *ruletypes.PostableRule,
|
||||
querier querier.Querier,
|
||||
logger *slog.Logger,
|
||||
externalURL *url.URL,
|
||||
opts ...baserules.RuleOption,
|
||||
) (*AnomalyRule, error) {
|
||||
logger.Info("creating new AnomalyRule", slog.String("rule.id", id))
|
||||
|
||||
opts = append(opts, baserules.WithLogger(logger))
|
||||
|
||||
baseRule, err := baserules.NewBaseRule(id, orgID, p, opts...)
|
||||
baseRule, err := baserules.NewBaseRule(id, orgID, p, externalURL, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package rules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -120,6 +121,7 @@ func TestAnomalyRule_NoData_AlertOnAbsent(t *testing.T) {
|
||||
&postableRule,
|
||||
nil,
|
||||
logger,
|
||||
mustParseURL(t, "http://localhost:8000"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -247,7 +249,8 @@ func TestAnomalyRule_NoData_AbsentFor(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
rule, err := NewAnomalyRule("test-anomaly-rule", valuer.GenerateUUID(), &postableRule, nil, logger)
|
||||
externalURL := mustParseURL(t, "http://localhost:8000")
|
||||
rule, err := NewAnomalyRule("test-anomaly-rule", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
rule.provider = &mockAnomalyProvider{
|
||||
@@ -264,3 +267,10 @@ func TestAnomalyRule_NoData_AbsentFor(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseURL(t *testing.T, raw string) *url.URL {
|
||||
t.Helper()
|
||||
u, err := url.Parse(raw)
|
||||
require.NoError(t, err)
|
||||
return u
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Rule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
|
||||
@@ -59,6 +60,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Rule,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
|
||||
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
|
||||
@@ -82,6 +84,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Rule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
|
||||
@@ -141,6 +144,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
|
||||
parsedRule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
@@ -162,6 +166,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
|
||||
parsedRule,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
@@ -181,6 +186,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
|
||||
parsedRule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
|
||||
alertmanagermock "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
@@ -51,6 +52,7 @@ func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
|
||||
fAlert := am.(*alertmanagermock.MockAlertmanager)
|
||||
// mock set notification config
|
||||
fAlert.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
fAlert.On("Config").Return(alertmanagerserver.Config{ExternalURL: mustParseURL(t, "http://localhost:8080")})
|
||||
// for saving temp alerts that are triggered via TestNotification
|
||||
if tc.ExpectAlerts > 0 {
|
||||
fAlert.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||
@@ -166,6 +168,7 @@ func TestManager_TestNotification_SendUnmatched_PromRule(t *testing.T) {
|
||||
mockAM := am.(*alertmanagermock.MockAlertmanager)
|
||||
// mock set notification config
|
||||
mockAM.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
mockAM.On("Config").Return(alertmanagerserver.Config{ExternalURL: mustParseURL(t, "http://localhost:8080")})
|
||||
// for saving temp alerts that are triggered via TestNotification
|
||||
if tc.ExpectAlerts > 0 {
|
||||
mockAM.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
amConfig "github.com/prometheus/alertmanager/config"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/statsreporter"
|
||||
@@ -48,6 +49,9 @@ type Alertmanager interface {
|
||||
// DeleteChannelByID deletes a channel for the organization.
|
||||
DeleteChannelByID(context.Context, string, valuer.UUID) error
|
||||
|
||||
// Config returns the alertmanagerserver configuration.
|
||||
Config() alertmanagerserver.Config
|
||||
|
||||
// SetConfig sets the config for the organization.
|
||||
SetConfig(context.Context, *alertmanagertypes.Config) error
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
@@ -109,6 +110,50 @@ func (_c *MockAlertmanager_Collect_Call) RunAndReturn(run func(context1 context.
|
||||
return _c
|
||||
}
|
||||
|
||||
// Config provides a mock function for the type MockAlertmanager
|
||||
func (_mock *MockAlertmanager) Config() alertmanagerserver.Config {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Config")
|
||||
}
|
||||
|
||||
var r0 alertmanagerserver.Config
|
||||
if returnFunc, ok := ret.Get(0).(func() alertmanagerserver.Config); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(alertmanagerserver.Config)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockAlertmanager_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config'
|
||||
type MockAlertmanager_Config_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Config is a helper method to define mock.On call
|
||||
func (_e *MockAlertmanager_Expecter) Config() *MockAlertmanager_Config_Call {
|
||||
return &MockAlertmanager_Config_Call{Call: _e.mock.On("Config")}
|
||||
}
|
||||
|
||||
func (_c *MockAlertmanager_Config_Call) Run(run func()) *MockAlertmanager_Config_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockAlertmanager_Config_Call) Return(config alertmanagerserver.Config) *MockAlertmanager_Config_Call {
|
||||
_c.Call.Return(config)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockAlertmanager_Config_Call) RunAndReturn(run func() alertmanagerserver.Config) *MockAlertmanager_Config_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreateChannel provides a mock function for the type MockAlertmanager
|
||||
func (_mock *MockAlertmanager) CreateChannel(context1 context.Context, s string, v alertmanagertypes.Receiver) (*alertmanagertypes.Channel, error) {
|
||||
ret := _mock.Called(context1, s, v)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerstore/sqlalertmanagerstore"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/nfmanager"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@@ -235,6 +236,10 @@ func (provider *provider) CreateChannel(ctx context.Context, orgID string, recei
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Config() alertmanagerserver.Config {
|
||||
return provider.config.Signoz.Config
|
||||
}
|
||||
|
||||
func (provider *provider) SetConfig(ctx context.Context, config *alertmanagertypes.Config) error {
|
||||
return provider.configStore.Set(ctx, config)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
tracesV3 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v3"
|
||||
@@ -226,7 +227,7 @@ func PrepareFilters(labels map[string]string, whereClauseItems []v3.FilterItem,
|
||||
return filterItems
|
||||
}
|
||||
|
||||
func PrepareLinksToTracesV5(start, end time.Time, whereClause string) string {
|
||||
func PrepareParamsForTracesV5(start, end time.Time, whereClause string) url.Values {
|
||||
|
||||
// Traces list view expects time in nanoseconds
|
||||
tr := URLShareableTimeRange{
|
||||
@@ -238,7 +239,6 @@ func PrepareLinksToTracesV5(start, end time.Time, whereClause string) string {
|
||||
options := URLShareableOptions{}
|
||||
|
||||
period, _ := json.Marshal(tr)
|
||||
urlEncodedTimeRange := url.QueryEscape(string(period))
|
||||
|
||||
linkQuery := LinkQuery{
|
||||
BuilderQuery: v3.BuilderQuery{
|
||||
@@ -265,15 +265,20 @@ func PrepareLinksToTracesV5(start, end time.Time, whereClause string) string {
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(urlData)
|
||||
compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
|
||||
compositeQuery := url.QueryEscape(string(data))
|
||||
|
||||
optionsData, _ := json.Marshal(options)
|
||||
urlEncodedOptions := url.QueryEscape(string(optionsData))
|
||||
|
||||
return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
|
||||
params := url.Values{}
|
||||
params.Set("compositeQuery", compositeQuery)
|
||||
params.Set("timeRange", string(period))
|
||||
params.Set("startTime", strconv.FormatInt(tr.Start, 10))
|
||||
params.Set("endTime", strconv.FormatInt(tr.End, 10))
|
||||
params.Set("options", string(optionsData))
|
||||
return params
|
||||
}
|
||||
|
||||
func PrepareLinksToLogsV5(start, end time.Time, whereClause string) string {
|
||||
func PrepareParamsForLogsV5(start, end time.Time, whereClause string) url.Values {
|
||||
|
||||
// Logs list view expects time in milliseconds
|
||||
tr := URLShareableTimeRange{
|
||||
@@ -285,7 +290,6 @@ func PrepareLinksToLogsV5(start, end time.Time, whereClause string) string {
|
||||
options := URLShareableOptions{}
|
||||
|
||||
period, _ := json.Marshal(tr)
|
||||
urlEncodedTimeRange := url.QueryEscape(string(period))
|
||||
|
||||
linkQuery := LinkQuery{
|
||||
BuilderQuery: v3.BuilderQuery{
|
||||
@@ -312,10 +316,15 @@ func PrepareLinksToLogsV5(start, end time.Time, whereClause string) string {
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(urlData)
|
||||
compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
|
||||
compositeQuery := url.QueryEscape(string(data))
|
||||
|
||||
optionsData, _ := json.Marshal(options)
|
||||
urlEncodedOptions := url.QueryEscape(string(optionsData))
|
||||
|
||||
return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
|
||||
params := url.Values{}
|
||||
params.Set("compositeQuery", compositeQuery)
|
||||
params.Set("timeRange", string(period))
|
||||
params.Set("startTime", strconv.FormatInt(tr.Start, 10))
|
||||
params.Set("endTime", strconv.FormatInt(tr.End, 10))
|
||||
params.Set("options", string(optionsData))
|
||||
return params
|
||||
}
|
||||
|
||||
@@ -219,25 +219,19 @@ func (m *module) GetStats(ctx context.Context, orgID valuer.UUID, req *metricsex
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
metricStats []metricsexplorertypes.Stat
|
||||
total uint64
|
||||
err error
|
||||
)
|
||||
|
||||
hasFilter := req.Filter != nil && strings.TrimSpace(req.Filter.Expression) != ""
|
||||
|
||||
if hasFilter {
|
||||
var filterWhereClause *sqlbuilder.WhereClause
|
||||
filterWhereClause, err = m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metricStats, total, err = m.fetchMetricsStatsWithSamples(ctx, req, filterWhereClause, false, req.OrderBy)
|
||||
} else {
|
||||
metricStats, total, err = m.fetchMetricsStatsWithSamplesFastPath(ctx, req, false, req.OrderBy)
|
||||
filterWhereClause, err := m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Single query to get stats with samples, timeseries counts in required sorting order
|
||||
metricStats, total, err := m.fetchMetricsStatsWithSamples(
|
||||
ctx,
|
||||
req,
|
||||
filterWhereClause,
|
||||
false,
|
||||
req.OrderBy,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -274,28 +268,21 @@ func (m *module) GetTreemap(ctx context.Context, orgID valuer.UUID, req *metrics
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hasFilter := req.Filter != nil && strings.TrimSpace(req.Filter.Expression) != ""
|
||||
filterWhereClause, err := m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var entries []metricsexplorertypes.TreemapEntry
|
||||
|
||||
resp := &metricsexplorertypes.TreemapResponse{}
|
||||
switch req.Mode {
|
||||
case metricsexplorertypes.TreemapModeSamples:
|
||||
if hasFilter {
|
||||
entries, err = m.computeSamplesTreemap(ctx, req, filterWhereClause)
|
||||
} else {
|
||||
entries, err = m.computeSamplesTreemapFastPath(ctx, req)
|
||||
}
|
||||
entries, err := m.computeSamplesTreemap(ctx, req, filterWhereClause)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Samples = entries
|
||||
default: // TreemapModeTimeSeries
|
||||
entries, err = m.computeTimeseriesTreemap(ctx, req, filterWhereClause)
|
||||
entries, err := m.computeTimeseriesTreemap(ctx, req, filterWhereClause)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1098,109 +1085,6 @@ func (m *module) fetchMetricsStatsWithSamples(
|
||||
return metricStats, total, nil
|
||||
}
|
||||
|
||||
func (m *module) fetchMetricsStatsWithSamplesFastPath(
|
||||
ctx context.Context,
|
||||
req *metricsexplorertypes.StatsRequest,
|
||||
normalized bool,
|
||||
orderBy *qbtypes.OrderBy,
|
||||
) ([]metricsexplorertypes.Stat, uint64, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "fetchMetricsStatsWithSamplesFastPath")
|
||||
|
||||
start, end, distributedTsTable, localTsTable := telemetrymetrics.WhichTSTableToUse(uint64(req.Start), uint64(req.End), nil)
|
||||
samplesTable := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
|
||||
countExp := telemetrymetrics.CountExpressionForSamplesTable(samplesTable)
|
||||
|
||||
// Timeseries counts per metric
|
||||
tsSB := sqlbuilder.NewSelectBuilder()
|
||||
tsSB.Select(
|
||||
"metric_name",
|
||||
"uniq(fingerprint) AS timeseries",
|
||||
)
|
||||
tsSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, distributedTsTable))
|
||||
tsSB.Where(tsSB.Between("unix_milli", start, end))
|
||||
tsSB.Where("NOT startsWith(metric_name, 'signoz')")
|
||||
tsSB.Where(tsSB.E("__normalized", normalized))
|
||||
tsSB.GroupBy("metric_name")
|
||||
|
||||
// Distinct metric_names from local TS table — narrows samples scan on its leading sort key
|
||||
metricNamesSB := sqlbuilder.NewSelectBuilder()
|
||||
metricNamesSB.Select("DISTINCT metric_name")
|
||||
metricNamesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, localTsTable))
|
||||
metricNamesSB.Where(metricNamesSB.Between("unix_milli", start, end))
|
||||
metricNamesSB.Where("NOT startsWith(metric_name, 'signoz')")
|
||||
metricNamesSB.Where(metricNamesSB.E("__normalized", normalized))
|
||||
|
||||
// Samples counts per metric
|
||||
samplesSB := sqlbuilder.NewSelectBuilder()
|
||||
samplesSB.Select(
|
||||
"metric_name",
|
||||
fmt.Sprintf("%s AS samples", countExp),
|
||||
)
|
||||
samplesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, samplesTable))
|
||||
samplesSB.Where(samplesSB.Between("unix_milli", req.Start, req.End))
|
||||
samplesSB.Where("NOT startsWith(metric_name, 'signoz')")
|
||||
samplesSB.Where(fmt.Sprintf("metric_name IN (%s)", samplesSB.Var(metricNamesSB)))
|
||||
samplesSB.GroupBy("metric_name")
|
||||
|
||||
cteBuilder := sqlbuilder.With(
|
||||
sqlbuilder.CTEQuery("__time_series_counts").As(tsSB),
|
||||
sqlbuilder.CTEQuery("__sample_counts").As(samplesSB),
|
||||
)
|
||||
|
||||
finalSB := cteBuilder.Select(
|
||||
"COALESCE(ts.metric_name, s.metric_name) AS metric_name",
|
||||
"COALESCE(ts.timeseries, 0) AS timeseries",
|
||||
"COALESCE(s.samples, 0) AS samples",
|
||||
"COUNT(*) OVER() AS total",
|
||||
)
|
||||
finalSB.From("__time_series_counts ts")
|
||||
finalSB.JoinWithOption(sqlbuilder.FullOuterJoin, "__sample_counts s", "ts.metric_name = s.metric_name")
|
||||
finalSB.Where("(COALESCE(ts.timeseries, 0) > 0 OR COALESCE(s.samples, 0) > 0)")
|
||||
|
||||
orderByColumn, orderDirection, err := getStatsOrderByColumn(orderBy)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
finalSB.OrderBy(
|
||||
fmt.Sprintf("%s %s", orderByColumn, strings.ToUpper(orderDirection)),
|
||||
"metric_name ASC",
|
||||
)
|
||||
finalSB.Limit(req.Limit)
|
||||
finalSB.Offset(req.Offset)
|
||||
|
||||
query, args := finalSB.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
|
||||
valueCtx := ctxtypes.SetClickhouseMaxThreads(ctx, m.config.TelemetryStore.Threads)
|
||||
db := m.telemetryStore.ClickhouseDB()
|
||||
rows, err := db.Query(valueCtx, query, args...)
|
||||
if err != nil {
|
||||
return nil, 0, errors.WrapInternalf(err, errors.CodeInternal, "failed to execute metrics stats with samples fastpath query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
metricStats := make([]metricsexplorertypes.Stat, 0)
|
||||
var total uint64
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
metricStat metricsexplorertypes.Stat
|
||||
rowTotal uint64
|
||||
)
|
||||
if err := rows.Scan(&metricStat.MetricName, &metricStat.TimeSeries, &metricStat.Samples, &rowTotal); err != nil {
|
||||
return nil, 0, errors.WrapInternalf(err, errors.CodeInternal, "failed to scan metrics stats row")
|
||||
}
|
||||
metricStats = append(metricStats, metricStat)
|
||||
total = rowTotal
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, 0, errors.WrapInternalf(err, errors.CodeInternal, "error iterating metrics stats rows")
|
||||
}
|
||||
|
||||
return metricStats, total, nil
|
||||
}
|
||||
|
||||
func (m *module) computeTimeseriesTreemap(ctx context.Context, req *metricsexplorertypes.TreemapRequest, filterWhereClause *sqlbuilder.WhereClause) ([]metricsexplorertypes.TreemapEntry, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "computeTimeseriesTreemap")
|
||||
|
||||
@@ -1369,84 +1253,6 @@ func (m *module) computeSamplesTreemap(ctx context.Context, req *metricsexplorer
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (m *module) computeSamplesTreemapFastPath(ctx context.Context, req *metricsexplorertypes.TreemapRequest) ([]metricsexplorertypes.TreemapEntry, error) {
|
||||
|
||||
ctx = m.withMetricsExplorerContext(ctx, "computeSamplesTreemapFastPath")
|
||||
|
||||
start, end, distributedTsTable, _ := telemetrymetrics.WhichTSTableToUse(uint64(req.Start), uint64(req.End), nil)
|
||||
samplesTable := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
|
||||
countExp := telemetrymetrics.CountExpressionForSamplesTable(samplesTable)
|
||||
|
||||
candidateLimit := req.Limit + 50
|
||||
|
||||
metricCandidatesSB := sqlbuilder.NewSelectBuilder()
|
||||
metricCandidatesSB.Select("metric_name")
|
||||
metricCandidatesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, distributedTsTable))
|
||||
metricCandidatesSB.Where("NOT startsWith(metric_name, 'signoz')")
|
||||
metricCandidatesSB.Where(metricCandidatesSB.E("__normalized", false))
|
||||
metricCandidatesSB.Where(metricCandidatesSB.Between("unix_milli", start, end))
|
||||
metricCandidatesSB.GroupBy("metric_name")
|
||||
metricCandidatesSB.OrderBy("uniq(fingerprint) DESC")
|
||||
metricCandidatesSB.Limit(candidateLimit)
|
||||
|
||||
totalSamplesSB := sqlbuilder.NewSelectBuilder()
|
||||
totalSamplesSB.Select(fmt.Sprintf("%s AS total_samples", countExp))
|
||||
totalSamplesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, samplesTable))
|
||||
totalSamplesSB.Where(totalSamplesSB.Between("unix_milli", req.Start, req.End))
|
||||
|
||||
sampleCountsSB := sqlbuilder.NewSelectBuilder()
|
||||
sampleCountsSB.Select(
|
||||
"metric_name",
|
||||
fmt.Sprintf("%s AS samples", countExp),
|
||||
)
|
||||
sampleCountsSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, samplesTable))
|
||||
sampleCountsSB.Where(sampleCountsSB.Between("unix_milli", req.Start, req.End))
|
||||
sampleCountsSB.Where("metric_name GLOBAL IN (SELECT metric_name FROM __metric_candidates)")
|
||||
sampleCountsSB.GroupBy("metric_name")
|
||||
|
||||
cteBuilder := sqlbuilder.With(
|
||||
sqlbuilder.CTEQuery("__metric_candidates").As(metricCandidatesSB),
|
||||
sqlbuilder.CTEQuery("__sample_counts").As(sampleCountsSB),
|
||||
sqlbuilder.CTEQuery("__total_samples").As(totalSamplesSB),
|
||||
)
|
||||
|
||||
finalSB := cteBuilder.Select(
|
||||
"mc.metric_name",
|
||||
"COALESCE(sc.samples, 0) AS samples",
|
||||
"CASE WHEN ts.total_samples = 0 THEN 0 ELSE (COALESCE(sc.samples, 0) * 100.0 / ts.total_samples) END AS percentage",
|
||||
)
|
||||
finalSB.From("__metric_candidates mc")
|
||||
finalSB.JoinWithOption(sqlbuilder.LeftJoin, "__sample_counts sc", "mc.metric_name = sc.metric_name")
|
||||
finalSB.Join("__total_samples ts", "1=1")
|
||||
finalSB.OrderBy("percentage DESC")
|
||||
finalSB.Limit(req.Limit)
|
||||
|
||||
query, args := finalSB.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
|
||||
valueCtx := ctxtypes.SetClickhouseMaxThreads(ctx, m.config.TelemetryStore.Threads)
|
||||
db := m.telemetryStore.ClickhouseDB()
|
||||
rows, err := db.Query(valueCtx, query, args...)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to execute samples treemap fastpath query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
entries := make([]metricsexplorertypes.TreemapEntry, 0)
|
||||
for rows.Next() {
|
||||
var treemapEntry metricsexplorertypes.TreemapEntry
|
||||
if err := rows.Scan(&treemapEntry.MetricName, &treemapEntry.TotalValue, &treemapEntry.Percentage); err != nil {
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to scan samples treemap row")
|
||||
}
|
||||
entries = append(entries, treemapEntry)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "error iterating samples treemap rows")
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// getMetricDataPoints returns the total number of data points (samples) for a metric.
|
||||
func (m *module) getMetricDataPoints(ctx context.Context, metricName string) (uint64, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "getMetricDataPoints")
|
||||
|
||||
@@ -958,7 +958,7 @@ func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request
|
||||
|
||||
whereClause := contextlinks.PrepareFilterExpression(lbls, filterExpr, q.GroupBy)
|
||||
|
||||
res.Items[idx].RelatedLogsLink = contextlinks.PrepareLinksToLogsV5(start, end, whereClause)
|
||||
res.Items[idx].RelatedLogsLink = contextlinks.PrepareParamsForLogsV5(start, end, whereClause).Encode()
|
||||
} else if rule.AlertType == ruletypes.AlertTypeTraces {
|
||||
// TODO(srikanthccv): re-visit this and support multiple queries
|
||||
var q qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]
|
||||
@@ -978,7 +978,7 @@ func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
whereClause := contextlinks.PrepareFilterExpression(lbls, filterExpr, q.GroupBy)
|
||||
res.Items[idx].RelatedTracesLink = contextlinks.PrepareLinksToTracesV5(start, end, whereClause)
|
||||
res.Items[idx].RelatedTracesLink = contextlinks.PrepareParamsForTracesV5(start, end, whereClause).Encode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -23,7 +24,7 @@ type BaseRule struct {
|
||||
id string
|
||||
name string
|
||||
orgID valuer.UUID
|
||||
source string
|
||||
externalURL *url.URL
|
||||
handledRestart bool
|
||||
|
||||
// Type of the rule
|
||||
@@ -138,7 +139,13 @@ func WithRuleStateHistoryModule(module rulestatehistory.Module) RuleOption {
|
||||
}
|
||||
}
|
||||
|
||||
func NewBaseRule(id string, orgID valuer.UUID, p *ruletypes.PostableRule, opts ...RuleOption) (*BaseRule, error) {
|
||||
func NewBaseRule(
|
||||
id string,
|
||||
orgID valuer.UUID,
|
||||
p *ruletypes.PostableRule,
|
||||
externalURL *url.URL,
|
||||
opts ...RuleOption,
|
||||
) (*BaseRule, error) {
|
||||
threshold, err := p.RuleCondition.Thresholds.GetRuleThreshold()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -151,8 +158,8 @@ func NewBaseRule(id string, orgID valuer.UUID, p *ruletypes.PostableRule, opts .
|
||||
baseRule := &BaseRule{
|
||||
id: id,
|
||||
orgID: orgID,
|
||||
externalURL: externalURL,
|
||||
name: p.AlertName,
|
||||
source: p.Source,
|
||||
typ: p.AlertType,
|
||||
ruleCondition: p.RuleCondition,
|
||||
evalWindow: p.EvalWindow,
|
||||
@@ -241,7 +248,17 @@ func (r *BaseRule) Annotations() ruletypes.Labels { return r.annotations }
|
||||
func (r *BaseRule) PreferredChannels() []string { return r.preferredChannels }
|
||||
|
||||
func (r *BaseRule) GeneratorURL() string {
|
||||
return ruletypes.PrepareRuleGeneratorURL(r.ID(), r.source)
|
||||
params := url.Values{}
|
||||
params.Set("ruleId", r.id)
|
||||
return r.ExternalURL("alerts/overview", params)
|
||||
}
|
||||
|
||||
func (r *BaseRule) ExternalURL(path string, params url.Values) string {
|
||||
u := r.externalURL.JoinPath(path)
|
||||
if len(params) > 0 {
|
||||
u.RawQuery = params.Encode()
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (r *BaseRule) SelectedQuery(ctx context.Context) string {
|
||||
|
||||
@@ -3,6 +3,7 @@ package rules
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -18,6 +19,13 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func mustParseURL(t *testing.T, raw string) *url.URL {
|
||||
t.Helper()
|
||||
u, err := url.Parse(raw)
|
||||
require.NoError(t, err)
|
||||
return u
|
||||
}
|
||||
|
||||
// createTestSeries creates a *qbtypes.TimeSeries with the given labels and optional points
|
||||
// so we don't exactly need the points in the series because the labels are used to determine if the series is new or old
|
||||
// we use the labels to create a lookup key for the series and then check the first_seen timestamp for the series in the metadata table
|
||||
@@ -681,7 +689,15 @@ func TestBaseRule_FilterNewSeries(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create BaseRule using NewBaseRule
|
||||
rule, err := NewBaseRule("test-rule", valuer.GenerateUUID(), &postableRule, WithQueryParser(queryParser), WithLogger(logger), WithMetadataStore(mockMetadataStore))
|
||||
rule, err := NewBaseRule(
|
||||
"test-rule",
|
||||
valuer.GenerateUUID(),
|
||||
&postableRule,
|
||||
mustParseURL(t, "http://localhost:8080"),
|
||||
WithQueryParser(queryParser),
|
||||
WithLogger(logger),
|
||||
WithMetadataStore(mockMetadataStore),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
filteredSeries, err := rule.FilterNewSeries(context.Background(), tt.evalTime, tt.series)
|
||||
@@ -723,6 +739,69 @@ func TestBaseRule_FilterNewSeries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseRule_ExternalURL(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
externalURL *url.URL
|
||||
want string
|
||||
}{
|
||||
{name: "default value returned as-is", externalURL: mustParseURL(t, "http://localhost:8080"), want: "http://localhost:8080"},
|
||||
{name: "configured https host", externalURL: mustParseURL(t, "https://signoz.example.com"), want: "https://signoz.example.com"},
|
||||
{name: "configured host with port", externalURL: mustParseURL(t, "http://signoz.internal:3301"), want: "http://signoz.internal:3301"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := createPostableRule(&ruletypes.AlertCompositeQuery{})
|
||||
r, err := NewBaseRule("some-id", valuer.GenerateUUID(), &p, tc.externalURL)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
require.Equal(t, tc.want, r.ExternalURL("", nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseRule_GeneratorURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ruleID string
|
||||
externalURL *url.URL
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "configured external URL",
|
||||
ruleID: "abc",
|
||||
externalURL: mustParseURL(t, "https://signoz.example.com"),
|
||||
want: "https://signoz.example.com/alerts/overview?ruleId=abc",
|
||||
},
|
||||
{
|
||||
name: "default external URL is used as-is",
|
||||
ruleID: "abc",
|
||||
externalURL: mustParseURL(t, "http://localhost:8080"),
|
||||
want: "http://localhost:8080/alerts/overview?ruleId=abc",
|
||||
},
|
||||
{
|
||||
name: "external URL with base path is preserved",
|
||||
ruleID: "abc",
|
||||
externalURL: mustParseURL(t, "https://signoz.example.com/signoz"),
|
||||
want: "https://signoz.example.com/signoz/alerts/overview?ruleId=abc",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := createPostableRule(&ruletypes.AlertCompositeQuery{})
|
||||
r, err := NewBaseRule(tc.ruleID, valuer.GenerateUUID(), &p, tc.externalURL)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
require.Equal(t, tc.want, r.GeneratorURL())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// labelsKey creates a deterministic string key from a labels map
|
||||
// This is used to group series by their unique label combinations
|
||||
func labelsKey(lbls []*qbtypes.Label) string {
|
||||
|
||||
@@ -150,6 +150,7 @@ func defaultPrepareTaskFunc(opts PrepareTaskOptions) (Task, error) {
|
||||
opts.Rule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
WithSQLStore(opts.SQLStore),
|
||||
WithQueryParser(opts.ManagerOpts.QueryParser),
|
||||
@@ -174,6 +175,7 @@ func defaultPrepareTaskFunc(opts PrepareTaskOptions) (Task, error) {
|
||||
opts.Rule,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
WithSQLStore(opts.SQLStore),
|
||||
WithQueryParser(opts.ManagerOpts.QueryParser),
|
||||
WithMetadataStore(opts.ManagerOpts.MetadataStore),
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
|
||||
alertmanagermock "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
@@ -50,6 +51,7 @@ func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
|
||||
mockAM := am.(*alertmanagermock.MockAlertmanager)
|
||||
// mock set notification config
|
||||
mockAM.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
mockAM.On("Config").Return(alertmanagerserver.Config{ExternalURL: mustParseURL(t, "http://localhost:8080")})
|
||||
// for saving temp alerts that are triggered via TestNotification
|
||||
if tc.ExpectAlerts > 0 {
|
||||
mockAM.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||
@@ -162,6 +164,7 @@ func TestManager_TestNotification_SendUnmatched_PromRule(t *testing.T) {
|
||||
mockAM := am.(*alertmanagermock.MockAlertmanager)
|
||||
// mock set notification config
|
||||
mockAM.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
mockAM.On("Config").Return(alertmanagerserver.Config{ExternalURL: mustParseURL(t, "http://localhost:8080")})
|
||||
// for saving temp alerts that are triggered via TestNotification
|
||||
if tc.ExpectAlerts > 0 {
|
||||
mockAM.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/prometheus/model/labels"
|
||||
@@ -34,11 +35,12 @@ func NewPromRule(
|
||||
postableRule *ruletypes.PostableRule,
|
||||
logger *slog.Logger,
|
||||
prometheus prometheus.Prometheus,
|
||||
externalURL *url.URL,
|
||||
opts ...RuleOption,
|
||||
) (*PromRule, error) {
|
||||
opts = append(opts, WithLogger(logger))
|
||||
|
||||
baseRule, err := NewBaseRule(id, orgID, postableRule, opts...)
|
||||
baseRule, err := NewBaseRule(id, orgID, postableRule, externalURL, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -704,7 +704,8 @@ func TestPromRuleEval(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
rule, err := NewPromRule("69", valuer.GenerateUUID(), &postableRule, logger, nil)
|
||||
externalUrl := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewPromRule("69", valuer.GenerateUUID(), &postableRule, logger, nil, externalUrl)
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -967,7 +968,8 @@ func TestPromRuleUnitCombinations(t *testing.T) {
|
||||
"summary": "The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}",
|
||||
}
|
||||
|
||||
rule, err := NewPromRule("69", valuer.GenerateUUID(), &postableRule, logger, promProvider)
|
||||
externalUrl := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewPromRule("69", valuer.GenerateUUID(), &postableRule, logger, promProvider, externalUrl)
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
promProvider.Close()
|
||||
@@ -1083,7 +1085,8 @@ func TestPromRuleNoData(t *testing.T) {
|
||||
"summary": "The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}",
|
||||
}
|
||||
|
||||
rule, err := NewPromRule("69", valuer.GenerateUUID(), &postableRule, logger, promProvider)
|
||||
externalUrl := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewPromRule("69", valuer.GenerateUUID(), &postableRule, logger, promProvider, externalUrl)
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
promProvider.Close()
|
||||
@@ -1316,7 +1319,8 @@ func TestMultipleThresholdPromRule(t *testing.T) {
|
||||
"summary": "The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}",
|
||||
}
|
||||
|
||||
rule, err := NewPromRule("69", valuer.GenerateUUID(), &postableRule, logger, promProvider)
|
||||
externalUrl := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewPromRule("69", valuer.GenerateUUID(), &postableRule, logger, promProvider, externalUrl)
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
promProvider.Close()
|
||||
@@ -1453,7 +1457,8 @@ func TestPromRule_NoData(t *testing.T) {
|
||||
_ = promProvider.Close()
|
||||
}()
|
||||
|
||||
rule, err := NewPromRule("some-id", valuer.GenerateUUID(), &postableRule, logger, promProvider)
|
||||
externalUrl := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewPromRule("some-id", valuer.GenerateUUID(), &postableRule, logger, promProvider, externalUrl)
|
||||
require.NoError(t, err)
|
||||
|
||||
alertsFound, err := rule.Eval(context.Background(), evalTime)
|
||||
@@ -1603,7 +1608,8 @@ func TestPromRule_NoData_AbsentFor(t *testing.T) {
|
||||
_ = promProvider.Close()
|
||||
}()
|
||||
|
||||
rule, err := NewPromRule("some-id", valuer.GenerateUUID(), &postableRule, logger, promProvider)
|
||||
externalUrl := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewPromRule("some-id", valuer.GenerateUUID(), &postableRule, logger, promProvider, externalUrl)
|
||||
require.NoError(t, err)
|
||||
|
||||
// First eval with data - should NOT alert, but populates lastTimestampWithDatapoints
|
||||
@@ -1762,7 +1768,8 @@ func TestPromRuleEval_RequireMinPoints(t *testing.T) {
|
||||
_ = promProvider.Close()
|
||||
}()
|
||||
|
||||
rule, err := NewPromRule("some-id", valuer.GenerateUUID(), &postableRule, logger, promProvider)
|
||||
externalUrl := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewPromRule("some-id", valuer.GenerateUUID(), &postableRule, logger, promProvider, externalUrl)
|
||||
require.NoError(t, err)
|
||||
|
||||
alertsFound, err := rule.Eval(context.Background(), evalTime)
|
||||
|
||||
@@ -49,6 +49,7 @@ func defaultTestNotification(opts PrepareTestRuleOptions) (int, error) {
|
||||
parsedRule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
WithSendAlways(),
|
||||
WithSendUnmatched(),
|
||||
WithSQLStore(opts.SQLStore),
|
||||
@@ -70,6 +71,7 @@ func defaultTestNotification(opts PrepareTestRuleOptions) (int, error) {
|
||||
parsedRule,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
WithSendAlways(),
|
||||
WithSendUnmatched(),
|
||||
WithSQLStore(opts.SQLStore),
|
||||
|
||||
@@ -38,13 +38,14 @@ func NewThresholdRule(
|
||||
p *ruletypes.PostableRule,
|
||||
querier querier.Querier,
|
||||
logger *slog.Logger,
|
||||
externalURL *url.URL,
|
||||
opts ...RuleOption,
|
||||
) (*ThresholdRule, error) {
|
||||
logger.Info("creating new ThresholdRule", slog.String("rule.id", id))
|
||||
|
||||
opts = append(opts, WithLogger(logger))
|
||||
|
||||
baseRule, err := NewBaseRule(id, orgID, p, opts...)
|
||||
baseRule, err := NewBaseRule(id, orgID, p, externalURL, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -55,17 +56,6 @@ func NewThresholdRule(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *ThresholdRule) hostFromSource() string {
|
||||
parsedURL, err := url.Parse(r.source)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if parsedURL.Port() != "" {
|
||||
return fmt.Sprintf("%s://%s:%s", parsedURL.Scheme, parsedURL.Hostname(), parsedURL.Port())
|
||||
}
|
||||
return fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Hostname())
|
||||
}
|
||||
|
||||
func (r *ThresholdRule) Type() ruletypes.RuleType {
|
||||
return ruletypes.RuleTypeThreshold
|
||||
}
|
||||
@@ -95,19 +85,19 @@ func (r *ThresholdRule) prepareQueryRange(ctx context.Context, ts time.Time) (*q
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (r *ThresholdRule) prepareLinksToLogs(ctx context.Context, ts time.Time, lbls ruletypes.Labels) string {
|
||||
func (r *ThresholdRule) prepareParamsForLogs(ctx context.Context, ts time.Time, lbls ruletypes.Labels) url.Values {
|
||||
selectedQuery := r.SelectedQuery(ctx)
|
||||
|
||||
qr, err := r.prepareQueryRange(ctx, ts)
|
||||
if err != nil {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
start := time.UnixMilli(int64(qr.Start))
|
||||
end := time.UnixMilli(int64(qr.End))
|
||||
|
||||
// TODO(srikanthccv): handle formula queries
|
||||
if selectedQuery < "A" || selectedQuery > "Z" {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
var q qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]
|
||||
@@ -122,7 +112,7 @@ func (r *ThresholdRule) prepareLinksToLogs(ctx context.Context, ts time.Time, lb
|
||||
}
|
||||
|
||||
if q.Signal != telemetrytypes.SignalLogs {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
filterExpr := ""
|
||||
@@ -132,22 +122,22 @@ func (r *ThresholdRule) prepareLinksToLogs(ctx context.Context, ts time.Time, lb
|
||||
|
||||
whereClause := contextlinks.PrepareFilterExpression(lbls.Map(), filterExpr, q.GroupBy)
|
||||
|
||||
return contextlinks.PrepareLinksToLogsV5(start, end, whereClause)
|
||||
return contextlinks.PrepareParamsForLogsV5(start, end, whereClause)
|
||||
}
|
||||
|
||||
func (r *ThresholdRule) prepareLinksToTraces(ctx context.Context, ts time.Time, lbls ruletypes.Labels) string {
|
||||
func (r *ThresholdRule) prepareParamsForTraces(ctx context.Context, ts time.Time, lbls ruletypes.Labels) url.Values {
|
||||
selectedQuery := r.SelectedQuery(ctx)
|
||||
|
||||
qr, err := r.prepareQueryRange(ctx, ts)
|
||||
if err != nil {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
start := time.UnixMilli(int64(qr.Start))
|
||||
end := time.UnixMilli(int64(qr.End))
|
||||
|
||||
// TODO(srikanthccv): handle formula queries
|
||||
if selectedQuery < "A" || selectedQuery > "Z" {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
var q qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]
|
||||
@@ -162,7 +152,7 @@ func (r *ThresholdRule) prepareLinksToTraces(ctx context.Context, ts time.Time,
|
||||
}
|
||||
|
||||
if q.Signal != telemetrytypes.SignalTraces {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
filterExpr := ""
|
||||
@@ -172,7 +162,7 @@ func (r *ThresholdRule) prepareLinksToTraces(ctx context.Context, ts time.Time,
|
||||
|
||||
whereClause := contextlinks.PrepareFilterExpression(lbls.Map(), filterExpr, q.GroupBy)
|
||||
|
||||
return contextlinks.PrepareLinksToTracesV5(start, end, whereClause)
|
||||
return contextlinks.PrepareParamsForTracesV5(start, end, whereClause)
|
||||
}
|
||||
|
||||
func (r *ThresholdRule) buildAndRunQuery(ctx context.Context, orgID valuer.UUID, ts time.Time) (ruletypes.Vector, error) {
|
||||
@@ -349,16 +339,18 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time) (int, error) {
|
||||
// label set, but different timestamps, together.
|
||||
switch r.typ {
|
||||
case ruletypes.AlertTypeTraces:
|
||||
link := r.prepareLinksToTraces(ctx, ts, smpl.Metric)
|
||||
if link != "" && r.hostFromSource() != "" {
|
||||
r.logger.InfoContext(ctx, "adding traces link to annotations", slog.String("annotation.link", fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)))
|
||||
annotations = append(annotations, ruletypes.Label{Name: ruletypes.AnnotationRelatedTraces, Value: fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)})
|
||||
params := r.prepareParamsForTraces(ctx, ts, smpl.Metric)
|
||||
if len(params) > 0 {
|
||||
link := r.ExternalURL("traces-explorer", params)
|
||||
r.logger.InfoContext(ctx, "adding traces link to annotations", slog.String("annotation.link", link))
|
||||
annotations = append(annotations, ruletypes.Label{Name: ruletypes.AnnotationRelatedTraces, Value: link})
|
||||
}
|
||||
case ruletypes.AlertTypeLogs:
|
||||
link := r.prepareLinksToLogs(ctx, ts, smpl.Metric)
|
||||
if link != "" && r.hostFromSource() != "" {
|
||||
r.logger.InfoContext(ctx, "adding logs link to annotations", slog.String("annotation.link", fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)))
|
||||
annotations = append(annotations, ruletypes.Label{Name: ruletypes.AnnotationRelatedLogs, Value: fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)})
|
||||
params := r.prepareParamsForLogs(ctx, ts, smpl.Metric)
|
||||
if len(params) > 0 {
|
||||
link := r.ExternalURL("logs/logs-explorer", params)
|
||||
r.logger.InfoContext(ctx, "adding logs link to annotations", slog.String("annotation.link", link))
|
||||
annotations = append(annotations, ruletypes.Label{Name: ruletypes.AnnotationRelatedLogs, Value: link})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,8 @@ func TestThresholdRuleEvalWithoutRecoveryTarget(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
externalURL := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
assert.NoError(t, err)
|
||||
|
||||
values := c.values
|
||||
@@ -141,7 +142,7 @@ func TestNormalizeLabelName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareLinksToLogs(t *testing.T) {
|
||||
func TestPrepareParamsForLogs(t *testing.T) {
|
||||
postableRule := ruletypes.PostableRule{
|
||||
AlertName: "Tricky Condition Tests",
|
||||
AlertType: ruletypes.AlertTypeLogs,
|
||||
@@ -187,16 +188,20 @@ func TestPrepareLinksToLogs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
|
||||
externalURL := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
assert.NoError(t, err)
|
||||
|
||||
ts := time.UnixMilli(1705469040000)
|
||||
|
||||
link := rule.prepareLinksToLogs(context.Background(), ts, ruletypes.Labels{})
|
||||
assert.Contains(t, link, "&timeRange=%7B%22start%22%3A1705468620000%2C%22end%22%3A1705468920000%2C%22pageSize%22%3A100%7D&startTime=1705468620000&endTime=1705468920000")
|
||||
params := rule.prepareParamsForLogs(context.Background(), ts, ruletypes.Labels{}).Encode()
|
||||
assert.Contains(t, params, "&timeRange=%7B%22start%22%3A1705468620000%2C%22end%22%3A1705468920000%2C%22pageSize%22%3A100%7D")
|
||||
assert.Contains(t, params, "&startTime=1705468620000")
|
||||
assert.Contains(t, params, "&endTime=1705468920000")
|
||||
}
|
||||
|
||||
func TestPrepareLinksToLogsFilterExpression(t *testing.T) {
|
||||
func TestPrepareParamsForLogsFilterExpression(t *testing.T) {
|
||||
postableRule := ruletypes.PostableRule{
|
||||
AlertName: "Tricky Condition Tests",
|
||||
AlertType: ruletypes.AlertTypeLogs,
|
||||
@@ -246,16 +251,17 @@ func TestPrepareLinksToLogsFilterExpression(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
externalURL := mustParseURL(t, "http://localhost:8000")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
assert.NoError(t, err)
|
||||
|
||||
ts := time.UnixMilli(1753527163000)
|
||||
|
||||
link := rule.prepareLinksToLogs(context.Background(), ts, ruletypes.Labels{})
|
||||
assert.Contains(t, link, "compositeQuery=%257B%2522queryType%2522%253A%2522builder%2522%252C%2522builder%2522%253A%257B%2522queryData%2522%253A%255B%257B%2522queryName%2522%253A%2522A%2522%252C%2522stepInterval%2522%253A60%252C%2522dataSource%2522%253A%2522logs%2522%252C%2522aggregateOperator%2522%253A%2522noop%2522%252C%2522aggregateAttribute%2522%253A%257B%2522key%2522%253A%2522%2522%252C%2522dataType%2522%253A%2522%2522%252C%2522type%2522%253A%2522%2522%252C%2522isColumn%2522%253Afalse%252C%2522isJSON%2522%253Afalse%257D%252C%2522expression%2522%253A%2522A%2522%252C%2522disabled%2522%253Afalse%252C%2522limit%2522%253A0%252C%2522offset%2522%253A0%252C%2522pageSize%2522%253A0%252C%2522ShiftBy%2522%253A0%252C%2522IsAnomaly%2522%253Afalse%252C%2522QueriesUsedInFormula%2522%253Anull%252C%2522filter%2522%253A%257B%2522expression%2522%253A%2522service.name%2BEXISTS%2522%257D%257D%255D%252C%2522queryFormulas%2522%253A%255B%255D%257D%257D&timeRange=%7B%22start%22%3A1753526700000%2C%22end%22%3A1753527000000%2C%22pageSize%22%3A100%7D&startTime=1753526700000&endTime=1753527000000&options=%7B%22maxLines%22%3A0%2C%22format%22%3A%22%22%2C%22selectColumns%22%3Anull%7D")
|
||||
params := rule.prepareParamsForLogs(context.Background(), ts, ruletypes.Labels{}).Encode()
|
||||
assert.Contains(t, params, "compositeQuery=%257B%2522queryType%2522%253A%2522builder%2522%252C%2522builder%2522%253A%257B%2522queryData%2522%253A%255B%257B%2522queryName%2522%253A%2522A%2522%252C%2522stepInterval%2522%253A60%252C%2522dataSource%2522%253A%2522logs%2522%252C%2522aggregateOperator%2522%253A%2522noop%2522%252C%2522aggregateAttribute%2522%253A%257B%2522key%2522%253A%2522%2522%252C%2522dataType%2522%253A%2522%2522%252C%2522type%2522%253A%2522%2522%252C%2522isColumn%2522%253Afalse%252C%2522isJSON%2522%253Afalse%257D%252C%2522expression%2522%253A%2522A%2522%252C%2522disabled%2522%253Afalse%252C%2522limit%2522%253A0%252C%2522offset%2522%253A0%252C%2522pageSize%2522%253A0%252C%2522ShiftBy%2522%253A0%252C%2522IsAnomaly%2522%253Afalse%252C%2522QueriesUsedInFormula%2522%253Anull%252C%2522filter%2522%253A%257B%2522expression%2522%253A%2522service.name%2BEXISTS%2522%257D%257D%255D%252C%2522queryFormulas%2522%253A%255B%255D%257D%257D&endTime=1753527000000&options=%7B%22maxLines%22%3A0%2C%22format%22%3A%22%22%2C%22selectColumns%22%3Anull%7D&startTime=1753526700000&timeRange=%7B%22start%22%3A1753526700000%2C%22end%22%3A1753527000000%2C%22pageSize%22%3A100%7D")
|
||||
}
|
||||
|
||||
func TestPrepareLinksToTracesFilterExpression(t *testing.T) {
|
||||
func TestPrepareParamsForTracesFilterExpression(t *testing.T) {
|
||||
postableRule := ruletypes.PostableRule{
|
||||
AlertName: "Tricky Condition Tests",
|
||||
AlertType: ruletypes.AlertTypeTraces,
|
||||
@@ -305,16 +311,17 @@ func TestPrepareLinksToTracesFilterExpression(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
externalURL := mustParseURL(t, "http://localhost:8000")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
assert.NoError(t, err)
|
||||
|
||||
ts := time.UnixMilli(1753527163000)
|
||||
|
||||
link := rule.prepareLinksToTraces(context.Background(), ts, ruletypes.Labels{})
|
||||
assert.Contains(t, link, "compositeQuery=%257B%2522queryType%2522%253A%2522builder%2522%252C%2522builder%2522%253A%257B%2522queryData%2522%253A%255B%257B%2522queryName%2522%253A%2522A%2522%252C%2522stepInterval%2522%253A60%252C%2522dataSource%2522%253A%2522traces%2522%252C%2522aggregateOperator%2522%253A%2522noop%2522%252C%2522aggregateAttribute%2522%253A%257B%2522key%2522%253A%2522%2522%252C%2522dataType%2522%253A%2522%2522%252C%2522type%2522%253A%2522%2522%252C%2522isColumn%2522%253Afalse%252C%2522isJSON%2522%253Afalse%257D%252C%2522expression%2522%253A%2522A%2522%252C%2522disabled%2522%253Afalse%252C%2522limit%2522%253A0%252C%2522offset%2522%253A0%252C%2522pageSize%2522%253A0%252C%2522ShiftBy%2522%253A0%252C%2522IsAnomaly%2522%253Afalse%252C%2522QueriesUsedInFormula%2522%253Anull%252C%2522filter%2522%253A%257B%2522expression%2522%253A%2522service.name%2BEXISTS%2522%257D%257D%255D%252C%2522queryFormulas%2522%253A%255B%255D%257D%257D&timeRange=%7B%22start%22%3A1753526700000000000%2C%22end%22%3A1753527000000000000%2C%22pageSize%22%3A100%7D&startTime=1753526700000000000&endTime=1753527000000000000&options=%7B%22maxLines%22%3A0%2C%22format%22%3A%22%22%2C%22selectColumns%22%3Anull%7D")
|
||||
params := rule.prepareParamsForTraces(context.Background(), ts, ruletypes.Labels{}).Encode()
|
||||
assert.Contains(t, params, "compositeQuery=%257B%2522queryType%2522%253A%2522builder%2522%252C%2522builder%2522%253A%257B%2522queryData%2522%253A%255B%257B%2522queryName%2522%253A%2522A%2522%252C%2522stepInterval%2522%253A60%252C%2522dataSource%2522%253A%2522traces%2522%252C%2522aggregateOperator%2522%253A%2522noop%2522%252C%2522aggregateAttribute%2522%253A%257B%2522key%2522%253A%2522%2522%252C%2522dataType%2522%253A%2522%2522%252C%2522type%2522%253A%2522%2522%252C%2522isColumn%2522%253Afalse%252C%2522isJSON%2522%253Afalse%257D%252C%2522expression%2522%253A%2522A%2522%252C%2522disabled%2522%253Afalse%252C%2522limit%2522%253A0%252C%2522offset%2522%253A0%252C%2522pageSize%2522%253A0%252C%2522ShiftBy%2522%253A0%252C%2522IsAnomaly%2522%253Afalse%252C%2522QueriesUsedInFormula%2522%253Anull%252C%2522filter%2522%253A%257B%2522expression%2522%253A%2522service.name%2BEXISTS%2522%257D%257D%255D%252C%2522queryFormulas%2522%253A%255B%255D%257D%257D&endTime=1753527000000000000&options=%7B%22maxLines%22%3A0%2C%22format%22%3A%22%22%2C%22selectColumns%22%3Anull%7D&startTime=1753526700000000000&timeRange=%7B%22start%22%3A1753526700000000000%2C%22end%22%3A1753527000000000000%2C%22pageSize%22%3A100%7D")
|
||||
}
|
||||
|
||||
func TestPrepareLinksToTraces(t *testing.T) {
|
||||
func TestPrepareParamsForTraces(t *testing.T) {
|
||||
postableRule := ruletypes.PostableRule{
|
||||
AlertName: "Links to traces test",
|
||||
AlertType: ruletypes.AlertTypeTraces,
|
||||
@@ -360,15 +367,18 @@ func TestPrepareLinksToTraces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
externalURL := mustParseURL(t, "http://localhost:8000")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
ts := time.UnixMilli(1705469040000)
|
||||
|
||||
link := rule.prepareLinksToTraces(context.Background(), ts, ruletypes.Labels{})
|
||||
assert.Contains(t, link, "&timeRange=%7B%22start%22%3A1705468620000000000%2C%22end%22%3A1705468920000000000%2C%22pageSize%22%3A100%7D&startTime=1705468620000000000&endTime=1705468920000000000")
|
||||
params := rule.prepareParamsForTraces(context.Background(), ts, ruletypes.Labels{}).Encode()
|
||||
assert.Contains(t, params, "&timeRange=%7B%22start%22%3A1705468620000000000%2C%22end%22%3A1705468920000000000%2C%22pageSize%22%3A100%7D")
|
||||
assert.Contains(t, params, "&startTime=1705468620000000000")
|
||||
assert.Contains(t, params, "&endTime=1705468920000000000")
|
||||
}
|
||||
|
||||
func TestThresholdRuleLabelNormalization(t *testing.T) {
|
||||
@@ -444,7 +454,8 @@ func TestThresholdRuleLabelNormalization(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
externalURL := mustParseURL(t, "http://localhost:8000")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
assert.NoError(t, err)
|
||||
|
||||
values := c.values
|
||||
@@ -641,7 +652,8 @@ func TestThresholdRuleUnitCombinations(t *testing.T) {
|
||||
"summary": "The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}",
|
||||
}
|
||||
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger)
|
||||
externalURL := mustParseURL(t, "http://localhost:8000")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger, externalURL)
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -748,7 +760,8 @@ func TestThresholdRuleNoData(t *testing.T) {
|
||||
"summary": "The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}",
|
||||
}
|
||||
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger)
|
||||
externalURL := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger, externalURL)
|
||||
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
@@ -853,7 +866,8 @@ func TestThresholdRuleTracesLink(t *testing.T) {
|
||||
"summary": "The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}",
|
||||
}
|
||||
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger)
|
||||
externalURL := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger, externalURL)
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -970,7 +984,8 @@ func TestThresholdRuleLogsLink(t *testing.T) {
|
||||
"summary": "The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}",
|
||||
}
|
||||
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger)
|
||||
externalURL := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger, externalURL)
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -1149,7 +1164,8 @@ func TestMultipleThresholdRule(t *testing.T) {
|
||||
"summary": "The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}",
|
||||
}
|
||||
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger)
|
||||
externalURL := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, querier, logger, externalURL)
|
||||
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
@@ -1295,7 +1311,8 @@ func TestThresholdRuleEval_SendUnmatchedBypassesRecovery(t *testing.T) {
|
||||
}
|
||||
|
||||
logger := instrumentationtest.New().Logger()
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
externalURL := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now()
|
||||
@@ -1537,7 +1554,8 @@ func runEvalTests(t *testing.T, postableRule ruletypes.PostableRule, testCases [
|
||||
Spec: thresholds,
|
||||
}
|
||||
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
externalURL := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
@@ -1644,7 +1662,8 @@ func runMultiThresholdEvalTests(t *testing.T, postableRule ruletypes.PostableRul
|
||||
Spec: thresholds,
|
||||
}
|
||||
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
externalURL := mustParseURL(t, "http://localhost:8080")
|
||||
rule, err := NewThresholdRule("69", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL, WithEvalDelay(valuer.MustParseTextDuration("2m")))
|
||||
if err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
@@ -1931,6 +1950,7 @@ func TestThresholdEval_RequireMinPoints(t *testing.T) {
|
||||
&postableRule,
|
||||
querier,
|
||||
logger,
|
||||
mustParseURL(t, "http://localhost:8080"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
t.Run(fmt.Sprintf("%d, %s", idx, c.description), func(t *testing.T) {
|
||||
|
||||
@@ -2,10 +2,7 @@ package ruletypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
@@ -191,35 +188,3 @@ func (rc *RuleCondition) String() string {
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// PrepareRuleGeneratorURL creates an appropriate url for the rule. The URL is
|
||||
// sent in Slack messages as well as to other systems and allows backtracking
|
||||
// to the rule definition from the third party systems.
|
||||
func PrepareRuleGeneratorURL(ruleID string, source string) string {
|
||||
if source == "" {
|
||||
return source
|
||||
}
|
||||
|
||||
// check if source is a valid url
|
||||
parsedSource, err := url.Parse(source)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// since we capture window.location when a new rule is created
|
||||
// we end up with rulesource host:port/alerts/new. in this case
|
||||
// we want to replace new with rule id parameter
|
||||
|
||||
hasNew := strings.LastIndex(source, "new")
|
||||
if hasNew > -1 {
|
||||
ruleURL := fmt.Sprintf("%sedit?ruleId=%s", source[0:hasNew], ruleID)
|
||||
return ruleURL
|
||||
}
|
||||
|
||||
// The source contains the encoded query, start and end time
|
||||
// and other parameters. We don't want to include them in the generator URL
|
||||
// mainly to keep the URL short and lower the alert body contents
|
||||
// The generator URL with /alerts/edit?ruleId= is enough
|
||||
if parsedSource.Port() != "" {
|
||||
return fmt.Sprintf("%s://%s:%s/alerts/edit?ruleId=%s", parsedSource.Scheme, parsedSource.Hostname(), parsedSource.Port(), ruleID)
|
||||
}
|
||||
return fmt.Sprintf("%s://%s/alerts/edit?ruleId=%s", parsedSource.Scheme, parsedSource.Hostname(), ruleID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user