mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-04 11:20:21 +01:00
Compare commits
7 Commits
fix/panic-
...
feat/field
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1ba1d8d09 | ||
|
|
34de92339e | ||
|
|
46178decdf | ||
|
|
bbfba01aa9 | ||
|
|
34c6b593cb | ||
|
|
356676708b | ||
|
|
1c7b37d3e5 |
@@ -4295,6 +4295,10 @@ paths:
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricNamespace
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
@@ -4380,6 +4384,10 @@ paths:
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricNamespace
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
@@ -8142,6 +8150,10 @@ paths:
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricNamespace
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
@@ -8239,6 +8251,10 @@ paths:
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricNamespace
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
|
||||
@@ -3723,6 +3723,11 @@ export type GetFieldsKeysParams = {
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricNamespace?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
@@ -3777,6 +3782,11 @@ export type GetFieldsValuesParams = {
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricNamespace?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
@@ -4456,6 +4466,11 @@ export type GetRuleHistoryFilterKeysParams = {
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricNamespace?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
@@ -4513,6 +4528,11 @@ export type GetRuleHistoryFilterValuesParams = {
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricNamespace?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
|
||||
@@ -651,7 +651,12 @@ func (t *telemetryMetaStore) getMetricsKeys(ctx context.Context, fieldKeySelecto
|
||||
// }
|
||||
|
||||
if fieldKeySelector.MetricContext != nil {
|
||||
fieldConds = append(fieldConds, sb.E("metric_name", fieldKeySelector.MetricContext.MetricName))
|
||||
if fieldKeySelector.MetricContext.MetricName != "" {
|
||||
fieldConds = append(fieldConds, sb.E("metric_name", fieldKeySelector.MetricContext.MetricName))
|
||||
}
|
||||
if fieldKeySelector.MetricContext.MetricNamespace != "" {
|
||||
fieldConds = append(fieldConds, sb.Like("metric_name", escapeForLike(fieldKeySelector.MetricContext.MetricNamespace)+"%"))
|
||||
}
|
||||
}
|
||||
|
||||
conds = append(conds, sb.And(fieldConds...))
|
||||
@@ -739,7 +744,12 @@ func (t *telemetryMetaStore) getMeterSourceMetricKeys(ctx context.Context, field
|
||||
fieldConds = append(fieldConds, sb.NotLike("attr_name", "\\_\\_%"))
|
||||
|
||||
if fieldKeySelector.MetricContext != nil {
|
||||
fieldConds = append(fieldConds, sb.E("metric_name", fieldKeySelector.MetricContext.MetricName))
|
||||
if fieldKeySelector.MetricContext.MetricName != "" {
|
||||
fieldConds = append(fieldConds, sb.E("metric_name", fieldKeySelector.MetricContext.MetricName))
|
||||
}
|
||||
if fieldKeySelector.MetricContext.MetricNamespace != "" {
|
||||
fieldConds = append(fieldConds, sb.Like("metric_name", escapeForLike(fieldKeySelector.MetricContext.MetricNamespace)+"%"))
|
||||
}
|
||||
}
|
||||
|
||||
conds = append(conds, sb.And(fieldConds...))
|
||||
@@ -833,8 +843,8 @@ func enrichWithIntrinsicMetricKeys(keys map[string][]*telemetrytypes.TelemetryFi
|
||||
if selector.Signal != telemetrytypes.SignalMetrics && selector.Signal != telemetrytypes.SignalUnspecified {
|
||||
continue
|
||||
}
|
||||
// If a metricName is provided, don’t surface intrinsic metric keys
|
||||
if selector.MetricContext != nil && selector.MetricContext.MetricName != "" {
|
||||
// If metric filters are provided, do not surface intrinsic metric keys.
|
||||
if selector.MetricContext != nil && (selector.MetricContext.MetricName != "" || selector.MetricContext.MetricNamespace != "") {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1383,9 +1393,12 @@ func (t *telemetryMetaStore) getMetricFieldValues(ctx context.Context, fieldValu
|
||||
sb.Where(sb.E("attr_datatype", fieldValueSelector.FieldDataType.TagDataType()))
|
||||
}
|
||||
|
||||
if fieldValueSelector.MetricContext != nil {
|
||||
if fieldValueSelector.MetricContext != nil && fieldValueSelector.MetricContext.MetricName != "" {
|
||||
sb.Where(sb.E("metric_name", fieldValueSelector.MetricContext.MetricName))
|
||||
}
|
||||
if fieldValueSelector.MetricContext != nil && fieldValueSelector.MetricContext.MetricNamespace != "" {
|
||||
sb.Where(sb.Like("metric_name", escapeForLike(fieldValueSelector.MetricContext.MetricNamespace)+"%"))
|
||||
}
|
||||
|
||||
if fieldValueSelector.StartUnixMilli > 0 {
|
||||
sb.Where(sb.GE("last_reported_unix_milli", fieldValueSelector.StartUnixMilli))
|
||||
@@ -1467,6 +1480,9 @@ func (t *telemetryMetaStore) getIntrinsicMetricFieldValues(ctx context.Context,
|
||||
if fieldValueSelector.MetricContext != nil && fieldValueSelector.MetricContext.MetricName != "" {
|
||||
sb.Where(sb.E("metric_name", fieldValueSelector.MetricContext.MetricName))
|
||||
}
|
||||
if fieldValueSelector.MetricContext != nil && fieldValueSelector.MetricContext.MetricNamespace != "" {
|
||||
sb.Where(sb.Like("metric_name", escapeForLike(fieldValueSelector.MetricContext.MetricNamespace)+"%"))
|
||||
}
|
||||
|
||||
if fieldValueSelector.StartUnixMilli > 0 {
|
||||
sb.Where(sb.GE("unix_milli", fieldValueSelector.StartUnixMilli))
|
||||
@@ -1524,6 +1540,13 @@ func (t *telemetryMetaStore) getMeterSourceMetricFieldValues(ctx context.Context
|
||||
}
|
||||
sb.Where(sb.NotLike("attr.1", "\\_\\_%"))
|
||||
|
||||
if fieldValueSelector.MetricContext != nil && fieldValueSelector.MetricContext.MetricName != "" {
|
||||
sb.Where(sb.E("metric_name", fieldValueSelector.MetricContext.MetricName))
|
||||
}
|
||||
if fieldValueSelector.MetricContext != nil && fieldValueSelector.MetricContext.MetricNamespace != "" {
|
||||
sb.Where(sb.Like("metric_name", escapeForLike(fieldValueSelector.MetricContext.MetricNamespace)+"%"))
|
||||
}
|
||||
|
||||
if fieldValueSelector.Value != "" {
|
||||
if fieldValueSelector.SelectorMatchType == telemetrytypes.FieldSelectorMatchTypeExact {
|
||||
sb.Where(sb.E("attr.2", fieldValueSelector.Value))
|
||||
|
||||
@@ -314,6 +314,20 @@ func TestEnrichWithIntrinsicMetricKeys(t *testing.T) {
|
||||
},
|
||||
)
|
||||
assert.NotContains(t, result, "metric_name")
|
||||
|
||||
result = enrichWithIntrinsicMetricKeys(
|
||||
map[string][]*telemetrytypes.TelemetryFieldKey{},
|
||||
[]*telemetrytypes.FieldKeySelector{
|
||||
{
|
||||
Signal: telemetrytypes.SignalMetrics,
|
||||
MetricContext: &telemetrytypes.MetricContext{
|
||||
MetricNamespace: "system.cpu",
|
||||
},
|
||||
SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeFuzzy,
|
||||
},
|
||||
},
|
||||
)
|
||||
assert.NotContains(t, result, "metric_name")
|
||||
}
|
||||
|
||||
func TestGetMetricFieldValuesIntrinsicMetricName(t *testing.T) {
|
||||
@@ -386,3 +400,106 @@ func TestGetMetricFieldValuesIntrinsicBoolReturnsEmpty(t *testing.T) {
|
||||
assert.Empty(t, values.BoolValues)
|
||||
require.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func TestGetMetricFieldValuesAppliesMetricNamespace(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
|
||||
valueRows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "attr_string_value", Type: "String"},
|
||||
}, [][]any{{"value.a"}})
|
||||
|
||||
mock.ExpectQuery(regexp.QuoteMeta("SELECT DISTINCT attr_string_value FROM signoz_metrics.distributed_metadata WHERE attr_name = ? AND metric_name LIKE ? LIMIT ?")).
|
||||
WithArgs("custom_key", "system.cpu%", 11).
|
||||
WillReturnRows(valueRows)
|
||||
|
||||
values, complete, err := metadata.(*telemetryMetaStore).getMetricFieldValues(context.Background(), &telemetrytypes.FieldValueSelector{
|
||||
FieldKeySelector: &telemetrytypes.FieldKeySelector{
|
||||
Signal: telemetrytypes.SignalMetrics,
|
||||
Name: "custom_key",
|
||||
Limit: 10,
|
||||
SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeFuzzy,
|
||||
MetricContext: &telemetrytypes.MetricContext{
|
||||
MetricNamespace: "system.cpu",
|
||||
},
|
||||
},
|
||||
Limit: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, complete)
|
||||
assert.ElementsMatch(t, []string{"value.a"}, values.StringValues)
|
||||
require.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func TestGetMetricFieldValuesIntrinsicMetricNameAppliesMetricNamespace(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
|
||||
valueRows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "metric_name", Type: "String"},
|
||||
}, [][]any{{"system.cpu.utilization"}})
|
||||
|
||||
mock.ExpectQuery(regexp.QuoteMeta("SELECT metric_name FROM signoz_metrics.distributed_time_series_v4_1week WHERE metric_name LIKE ? GROUP BY metric_name LIMIT ?")).
|
||||
WithArgs("system.cpu%", 51).
|
||||
WillReturnRows(valueRows)
|
||||
|
||||
metadataRows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "attr_string_value", Type: "String"},
|
||||
}, [][]any{})
|
||||
|
||||
mock.ExpectQuery(regexp.QuoteMeta("SELECT DISTINCT attr_string_value FROM signoz_metrics.distributed_metadata WHERE attr_name = ? AND metric_name LIKE ? LIMIT ?")).
|
||||
WithArgs("metric_name", "system.cpu%", 50).
|
||||
WillReturnRows(metadataRows)
|
||||
|
||||
values, complete, err := metadata.(*telemetryMetaStore).getMetricFieldValues(context.Background(), &telemetrytypes.FieldValueSelector{
|
||||
FieldKeySelector: &telemetrytypes.FieldKeySelector{
|
||||
Signal: telemetrytypes.SignalMetrics,
|
||||
Name: "metric_name",
|
||||
Limit: 50,
|
||||
SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeFuzzy,
|
||||
MetricContext: &telemetrytypes.MetricContext{
|
||||
MetricNamespace: "system.cpu",
|
||||
},
|
||||
},
|
||||
Limit: 50,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, complete)
|
||||
assert.ElementsMatch(t, []string{"system.cpu.utilization"}, values.StringValues)
|
||||
require.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func TestGetMeterSourceMetricFieldValuesAppliesMetricNamespace(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
|
||||
rows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "attr", Type: "Array(String)"},
|
||||
}, [][]any{{[]string{"service.name", "frontend"}}})
|
||||
|
||||
mock.ExpectQuery(`SELECT .*distributed_samples_agg_1d.*metric_name LIKE .*`).
|
||||
WithArgs("service.name", "\\_\\_%", "system.cpu%", "", 11).
|
||||
WillReturnRows(rows)
|
||||
|
||||
values, complete, err := metadata.(*telemetryMetaStore).getMeterSourceMetricFieldValues(context.Background(), &telemetrytypes.FieldValueSelector{
|
||||
FieldKeySelector: &telemetrytypes.FieldKeySelector{
|
||||
Signal: telemetrytypes.SignalMetrics,
|
||||
Source: telemetrytypes.SourceMeter,
|
||||
Name: "service.name",
|
||||
MetricContext: &telemetrytypes.MetricContext{
|
||||
MetricNamespace: "system.cpu",
|
||||
},
|
||||
},
|
||||
Limit: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, complete)
|
||||
assert.ElementsMatch(t, []string{"frontend"}, values.StringValues)
|
||||
require.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
@@ -268,7 +268,8 @@ func (t *TelemetryFieldValues) NumValues() int {
|
||||
}
|
||||
|
||||
type MetricContext struct {
|
||||
MetricName string `json:"metricName"`
|
||||
MetricName string `json:"metricName"`
|
||||
MetricNamespace string `json:"metricNamespace,omitempty"`
|
||||
}
|
||||
|
||||
type FieldKeySelector struct {
|
||||
@@ -297,15 +298,16 @@ type GettableFieldKeys struct {
|
||||
}
|
||||
|
||||
type PostableFieldKeysParams struct {
|
||||
Signal Signal `query:"signal"`
|
||||
Source Source `query:"source"`
|
||||
Limit int `query:"limit"`
|
||||
StartUnixMilli int64 `query:"startUnixMilli"`
|
||||
EndUnixMilli int64 `query:"endUnixMilli"`
|
||||
FieldContext FieldContext `query:"fieldContext"`
|
||||
FieldDataType FieldDataType `query:"fieldDataType"`
|
||||
MetricName string `query:"metricName"`
|
||||
SearchText string `query:"searchText"`
|
||||
Signal Signal `query:"signal"`
|
||||
Source Source `query:"source"`
|
||||
Limit int `query:"limit"`
|
||||
StartUnixMilli int64 `query:"startUnixMilli"`
|
||||
EndUnixMilli int64 `query:"endUnixMilli"`
|
||||
FieldContext FieldContext `query:"fieldContext"`
|
||||
FieldDataType FieldDataType `query:"fieldDataType"`
|
||||
MetricName string `query:"metricName"`
|
||||
MetricNamespace string `query:"metricNamespace"`
|
||||
SearchText string `query:"searchText"`
|
||||
}
|
||||
|
||||
type GettableFieldValues struct {
|
||||
@@ -344,9 +346,10 @@ func NewFieldKeySelectorFromPostableFieldKeysParams(params PostableFieldKeysPara
|
||||
req.Limit = 1000
|
||||
}
|
||||
|
||||
if params.MetricName != "" {
|
||||
if params.MetricName != "" || params.MetricNamespace != "" {
|
||||
req.MetricContext = &MetricContext{
|
||||
MetricName: params.MetricName,
|
||||
MetricName: params.MetricName,
|
||||
MetricNamespace: params.MetricNamespace,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -394,3 +394,20 @@ func TestNormalize(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFieldKeySelectorFromPostableFieldKeysParamsMetricNamespace(t *testing.T) {
|
||||
selector := NewFieldKeySelectorFromPostableFieldKeysParams(PostableFieldKeysParams{
|
||||
Signal: SignalMetrics,
|
||||
MetricNamespace: "system.cpu",
|
||||
})
|
||||
|
||||
if selector.MetricContext == nil {
|
||||
t.Fatalf("expected metric context to be set")
|
||||
}
|
||||
if selector.MetricContext.MetricNamespace != "system.cpu" {
|
||||
t.Fatalf("expected metric namespace to be propagated, got %q", selector.MetricContext.MetricNamespace)
|
||||
}
|
||||
if selector.MetricContext.MetricName != "" {
|
||||
t.Fatalf("expected metric name to remain empty, got %q", selector.MetricContext.MetricName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,3 +658,211 @@ def test_non_existent_metrics_returns_404(
|
||||
get_error_message(response.json())
|
||||
== "could not find the metric whatevergoennnsgoeshere"
|
||||
)
|
||||
|
||||
|
||||
# Verify /api/v1/fields/values filters label values by metricNamespace prefix.
|
||||
# Inserts metrics under ns.a and ns.b, then asserts a specific prefix returns
|
||||
# only matching values while a common prefix returns both.
|
||||
def test_metric_namespace_values_filtering(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
|
||||
metrics: List[Metrics] = [
|
||||
Metrics(
|
||||
metric_name="ns.a.requests_total",
|
||||
labels={"service": "svc-a"},
|
||||
timestamp=now - timedelta(minutes=2),
|
||||
value=10.0,
|
||||
),
|
||||
Metrics(
|
||||
metric_name="ns.b.requests_total",
|
||||
labels={"service": "svc-b"},
|
||||
timestamp=now - timedelta(minutes=2),
|
||||
value=20.0,
|
||||
),
|
||||
]
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Specific prefix: metricNamespace=ns.a should return only svc-a
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/fields/values"),
|
||||
timeout=5,
|
||||
headers={"authorization": f"Bearer {token}"},
|
||||
params={
|
||||
"signal": "metrics",
|
||||
"name": "service",
|
||||
"searchText": "",
|
||||
"metricNamespace": "ns.a",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
values = response.json()["data"]["values"]["stringValues"]
|
||||
assert "svc-a" in values
|
||||
assert "svc-b" not in values
|
||||
|
||||
# Common prefix: metricNamespace=ns should return both
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/fields/values"),
|
||||
timeout=5,
|
||||
headers={"authorization": f"Bearer {token}"},
|
||||
params={
|
||||
"signal": "metrics",
|
||||
"name": "service",
|
||||
"searchText": "",
|
||||
"metricNamespace": "ns",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
values = response.json()["data"]["values"]["stringValues"]
|
||||
assert "svc-a" in values
|
||||
assert "svc-b" in values
|
||||
|
||||
|
||||
# Verify /api/v1/fields/values with name=metric_name filters metric names by
|
||||
# metricNamespace prefix. A specific prefix returns only its metric names;
|
||||
# a common prefix returns metric names from all matching namespaces.
|
||||
def test_metric_namespace_metric_name_values_filtering(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
|
||||
metrics: List[Metrics] = [
|
||||
Metrics(
|
||||
metric_name="ns.a.cpu.utilization",
|
||||
labels={"host": "host-a"},
|
||||
timestamp=now - timedelta(minutes=2),
|
||||
value=50.0,
|
||||
),
|
||||
Metrics(
|
||||
metric_name="ns.b.cpu.utilization",
|
||||
labels={"host": "host-b"},
|
||||
timestamp=now - timedelta(minutes=2),
|
||||
value=60.0,
|
||||
),
|
||||
]
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Specific prefix: metricNamespace=ns.a should return only ns.a.* metric names
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/fields/values"),
|
||||
timeout=5,
|
||||
headers={"authorization": f"Bearer {token}"},
|
||||
params={
|
||||
"signal": "metrics",
|
||||
"name": "metric_name",
|
||||
"searchText": "",
|
||||
"metricNamespace": "ns.a",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
values = response.json()["data"]["values"]["stringValues"]
|
||||
assert "ns.a.cpu.utilization" in values
|
||||
assert "ns.b.cpu.utilization" not in values
|
||||
|
||||
# Common prefix: metricNamespace=ns should return both
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/fields/values"),
|
||||
timeout=5,
|
||||
headers={"authorization": f"Bearer {token}"},
|
||||
params={
|
||||
"signal": "metrics",
|
||||
"name": "metric_name",
|
||||
"searchText": "",
|
||||
"metricNamespace": "ns",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
values = response.json()["data"]["values"]["stringValues"]
|
||||
assert "ns.a.cpu.utilization" in values
|
||||
assert "ns.b.cpu.utilization" in values
|
||||
|
||||
|
||||
# Verify /api/v1/fields/keys filters attribute keys by metricNamespace prefix.
|
||||
# Metrics under ns.a and ns.b carry distinct labels; a specific prefix returns
|
||||
# only its keys while a common prefix returns keys from both namespaces.
|
||||
def test_metric_namespace_keys_filtering(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
|
||||
metrics: List[Metrics] = [
|
||||
Metrics(
|
||||
metric_name="ns.a.cpu.utilization",
|
||||
labels={"a_only_label": "val-a"},
|
||||
timestamp=now - timedelta(minutes=2),
|
||||
value=10.0,
|
||||
),
|
||||
Metrics(
|
||||
metric_name="ns.b.cpu.utilization",
|
||||
labels={"b_only_label": "val-b"},
|
||||
timestamp=now - timedelta(minutes=2),
|
||||
value=20.0,
|
||||
),
|
||||
]
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Specific prefix: metricNamespace=ns.a should return only a_only_label
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/fields/keys"),
|
||||
timeout=5,
|
||||
headers={"authorization": f"Bearer {token}"},
|
||||
params={
|
||||
"signal": "metrics",
|
||||
"searchText": "label",
|
||||
"metricNamespace": "ns.a",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
keys = response.json()["data"]["keys"]
|
||||
assert "a_only_label" in keys
|
||||
assert "b_only_label" not in keys
|
||||
|
||||
# Common prefix: metricNamespace=ns should return both keys
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/fields/keys"),
|
||||
timeout=5,
|
||||
headers={"authorization": f"Bearer {token}"},
|
||||
params={
|
||||
"signal": "metrics",
|
||||
"searchText": "label",
|
||||
"metricNamespace": "ns",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
keys = response.json()["data"]["keys"]
|
||||
assert "a_only_label" in keys
|
||||
assert "b_only_label" in keys
|
||||
|
||||
@@ -108,3 +108,81 @@ def test_list_meter_metric_names(
|
||||
assert (
|
||||
metric_name in metric_names
|
||||
), f"Expected {metric_name} in metric names, got: {metric_names}"
|
||||
|
||||
|
||||
# Verify /api/v1/fields/values with source=meter filters label values by metricNamespace
|
||||
# prefix. Inserts meter-source metrics under ns.a and ns.b, then asserts a specific
|
||||
# prefix returns only matching values while a common prefix returns both.
|
||||
def test_metric_namespace_meter_values_filtering(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_meter_samples: Callable[[List[MeterSample]], None],
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
|
||||
samples_a = make_meter_samples(
|
||||
"meter.ns.a.cost",
|
||||
{"service": "billing-a"},
|
||||
now,
|
||||
count=5,
|
||||
base_value=10.0,
|
||||
temporality="Delta",
|
||||
type_="Sum",
|
||||
is_monotonic=True,
|
||||
)
|
||||
samples_b = make_meter_samples(
|
||||
"meter.ns.b.cost",
|
||||
{"service": "billing-b"},
|
||||
now,
|
||||
count=5,
|
||||
base_value=20.0,
|
||||
temporality="Delta",
|
||||
type_="Sum",
|
||||
is_monotonic=True,
|
||||
)
|
||||
insert_meter_samples(samples_a + samples_b)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Specific prefix: metricNamespace=meter.ns.a should return only billing-a
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/fields/values"),
|
||||
timeout=5,
|
||||
headers={"authorization": f"Bearer {token}"},
|
||||
params={
|
||||
"signal": "metrics",
|
||||
"source": "meter",
|
||||
"name": "service",
|
||||
"searchText": "",
|
||||
"metricNamespace": "meter.ns.a",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
values = response.json()["data"]["values"]["stringValues"]
|
||||
assert "billing-a" in values
|
||||
assert "billing-b" not in values
|
||||
|
||||
# Common prefix: metricNamespace=meter.ns should return both
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/fields/values"),
|
||||
timeout=5,
|
||||
headers={"authorization": f"Bearer {token}"},
|
||||
params={
|
||||
"signal": "metrics",
|
||||
"source": "meter",
|
||||
"name": "service",
|
||||
"searchText": "",
|
||||
"metricNamespace": "meter.ns",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
values = response.json()["data"]["values"]["stringValues"]
|
||||
assert "billing-a" in values
|
||||
assert "billing-b" in values
|
||||
|
||||
Reference in New Issue
Block a user