Compare commits

..

2 Commits

Author SHA1 Message Date
nityanandagohain
9064db9c84 fix: cleanup 2026-03-18 20:12:29 +05:30
nityanandagohain
095becaf93 fix: updated implementation for using trace summary in list view 2026-03-18 19:33:10 +05:30
40 changed files with 267 additions and 2998 deletions

View File

@@ -190,7 +190,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.116.0
image: signoz/signoz:v0.115.0
ports:
- "8080:8080" # signoz port
# - "6060:6060" # pprof port

View File

@@ -117,7 +117,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.116.0
image: signoz/signoz:v0.115.0
ports:
- "8080:8080" # signoz port
volumes:

View File

@@ -181,7 +181,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.116.0}
image: signoz/signoz:${VERSION:-v0.115.0}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -109,7 +109,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.116.0}
image: signoz/signoz:${VERSION:-v0.115.0}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -1,4 +0,0 @@
./setup.sh
sleep 2
./validate.sh
./deletePostValidate.sh

View File

@@ -1,4 +0,0 @@
module: "github.com/signoz"
language: {
version: "v0.12.0"
}

View File

@@ -1,2 +0,0 @@
docker rm -f perses
rm signoz-0.0.1.tar.gz

File diff suppressed because it is too large Load Diff

View File

@@ -1,628 +0,0 @@
{
"kind": "Dashboard",
"metadata": {
"name": "the-everything-dashboard",
"project": "signoz"
},
"spec": {
"display": {
"name": "The everything dashboard",
"description": "Trying to cover as many concepts here as possible"
},
"duration": "1h",
"variables": [
{
"kind": "ListVariable",
"spec": {
"name": "serviceName",
"display": {
"name": "serviceName"
},
"allowAllValue": true,
"allowMultiple": false,
"plugin": {
"kind": "SigNozDynamicVariable",
"spec": {
"dynamicVariablesAttribute": "service.name",
"dynamicVariablesSource": "Metrics",
"sort": "DISABLED"
}
}
}
},
{
"kind": "ListVariable",
"spec": {
"name": "statusCodesFromQuery",
"display": {
"name": "statusCodesFromQuery"
},
"allowAllValue": true,
"allowMultiple": true,
"plugin": {
"kind": "SigNozQueryVariable",
"spec": {
"queryValue": "SELECT JSONExtractString(labels, 'http.status_code') AS status_code FROM signoz_metrics.distributed_time_series_v4_1day WHERE status_code != '' GROUP BY status_code",
"sort": "ASC"
}
}
}
},
{
"kind": "ListVariable",
"spec": {
"name": "limit",
"display": {
"name": "limit"
},
"allowAllValue": false,
"allowMultiple": false,
"plugin": {
"kind": "SigNozCustomVariable",
"spec": {
"customValue": "1,10,20,40,80,160,200",
"sort": "DISABLED"
}
}
}
}
],
"panels": {
"24e2697b": {
"kind": "Panel",
"spec": {
"display": {
"name": "total resp size"
},
"plugin": {
"kind": "TimeSeriesChart",
"spec": {}
},
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "SigNozBuilderQuery",
"spec": {
"name": "A",
"signal": "metrics",
"expression": "A",
"aggregations": [
{
"metricName": "http.server.response.body.size.sum",
"reduceTo": "sum",
"spaceAggregation": "sum",
"timeAggregation": "rate"
}
],
"filter": {
"expression": "http.response.status_code IN $statusCodesFromQuery"
},
"groupBy": [
{
"name": "service.name",
"fieldDataType": "string",
"fieldContext": "tag"
}
]
}
}
}
}
]
}
},
"ff2f72f1": {
"kind": "Panel",
"spec": {
"display": {
"name": "fraction of calls"
},
"plugin": {
"kind": "TimeSeriesChart",
"spec": {}
},
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "SigNozCompositeQuery",
"spec": {
"queries": [
{
"type": "builder_query",
"spec": {
"name": "A",
"signal": "metrics",
"expression": "A",
"aggregations": [
{
"metricName": "signoz_calls_total",
"reduceTo": "sum",
"spaceAggregation": "sum",
"timeAggregation": "rate"
}
],
"filter": {
"expression": "service.name IN $serviceName AND http.status_code IN $statusCodesFromQuery"
}
}
},
{
"type": "builder_query",
"spec": {
"name": "B",
"signal": "metrics",
"expression": "B",
"aggregations": [
{
"metricName": "signoz_calls_total",
"reduceTo": "sum",
"spaceAggregation": "sum",
"timeAggregation": "rate"
}
],
"filter": {
"expression": "service.name in $serviceName"
}
}
},
{
"type": "builder_formula",
"spec": {
"name": "F1",
"expression": "A / B"
}
}
]
}
}
}
}
]
}
},
"011605e7": {
"kind": "Panel",
"spec": {
"display": {
"name": "total resp size"
},
"plugin": {
"kind": "BarChart",
"spec": {}
},
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "SigNozBuilderQuery",
"spec": {
"name": "A",
"signal": "metrics",
"expression": "A",
"aggregations": [
{
"metricName": "http.server.response.body.size.sum",
"reduceTo": "sum",
"spaceAggregation": "sum",
"timeAggregation": "rate"
}
],
"filter": {
"expression": "http.response.status_code IN $statusCodesFromQuery"
},
"groupBy": [
{
"name": "service.name",
"fieldDataType": "string",
"fieldContext": "tag"
}
]
}
}
}
}
]
}
},
"e23516fc": {
"kind": "Panel",
"spec": {
"display": {
"name": "num traces for service"
},
"plugin": {
"kind": "StatChart",
"spec": {}
},
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "SigNozBuilderQuery",
"spec": {
"name": "A",
"signal": "traces",
"expression": "A",
"expressionAggregations": [
{
"expression": "count() "
}
],
"filter": {
"expression": "service.name = $serviceName "
}
}
}
}
}
]
}
},
"130c8d6b": {
"kind": "Panel",
"spec": {
"display": {
"name": "num logs for service"
},
"plugin": {
"kind": "StatChart",
"spec": {}
},
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "SigNozBuilderQuery",
"spec": {
"name": "A",
"signal": "logs",
"expression": "A",
"expressionAggregations": [
{
"expression": "count() "
}
],
"filter": {
"expression": "service.name = $serviceName "
}
}
}
}
}
]
}
},
"246f7c6d": {
"kind": "Panel",
"spec": {
"display": {
"name": "num traces for service per resp code"
},
"plugin": {
"kind": "PieChart",
"spec": {
"radius": 4892398421894629486193
}
},
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "SigNozBuilderQuery",
"spec": {
"name": "A",
"signal": "traces",
"expression": "A",
"expressionAggregations": [
{
"expression": "count() "
}
],
"filter": {
"expression": "service.name = $serviceName isEntryPoint = 'true'"
},
"groupBy": [
{
"name": "http.response.status_code",
"fieldDataType": "float64",
"fieldContext": "tag"
}
],
"legend": "\"{{http.response.status_code}}\""
}
}
}
}
]
}
},
"21f7d4d0": {
"kind": "Panel",
"spec": {
"display": {
"name": "average latency per service"
},
"plugin": {
"kind": "Table",
"spec": {}
},
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "SigNozClickHouseSQL",
"spec": {
"name": "A",
"query": "WITH\n __spatial_aggregation_cte AS\n (\n SELECT\n toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), toIntervalSecond(60)) AS ts,\n `service.name`,\n le,\n sum(value) / 60 AS value\n FROM signoz_metrics.distributed_samples_v4 AS points\n INNER JOIN\n (\n SELECT\n fingerprint,\n JSONExtractString(labels, 'service.name') AS `service.name`,\n JSONExtractString(labels, 'le') AS le\n FROM signoz_metrics.time_series_v4\n WHERE (metric_name IN ('signoz_latency.bucket')) AND (LOWER(temporality) LIKE LOWER('delta')) AND (__normalized = 0)\n GROUP BY\n fingerprint,\n `service.name`,\n le\n ) AS filtered_time_series ON points.fingerprint = filtered_time_series.fingerprint\n WHERE metric_name IN ('signoz_latency.bucket')\n GROUP BY\n ts,\n `service.name`,\n le\n ),\n __histogramCTE AS\n (\n SELECT\n ts,\n `service.name`,\n histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), 0.9) AS value\n FROM __spatial_aggregation_cte\n GROUP BY\n `service.name`,\n ts\n ORDER BY\n `service.name` ASC,\n ts ASC\n )\nSELECT\n `service.name` AS service,\n avg(value) AS avgLatency\nFROM __histogramCTE\nGROUP BY `service.name`"
}
}
}
}
]
}
},
"ad5fd556": {
"kind": "Panel",
"spec": {
"display": {
"name": "logs from service"
},
"plugin": {
"kind": "LogsTable",
"spec": {}
},
"queries": [
{
"kind": "LogQuery",
"spec": {
"plugin": {
"kind": "SigNozBuilderQuery",
"spec": {
"name": "A",
"signal": "logs",
"expression": "A",
"expressionAggregations": [
{
"expression": "count() "
}
],
"filter": {
"expression": "service.name = $serviceName"
},
"groupBy": [],
"order": [
{
"columnName": "timestamp",
"order": "desc"
},
{
"columnName": "id",
"order": "desc"
}
]
}
}
}
}
]
}
},
"f07b59ee": {
"kind": "Panel",
"spec": {
"display": {
"name": "response size buckets"
},
"plugin": {
"kind": "HistogramChart",
"spec": {}
},
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "SigNozBuilderQuery",
"spec": {
"name": "A",
"signal": "metrics",
"expression": "A",
"aggregations": [
{
"metricName": "http.server.response.body.size.bucket",
"reduceTo": "avg",
"spaceAggregation": "p90",
"timeAggregation": "rate"
}
]
}
}
}
}
]
}
},
"e1a41831": {
"kind": "Panel",
"spec": {
"display": {
"name": "trace operator"
},
"plugin": {
"kind": "TimeSeriesChart",
"spec": {}
},
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "SigNozCompositeQuery",
"spec": {
"queries": [
{
"type": "builder_query",
"spec": {
"name": "A",
"signal": "traces",
"expression": "A",
"expressionAggregations": [
{
"expression": "count() "
}
],
"filter": {
"expression": "service.name = 'sampleapp-gateway' "
},
"legend": "Gateway"
}
},
{
"type": "builder_query",
"spec": {
"name": "B",
"signal": "traces",
"expression": "B",
"expressionAggregations": [
{
"expression": "count() "
}
],
"filter": {
"expression": "http.response.status_code = 200"
},
"legend": "$serviceName"
}
},
{
"type": "builder_trace_operator",
"spec": {
"name": "T1",
"expression": "A -> B ",
"aggregations": [
{
"expression": "count()",
"alias": "request_count"
}
]
}
}
]
}
}
}
}
]
}
}
},
"layouts": [
{
"kind": "Grid",
"spec": {
"items": [
{
"x": 0,
"y": 0,
"width": 6,
"height": 6,
"content": {
"$ref": "#/spec/panels/24e2697b"
}
},
{
"x": 6,
"y": 0,
"width": 6,
"height": 6,
"content": {
"$ref": "#/spec/panels/ff2f72f1"
}
},
{
"x": 0,
"y": 6,
"width": 6,
"height": 6,
"content": {
"$ref": "#/spec/panels/011605e7"
}
},
{
"x": 6,
"y": 6,
"width": 6,
"height": 3,
"content": {
"$ref": "#/spec/panels/e23516fc"
}
},
{
"x": 6,
"y": 9,
"width": 6,
"height": 3,
"content": {
"$ref": "#/spec/panels/130c8d6b"
}
},
{
"x": 0,
"y": 12,
"width": 6,
"height": 6,
"content": {
"$ref": "#/spec/panels/246f7c6d"
}
},
{
"x": 6,
"y": 12,
"width": 6,
"height": 6,
"content": {
"$ref": "#/spec/panels/21f7d4d0"
}
},
{
"x": 0,
"y": 18,
"width": 6,
"height": 6,
"content": {
"$ref": "#/spec/panels/ad5fd556"
}
},
{
"x": 6,
"y": 18,
"width": 6,
"height": 6,
"content": {
"$ref": "#/spec/panels/f07b59ee"
}
},
{
"x": 0,
"y": 24,
"width": 12,
"height": 6,
"content": {
"$ref": "#/spec/panels/e1a41831"
}
}
]
}
}
]
}
}

View File

@@ -1,9 +0,0 @@
{
"id": "signoz",
"name": "signoz",
"metaData": {
"buildInfo": {
"buildVersion": "0.0.1"
}
}
}

View File

@@ -1,88 +0,0 @@
{
"name": "@signoz/signoz",
"version": "0.0.1",
"description": "signoz plugin module for Perses",
"perses": {
"schemasPath": "schemas",
"plugins": [
{
"kind": "Datasource",
"spec": {
"name": "SigNozDatasource"
}
},
{
"kind": "Variable",
"spec": {
"name": "SigNozCustomVariable"
}
},
{
"kind": "Variable",
"spec": {
"name": "SigNozDynamicVariable"
}
},
{
"kind": "Variable",
"spec": {
"name": "SigNozQueryVariable"
}
},
{
"kind": "TimeSeriesQuery",
"spec": {
"name": "SigNozBuilderQuery"
}
},
{
"kind": "TimeSeriesQuery",
"spec": {
"name": "SigNozClickHouseSQL"
}
},
{
"kind": "TimeSeriesQuery",
"spec": {
"name": "SigNozPromQL"
}
},
{
"kind": "TimeSeriesQuery",
"spec": {
"name": "SigNozCompositeQuery"
}
},
{
"kind": "TimeSeriesQuery",
"spec": {
"name": "SigNozTraceOperator"
}
},
{
"kind": "TimeSeriesQuery",
"spec": {
"name": "SigNozFormula"
}
},
{
"kind": "TimeSeriesQuery",
"spec": {
"name": "SigNozJoin"
}
},
{
"kind": "LogQuery",
"spec": {
"name": "SigNozBuilderQuery"
}
},
{
"kind": "TraceQuery",
"spec": {
"name": "SigNozBuilderQuery"
}
}
]
}
}

View File

@@ -1,27 +0,0 @@
Panel Plugins
All SigNoz panels use Perses built-in panel kinds directly. No custom CUE
schemas exist yet because Perses does not publish CUE schemas for its panel
plugins (only Go SDK + TypeScript). Once it does, each panel can embed the
upstream spec and add the SigNoz-specific fields listed below.
SigNoz-specific fields by panel type:
TimeSeriesChart timePreference
StatChart timePreference, contextLinks
BarChart timePreference, contextLinks
PieChart timePreference, contextLinks
Table timePreference, contextLinks
LogsTable (List) timePreference, selectedLogFields, selectedTracesFields, columnWidths
TraceTable timePreference, selectedTracesFields, columnWidths
HistogramChart timePreference, contextLinks, bucketCount, bucketWidth, mergeAllActiveQueries
Common fields:
timePreference — panel-local vs dashboard-global time range
contextLinks — clickable drill-down links on data points
Panel-specific fields:
selectedLogFields / selectedTracesFields — which fields to display as columns in list views
columnWidths — saved column width overrides
bucketCount / bucketWidth — histogram bucket configuration
mergeAllActiveQueries — combine multiple queries into one histogram

View File

@@ -1,9 +0,0 @@
package model
kind: "SigNozDatasource"
// SigNoz has a single built-in backend — the frontend already knows
// the API endpoint, so there is no connection config to validate.
// Add fields here if SigNoz ever supports multiple backends or
// configurable API versions.
spec: close({})

View File

@@ -1,4 +0,0 @@
{
"kind": "SigNozDatasource",
"spec": {}
}

View File

@@ -1,84 +0,0 @@
package model
// Source: pkg/types/querybuildertypes/querybuildertypesv5/builder_query.go — QueryBuilderQuery
kind: "SigNozBuilderQuery"
spec: close({
name: #QueryName
signal: "metrics" | "logs" | "traces"
expression: string
disabled?: bool | *false
// Metrics use structured aggregations; logs/traces use expression-based.
aggregations?: [...#MetricAggregation]
expressionAggregations?: [...#ExpressionAggregation]
filter?: #FilterExpression
groupBy?: [...#GroupByItem]
order?: [...#OrderByItem]
selectFields?: [...]
limit?: #Limit
limitBy?: #LimitBy
offset?: #Offset
cursor?: string
having?: #HavingExpression
// secondaryAggregations not added — not yet implemented.
functions?: [...#Function]
legend?: string
stepInterval?: number
reduceTo?: #ReduceTo
pageSize?: int & >=1
source?: string
})
#LimitBy: close({
keys: [...string]
value: string
})
#QueryName: =~"^[A-Za-z][A-Za-z0-9_]*$"
#ReduceTo: "sum" | "count" | "avg" | "min" | "max" | "last" | "median"
#Limit: int & >=0 & <=10000
#Offset: int & >=0
#MetricAggregation: close({
metricName: string & !=""
timeAggregation: "latest" | "sum" | "avg" | "min" | "max" | "count" | "rate" | "increase"
spaceAggregation: "sum" | "avg" | "min" | "max" | "count" | "p50" | "p75" | "p90" | "p95" | "p99"
reduceTo?: #ReduceTo
temporality?: "delta" | "cumulative" | "unspecified"
})
#ExpressionAggregation: close({
expression: string & !=""
alias?: string
})
#FilterExpression: close({
expression: string
})
#GroupByItem: close({
name: string & !=""
fieldDataType?: string
fieldContext?: string
})
#OrderByItem: close({
columnName: string & !=""
order: "asc" | "desc"
})
#HavingExpression: close({
expression: string
})
#Function: close({
name: "cutOffMin" | "cutOffMax" | "clampMin" | "clampMax" |
"absolute" | "runningDiff" | "log2" | "log10" |
"cumulativeSum" | "ewma3" | "ewma5" | "ewma7" |
"median3" | "median5" | "median7" | "timeShift" |
"anomaly" | "fillZero"
args?: [...close({value: number | string | bool})]
})

View File

@@ -1,24 +0,0 @@
{
"kind": "SigNozBuilderQuery",
"spec": {
"name": "A",
"signal": "metrics",
"expression": "A",
"aggregations": [
{
"metricName": "redis_keyspace_hits",
"timeAggregation": "rate",
"spaceAggregation": "sum",
"reduceTo": "sum"
}
],
"filter": {
"expression": "host_name IN $host_name"
},
"groupBy": [],
"order": [],
"disabled": false,
"legend": "Hit/s across all hosts",
"stepInterval": 60
}
}

View File

@@ -1,12 +0,0 @@
package model
// Source: pkg/types/querybuildertypes/querybuildertypesv5/clickhouse_query.go — ClickHouseQuery
kind: "SigNozClickHouseSQL"
spec: close({
name: #QueryName
query: string & !=""
disabled?: bool | *false
legend?: string
})
#QueryName: =~"^[A-Za-z][A-Za-z0-9_]*$"

View File

@@ -1,9 +0,0 @@
{
"kind": "SigNozClickHouseSQL",
"spec": {
"name": "A",
"query": "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) AS ts, count() AS total FROM signoz_logs.distributed_logs GROUP BY ts ORDER BY ts",
"disabled": false,
"legend": "Log count"
}
}

View File

@@ -1,46 +0,0 @@
package model
import (
bq "github.com/signoz/schemas/queries/signoz-builder-query:model"
f "github.com/signoz/schemas/queries/signoz-formula:model"
j "github.com/signoz/schemas/queries/signoz-join:model"
to "github.com/signoz/schemas/queries/signoz-trace-operator:model"
pql "github.com/signoz/schemas/queries/signoz-promql:model"
ch "github.com/signoz/schemas/queries/signoz-clickhouse-sql:model"
)
// Source: pkg/types/querybuildertypes/querybuildertypesv5/req.go — CompositeQuery
// SigNozCompositeQuery groups multiple query plugins into a single
// query request. Each entry is a typed envelope whose spec is
// validated by the corresponding plugin schema.
kind: "SigNozCompositeQuery"
spec: close({
queries: [...#QueryEnvelope]
})
// QueryEnvelope wraps a single query plugin with a type discriminator.
#QueryEnvelope:
close({
type: "builder_query",
spec: bq.spec
}) |
close({
type: "builder_formula",
spec: f.spec
}) |
close({
type: "builder_join",
spec: j.spec
}) |
close({
type: "builder_trace_operator",
spec: to.spec
}) |
close({
type: "promql",
spec: pql.spec
}) |
close({
type: "clickhouse_sql",
spec: ch.spec
})

View File

@@ -1,53 +0,0 @@
{
"kind": "SigNozCompositeQuery",
"spec": {
"queries": [
{
"type": "builder_query",
"spec": {
"name": "A",
"signal": "metrics",
"expression": "A",
"aggregations": [
{
"metricName": "redis_keyspace_hits",
"timeAggregation": "rate",
"spaceAggregation": "sum",
"reduceTo": "sum"
}
],
"filter": {
"expression": "host_name IN $host_name"
}
}
},
{
"type": "builder_query",
"spec": {
"name": "B",
"signal": "metrics",
"expression": "B",
"aggregations": [
{
"metricName": "redis_keyspace_misses",
"timeAggregation": "rate",
"spaceAggregation": "sum",
"reduceTo": "sum"
}
],
"filter": {
"expression": "host_name IN $host_name"
}
}
},
{
"type": "builder_formula",
"spec": {
"name": "F1",
"expression": "A / (A + B) * 100",
"legend": "Hit rate %"
}
}
]
}
}

View File

@@ -1,27 +0,0 @@
package model
// Source: pkg/types/querybuildertypes/querybuildertypesv5/formula.go — QueryBuilderFormula
kind: "SigNozFormula"
spec: close({
name: #QueryName
expression: string
disabled?: bool | *false
legend?: string
limit?: #Limit
having?: #HavingExpression
stepInterval?: number
order?: [...#OrderByItem]
})
#QueryName: =~"^[A-Za-z][A-Za-z0-9_]*$"
#Limit: int & >=0 & <=10000
#HavingExpression: close({
expression: string
})
#OrderByItem: close({
columnName: string & !=""
order: "asc" | "desc"
})

View File

@@ -1,8 +0,0 @@
{
"kind": "SigNozFormula",
"spec": {
"name": "F1",
"expression": "A / B * 100",
"legend": "Hit rate %"
}
}

View File

@@ -1,75 +0,0 @@
package model
// Source: pkg/types/querybuildertypes/querybuildertypesv5/join.go — QueryBuilderJoin
kind: "SigNozJoin"
spec: close({
name: #QueryName
left: #QueryRef
right: #QueryRef
type: #JoinType
on: string
disabled?: bool | *false
aggregations?: [...#MetricAggregation]
expressionAggregations?: [...#ExpressionAggregation]
selectFields?: [...]
filter?: #FilterExpression
groupBy?: [...#GroupByItem]
having?: #HavingExpression
// secondaryAggregations not added — not yet implemented.
order?: [...#OrderByItem]
limit?: #Limit
functions?: [...#Function]
})
#QueryRef: close({
name: #QueryName
})
#JoinType: "inner" | "left" | "right" | "full" | "cross"
#QueryName: =~"^[A-Za-z][A-Za-z0-9_]*$"
#ReduceTo: "sum" | "count" | "avg" | "min" | "max" | "last" | "median"
#Limit: int & >=0 & <=10000
#MetricAggregation: close({
metricName: string & !=""
timeAggregation: "latest" | "sum" | "avg" | "min" | "max" | "count" | "rate" | "increase"
spaceAggregation: "sum" | "avg" | "min" | "max" | "count" | "p50" | "p75" | "p90" | "p95" | "p99"
reduceTo?: #ReduceTo
temporality?: "delta" | "cumulative" | "unspecified"
})
#ExpressionAggregation: close({
expression: string & !=""
alias?: string
})
#FilterExpression: close({
expression: string
})
#GroupByItem: close({
name: string & !=""
fieldDataType?: string
fieldContext?: string
})
#OrderByItem: close({
columnName: string & !=""
order: "asc" | "desc"
})
#HavingExpression: close({
expression: string
})
#Function: close({
name: "cutOffMin" | "cutOffMax" | "clampMin" | "clampMax" |
"absolute" | "runningDiff" | "log2" | "log10" |
"cumulativeSum" | "ewma3" | "ewma5" | "ewma7" |
"median3" | "median5" | "median7" | "timeShift" |
"anomaly" | "fillZero"
args?: [...close({value: number | string | bool})]
})

View File

@@ -1,11 +0,0 @@
{
"kind": "SigNozJoin",
"spec": {
"name": "J1",
"left": {"name": "A"},
"right": {"name": "B"},
"type": "inner",
"on": "service.name = service.name",
"disabled": false
}
}

View File

@@ -1,14 +0,0 @@
package model
// Source: pkg/types/querybuildertypes/querybuildertypesv5/prom_query.go — PromQuery
kind: "SigNozPromQL"
spec: close({
name: #QueryName
query: string & !=""
disabled?: bool | *false
step?: number
stats?: bool
legend?: string
})
#QueryName: =~"^[A-Za-z][A-Za-z0-9_]*$"

View File

@@ -1,9 +0,0 @@
{
"kind": "SigNozPromQL",
"spec": {
"name": "A",
"query": "rate(http_requests_total{status=\"200\"}[5m])",
"disabled": false,
"legend": "{{method}} {{path}}"
}
}

View File

@@ -1,68 +0,0 @@
package model
// Source: pkg/types/querybuildertypes/querybuildertypesv5/trace_operator.go — QueryBuilderTraceOperator
// SigNozTraceOperator composes multiple trace BuilderQueries using
// relational operators (=>, ->, &&, ||, NOT) to query trace relationships.
// Signal is implicitly "traces" — all referenced queries must be trace queries.
kind: "SigNozTraceOperator"
spec: close({
name: #QueryName
// Operator expression composing trace queries, e.g. "A => B && C".
expression: string & !=""
disabled?: bool | *false
// Which query's spans to return (must be a query referenced in expression).
returnSpansFrom?: #QueryName
aggregations?: [...#ExpressionAggregation]
filter?: #FilterExpression
groupBy?: [...#GroupByItem]
order?: [...#OrderByItem]
limit?: #Limit
offset?: #Offset
cursor?: string
functions?: [...#Function]
stepInterval?: number
having?: #HavingExpression
legend?: string
selectFields?: [...]
})
#QueryName: =~"^[A-Za-z][A-Za-z0-9_]*$"
#Limit: int & >=0 & <=10000
#Offset: int & >=0
#ExpressionAggregation: close({
expression: string & !=""
alias?: string
})
#FilterExpression: close({
expression: string
})
#GroupByItem: close({
name: string & !=""
fieldDataType?: string
fieldContext?: string
})
#OrderByItem: close({
columnName: string & !=""
order: "asc" | "desc"
})
#HavingExpression: close({
expression: string
})
#Function: close({
name: "cutOffMin" | "cutOffMax" | "clampMin" | "clampMax" |
"absolute" | "runningDiff" | "log2" | "log10" |
"cumulativeSum" | "ewma3" | "ewma5" | "ewma7" |
"median3" | "median5" | "median7" | "timeShift" |
"anomaly" | "fillZero"
args?: [...close({value: number | string | bool})]
})

View File

@@ -1,19 +0,0 @@
{
"kind": "SigNozTraceOperator",
"spec": {
"name": "T1",
"expression": "A => B",
"returnSpansFrom": "A",
"aggregations": [
{
"expression": "count()",
"alias": "request_count"
}
],
"filter": {
"expression": "service.name = 'frontend'"
},
"groupBy": [],
"order": []
}
}

View File

@@ -1,10 +0,0 @@
package model
// defaultValue lives on the Perses ListVariable wrapper (spec level).
kind: "SigNozCustomVariable"
spec: close({
customValue: =~"^[^,]+(,[^,]+)*$"
sort?: #VariableSortOrder
})
#VariableSortOrder: *"DISABLED" | "ASC" | "DESC"

View File

@@ -1,7 +0,0 @@
{
"kind": "SigNozCustomVariable",
"spec": {
"customValue": "production,staging,development",
"sort": "DISABLED"
}
}

View File

@@ -1,11 +0,0 @@
package model
// defaultValue lives on the Perses ListVariable wrapper (spec level).
kind: "SigNozDynamicVariable"
spec: close({
dynamicVariablesAttribute: string
dynamicVariablesSource: string
sort?: #VariableSortOrder
})
#VariableSortOrder: *"DISABLED" | "ASC" | "DESC"

View File

@@ -1,8 +0,0 @@
{
"kind": "SigNozDynamicVariable",
"spec": {
"dynamicVariablesAttribute": "host_name",
"dynamicVariablesSource": "metrics",
"sort": "ASC"
}
}

View File

@@ -1,10 +0,0 @@
package model
// defaultValue lives on the Perses ListVariable wrapper (spec level).
kind: "SigNozQueryVariable"
spec: close({
queryValue: string
sort?: #VariableSortOrder
})
#VariableSortOrder: *"DISABLED" | "ASC" | "DESC"

View File

@@ -1,7 +0,0 @@
{
"kind": "SigNozQueryVariable",
"spec": {
"queryValue": "SELECT DISTINCT host_name FROM signoz_metrics.distributed_time_series_v4_1day WHERE metric_name = 'redis_cpu_time'",
"sort": "ASC"
}
}

View File

@@ -1,2 +0,0 @@
tar -czf signoz-0.0.1.tar.gz package.json mf-manifest.json schemas cue.mod
docker run -d -p 8080:8080 --name perses -v $(pwd)/signoz-0.0.1.tar.gz:/etc/perses/plugins-archive/signoz-plugin.tar.gz persesdev/perses:latest-debug

View File

@@ -1 +0,0 @@
percli lint -f ./examples/perses.json --online

View File

@@ -361,6 +361,10 @@ func (q *builderQuery[T]) executeWindowList(ctx context.Context) (*qbtypes.Resul
if err != nil {
return nil, err
}
// skip = true means we have indentified from the query itself that we don't need to execute this bucket
if stmt.Skip {
continue
}
warnings = stmt.Warnings
warningsDocURL = stmt.WarningsDocURL
// Execute with proper context for partial value detection

View File

@@ -121,8 +121,18 @@ func (b *traceQueryStatementBuilder) Build(
if !ok {
b.logger.DebugContext(ctx, "failed to get trace time range", "trace_ids", traceIDs)
} else if traceStart > 0 && traceEnd > 0 {
start = uint64(traceStart)
end = uint64(traceEnd)
// we don't need to query if the start and end are non overlapping
if uint64(traceStart) > end || uint64(traceEnd) < start {
return &qbtypes.Statement{Skip: true}, nil
}
// clamp start/end to trace time range to avoid overlap between buckets
if uint64(traceStart) > start {
start = uint64(traceStart)
}
if uint64(traceEnd) < end {
end = uint64(traceEnd)
}
b.logger.DebugContext(ctx, "optimized time range for traces", "trace_ids", traceIDs, "start", start, "end", end)
}
}

View File

@@ -47,6 +47,7 @@ type Statement struct {
Args []any
Warnings []string
WarningsDocURL string
Skip bool // don't run this query
}
// StatementBuilder builds the query.

View File

@@ -696,7 +696,6 @@ def test_traces_list_with_corrupt_data(
assert response.status_code == status_code
if response.status_code == HTTPStatus.OK:
if not results(traces):
# No results expected
assert response.json()["data"]["data"]["results"][0]["rows"] is None
@@ -2026,3 +2025,249 @@ def test_traces_fill_zero_formula_with_group_by(
expected_by_ts=expectations[service_name],
context=f"traces/fillZero/F1/{service_name}",
)
def test_traces_list_filter_by_trace_id(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
"""
Tests that filtering by trace_id:
1. Returns the matching span (narrow window, single bucket).
2. Does not return duplicate spans when the query window spans multiple
exponential buckets (>1 h) — regression test for the expand-vs-clamp bug.
3. Returns no results when the query window does not contain the trace.
"""
target_trace_id = TraceIdGenerator.trace_id()
other_trace_id = TraceIdGenerator.trace_id()
span_id_root = TraceIdGenerator.span_id()
other_span_id = TraceIdGenerator.span_id()
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
common_resources = {
"deployment.environment": "production",
"service.name": "trace-filter-service",
"cloud.provider": "integration",
}
insert_traces(
[
Traces(
timestamp=now - timedelta(seconds=10),
duration=timedelta(seconds=5),
trace_id=target_trace_id,
span_id=span_id_root,
parent_span_id="",
name="root-span",
kind=TracesKind.SPAN_KIND_SERVER,
status_code=TracesStatusCode.STATUS_CODE_OK,
status_message="",
resources=common_resources,
attributes={"http.request.method": "GET"},
),
# span from a different trace — must not appear in results
Traces(
timestamp=now - timedelta(seconds=5),
duration=timedelta(seconds=1),
trace_id=other_trace_id,
span_id=other_span_id,
parent_span_id="",
name="other-root-span",
kind=TracesKind.SPAN_KIND_SERVER,
status_code=TracesStatusCode.STATUS_CODE_OK,
status_message="",
resources=common_resources,
attributes={},
),
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
trace_filter = f"trace_id = '{target_trace_id}'"
def _query(start_ms: int, end_ms: int) -> List:
response = make_query_request(
signoz,
token,
start_ms=start_ms,
end_ms=end_ms,
request_type="raw",
queries=[
{
"type": "builder_query",
"spec": {
"name": "A",
"signal": "traces",
"limit": 100,
"offset": 0,
"filter": {"expression": trace_filter},
"order": [{"key": {"name": "timestamp"}, "direction": "desc"}],
"selectFields": [
{
"name": "name",
"fieldDataType": "string",
"fieldContext": "span",
"signal": "traces",
}
],
},
}
],
)
assert response.status_code == HTTPStatus.OK
assert response.json()["status"] == "success"
return response.json()["data"]["data"]["results"][0]["rows"] or []
now_ms = int(now.timestamp() * 1000)
# --- Test 1: narrow window (single bucket, <1 h) ---
narrow_start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
narrow_rows = _query(narrow_start_ms, now_ms)
assert len(narrow_rows) == 1, (
f"Expected 1 span for trace_id filter (narrow window), got {len(narrow_rows)}"
)
assert narrow_rows[0]["data"]["span_id"] == span_id_root
assert narrow_rows[0]["data"]["trace_id"] == target_trace_id
# --- Test 2: wide window (>1 h, triggers multiple exponential buckets) ---
# should just return 1 span, not duplicate
wide_start_ms = int((now - timedelta(hours=12)).timestamp() * 1000)
wide_rows = _query(wide_start_ms, now_ms)
assert len(wide_rows) == 1, (
f"Expected 1 span for trace_id filter (wide window, multi-bucket), "
f"got {len(wide_rows)} — possible duplicate-span regression"
)
assert wide_rows[0]["data"]["span_id"] == span_id_root
assert wide_rows[0]["data"]["trace_id"] == target_trace_id
# --- Test 3: window that does not contain the trace returns no results ---
past_start_ms = int((now - timedelta(hours=6)).timestamp() * 1000)
past_end_ms = int((now - timedelta(hours=2)).timestamp() * 1000)
past_rows = _query(past_start_ms, past_end_ms)
assert len(past_rows) == 0, (
f"Expected 0 spans for trace_id filter outside time window, "
f"got {len(past_rows)}"
)
def test_traces_list_filter_by_trace_id_cross_bucket(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
"""
Regression test for multi-bucket trace_id queries.
Setup: a single trace with two spans placed in different exponential buckets.
makeBuckets over a 3-hour window produces (newest-first):
bucket 1: [now-1h, now] ← span_a lives here (now-30min)
bucket 2: [now-3h, now-1h] ← span_b lives here (now-90min)
Without the fix: both buckets expanded their window to [traceStart, traceEnd]
and therefore each returned both spans → 4 rows total (duplicates).
With the fix: each bucket is tied to its intersection with the trace range,
so bucket 1 returns span_a and bucket 2 returns span_b → exactly 2 rows.
"""
trace_id = TraceIdGenerator.trace_id()
span_id_a = TraceIdGenerator.span_id()
span_id_b = TraceIdGenerator.span_id()
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
common_resources = {
"deployment.environment": "production",
"service.name": "cross-bucket-service",
"cloud.provider": "integration",
}
insert_traces(
[
# span_a: sits in bucket 1 [now-1h, now]
Traces(
timestamp=now - timedelta(minutes=30),
duration=timedelta(seconds=1),
trace_id=trace_id,
span_id=span_id_a,
parent_span_id="",
name="span-a",
kind=TracesKind.SPAN_KIND_SERVER,
status_code=TracesStatusCode.STATUS_CODE_OK,
status_message="",
resources=common_resources,
attributes={},
),
# span_b: sits in bucket 2 [now-3h, now-1h]
Traces(
timestamp=now - timedelta(minutes=90),
duration=timedelta(seconds=1),
trace_id=trace_id,
span_id=span_id_b,
parent_span_id=span_id_a,
name="span-b",
kind=TracesKind.SPAN_KIND_CLIENT,
status_code=TracesStatusCode.STATUS_CODE_OK,
status_message="",
resources=common_resources,
attributes={},
),
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# 3-hour window forces makeBuckets to create two buckets:
# bucket 1: [now-1h, now]
# bucket 2: [now-3h, now-1h]
start_ms = int((now - timedelta(hours=3)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
response = make_query_request(
signoz,
token,
start_ms=start_ms,
end_ms=end_ms,
request_type="raw",
queries=[
{
"type": "builder_query",
"spec": {
"name": "A",
"signal": "traces",
"limit": 100,
"offset": 0,
"filter": {"expression": f"trace_id = '{trace_id}'"},
"order": [{"key": {"name": "timestamp"}, "direction": "desc"}],
"selectFields": [
{
"name": "name",
"fieldDataType": "string",
"fieldContext": "span",
"signal": "traces",
}
],
},
}
],
)
assert response.status_code == HTTPStatus.OK
assert response.json()["status"] == "success"
rows = response.json()["data"]["data"]["results"][0]["rows"] or []
assert len(rows) == 2, (
f"Expected exactly 2 spans (one per bucket), got {len(rows)}"
f"likely a duplicate-span regression from bucket window expansion"
)
returned_span_ids = {r["data"]["span_id"] for r in rows}
assert returned_span_ids == {span_id_a, span_id_b}
assert all(r["data"]["trace_id"] == trace_id for r in rows)