diff --git a/README.md b/README.md index 324b8e2d69..532abcf0bc 100644 --- a/README.md +++ b/README.md @@ -35,14 +35,31 @@ SigNoz helps developers monitor applications and troubleshoot problems in their 👉 Filter and query logs, build dashboards and alerts based on attributes in logs -![screenzy-1670570187181](https://user-images.githubusercontent.com/504541/206646629-829fdafe-70e2-4503-a9c4-1301b7918586.png) -
-![screenzy-1670570193901](https://user-images.githubusercontent.com/504541/206646676-a676fdeb-331c-4847-aea9-d1cabf7c47e1.png) -
-![screenzy-1670570199026](https://user-images.githubusercontent.com/504541/206646754-28c5534f-0377-428c-9c6e-5c7c0d9dd22d.png) -
-![screenzy-1670569888865](https://user-images.githubusercontent.com/504541/206645819-1e865a56-71b4-4fde-80cc-fbdb137a4da5.png) +👉 Record exceptions automatically in Python, Java, Ruby, and Javascript +👉 Easy to set alerts with DIY query builder + + +### Application Metrics + +application_metrics + +### Distributed Tracing +distributed_tracing_2 2 + +distributed_tracing_1 + +### Logs Management + +logs_management + +### Infrastructure Monitoring + +infrastructure_monitoring + +### Alerts + +alerts_management

@@ -65,6 +82,10 @@ Come say Hi to us on [Slack](https://signoz.io/slack) 👋 - See exact request trace to figure out issues in downstream services, slow DB queries, call to 3rd party services like payment gateways, etc - Filter traces by service name, operation, latency, error, tags/annotations. - Run aggregates on trace data (events/spans) to get business relevant metrics. e.g. You can get error rate and 99th percentile latency of `customer_type: gold` or `deployment_version: v2` or `external_call: paypal` +- Native support for OpenTelemetry Logs, advanced log query builder, and automatic log collection from k8s cluster +- Lightening quick log analytics ([Logs Perf. Benchmark](https://signoz.io/blog/logs-performance-benchmark/)) +- End-to-End visibility into infrastructure performance, ingest metrics from all kinds of host environments +- Easy to set alerts with DIY query builder

diff --git a/frontend/src/components/TimePreferenceDropDown/index.tsx b/frontend/src/components/TimePreferenceDropDown/index.tsx index ff6d31bcc1..3ce9795f15 100644 --- a/frontend/src/components/TimePreferenceDropDown/index.tsx +++ b/frontend/src/components/TimePreferenceDropDown/index.tsx @@ -32,8 +32,9 @@ function TimePreference({ return ( - - + + + ); } diff --git a/pkg/query-service/app/metrics/query_builder.go b/pkg/query-service/app/metrics/query_builder.go index 784b727514..c57cdf49ca 100644 --- a/pkg/query-service/app/metrics/query_builder.go +++ b/pkg/query-service/app/metrics/query_builder.go @@ -44,6 +44,9 @@ var AggregateOperatorToSQLFunc = map[model.AggregateOperator]string{ model.RATE_MIN: "min", } +// See https://github.com/SigNoz/signoz/issues/2151#issuecomment-1467249056 +var rateWithoutNegative = `if (runningDifference(value) < 0 OR runningDifference(ts) < 0, nan, runningDifference(value)/runningDifference(ts))` + var SupportedFunctions = []string{"exp", "log", "ln", "exp2", "log2", "exp10", "log10", "sqrt", "cbrt", "erf", "erfc", "lgamma", "tgamma", "sin", "cos", "tan", "asin", "acos", "atan", "degrees", "radians"} func GoValuateFuncs() map[string]govaluate.ExpressionFunction { @@ -200,7 +203,7 @@ func BuildMetricQuery(qp *model.QueryRangeParamsV2, mq *model.MetricQuery, table subQuery := fmt.Sprintf( queryTmpl, "any(labels) as labels, "+groupTags, qp.Step, op, filterSubQuery, groupBy, groupTags, ) // labels will be same so any should be fine - query := `SELECT %s ts, runningDifference(value)/runningDifference(ts) as value FROM(%s)` + query := `SELECT %s ts, ` + rateWithoutNegative + ` as value FROM(%s)` query = fmt.Sprintf(query, "labels as fullLabels,", subQuery) return query, nil @@ -211,14 +214,14 @@ func BuildMetricQuery(qp *model.QueryRangeParamsV2, mq *model.MetricQuery, table subQuery := fmt.Sprintf( queryTmpl, rateGroupTags, qp.Step, op, filterSubQuery, rateGroupBy, rateGroupTags, ) // labels will be same so any should be fine - query := `SELECT %s ts, runningDifference(value)/runningDifference(ts) as value FROM(%s) OFFSET 1` + query := `SELECT %s ts, ` + rateWithoutNegative + `as value FROM(%s)` query = fmt.Sprintf(query, groupTags, subQuery) query = fmt.Sprintf(`SELECT %s ts, sum(value) as value FROM (%s) GROUP BY %s ORDER BY %s ts`, groupTags, query, groupBy, groupTags) return query, nil case model.RATE_SUM, model.RATE_MAX, model.RATE_AVG, model.RATE_MIN: op := fmt.Sprintf("%s(value)", AggregateOperatorToSQLFunc[mq.AggregateOperator]) subQuery := fmt.Sprintf(queryTmpl, groupTags, qp.Step, op, filterSubQuery, groupBy, groupTags) - query := `SELECT %s ts, runningDifference(value)/runningDifference(ts) as value FROM(%s) OFFSET 1` + query := `SELECT %s ts, ` + rateWithoutNegative + `as value FROM(%s)` query = fmt.Sprintf(query, groupTags, subQuery) return query, nil case model.P05, model.P10, model.P20, model.P25, model.P50, model.P75, model.P90, model.P95, model.P99: @@ -232,9 +235,10 @@ func BuildMetricQuery(qp *model.QueryRangeParamsV2, mq *model.MetricQuery, table subQuery := fmt.Sprintf( queryTmpl, rateGroupTags, qp.Step, op, filterSubQuery, rateGroupBy, rateGroupTags, ) // labels will be same so any should be fine - query := `SELECT %s ts, runningDifference(value)/runningDifference(ts) as value FROM(%s) OFFSET 1` + query := `SELECT %s ts, ` + rateWithoutNegative + ` as value FROM(%s)` query = fmt.Sprintf(query, groupTags, subQuery) - query = fmt.Sprintf(`SELECT %s ts, sum(value) as value FROM (%s) GROUP BY %s ORDER BY %s ts`, groupTags, query, groupBy, groupTags) + // filter out NaN values from the rate query as histogramQuantile doesn't support NaN values + query = fmt.Sprintf(`SELECT %s ts, sum(value) as value FROM (%s) GROUP BY %s HAVING isNaN(value) = 0 ORDER BY %s ts`, groupTags, query, groupBy, groupTags) value := AggregateOperatorToPercentile[mq.AggregateOperator] query = fmt.Sprintf(`SELECT %s ts, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), %.3f) as value FROM (%s) GROUP BY %s ORDER BY %s ts`, groupTagsWithoutLe, value, query, groupByWithoutLe, groupTagsWithoutLe) diff --git a/pkg/query-service/app/metrics/query_builder_test.go b/pkg/query-service/app/metrics/query_builder_test.go index 92bc60c5b0..c749224689 100644 --- a/pkg/query-service/app/metrics/query_builder_test.go +++ b/pkg/query-service/app/metrics/query_builder_test.go @@ -28,7 +28,30 @@ func TestBuildQuery(t *testing.T) { queries := PrepareBuilderMetricQueries(q, "table").Queries So(len(queries), ShouldEqual, 1) So(queries["A"], ShouldContainSubstring, "WHERE metric_name = 'name'") - So(queries["A"], ShouldContainSubstring, "runningDifference(value)/runningDifference(ts)") + So(queries["A"], ShouldContainSubstring, rateWithoutNegative) + }) + + Convey("TestSimpleQueryWithHistQuantile", t, func() { + q := &model.QueryRangeParamsV2{ + Start: 1650991982000, + End: 1651078382000, + Step: 60, + CompositeMetricQuery: &model.CompositeMetricQuery{ + BuilderQueries: map[string]*model.MetricQuery{ + "A": { + QueryName: "A", + MetricName: "name", + AggregateOperator: model.HIST_QUANTILE_99, + Expression: "A", + }, + }, + }, + } + queries := PrepareBuilderMetricQueries(q, "table").Queries + So(len(queries), ShouldEqual, 1) + So(queries["A"], ShouldContainSubstring, "WHERE metric_name = 'name'") + So(queries["A"], ShouldContainSubstring, rateWithoutNegative) + So(queries["A"], ShouldContainSubstring, "HAVING isNaN(value) = 0") }) } @@ -57,7 +80,7 @@ func TestBuildQueryWithFilters(t *testing.T) { So(len(queries), ShouldEqual, 1) So(queries["A"], ShouldContainSubstring, "WHERE metric_name = 'name' AND JSONExtractString(labels, 'a') != 'b'") - So(queries["A"], ShouldContainSubstring, "runningDifference(value)/runningDifference(ts)") + So(queries["A"], ShouldContainSubstring, rateWithoutNegative) So(queries["A"], ShouldContainSubstring, "not match(JSONExtractString(labels, 'code'), 'ERROR_*')") }) } @@ -91,7 +114,7 @@ func TestBuildQueryWithMultipleQueries(t *testing.T) { queries := PrepareBuilderMetricQueries(q, "table").Queries So(len(queries), ShouldEqual, 2) So(queries["A"], ShouldContainSubstring, "WHERE metric_name = 'name' AND JSONExtractString(labels, 'in') IN ['a','b','c']") - So(queries["A"], ShouldContainSubstring, "runningDifference(value)/runningDifference(ts)") + So(queries["A"], ShouldContainSubstring, rateWithoutNegative) }) } @@ -128,7 +151,7 @@ func TestBuildQueryWithMultipleQueriesAndFormula(t *testing.T) { So(len(queries), ShouldEqual, 3) So(queries["C"], ShouldContainSubstring, "SELECT A.ts as ts, A.value / B.value") So(queries["C"], ShouldContainSubstring, "WHERE metric_name = 'name' AND JSONExtractString(labels, 'in') IN ['a','b','c']") - So(queries["C"], ShouldContainSubstring, "runningDifference(value)/runningDifference(ts)") + So(queries["C"], ShouldContainSubstring, rateWithoutNegative) }) } diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index e097fdd6f1..1221e46a1b 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -229,7 +229,6 @@ type FilterAttributeKeyResponse struct { type AttributeKeyType string const ( - AttributeKeyTypeColumn AttributeKeyType = "column" AttributeKeyTypeTag AttributeKeyType = "tag" AttributeKeyTypeResource AttributeKeyType = "resource" ) @@ -238,6 +237,29 @@ type AttributeKey struct { Key string `json:"key"` DataType AttributeKeyDataType `json:"dataType"` Type AttributeKeyType `json:"type"` + IsColumn bool `json:"isColumn"` +} + +func (a AttributeKey) Validate() error { + switch a.DataType { + case AttributeKeyDataTypeBool, AttributeKeyDataTypeNumber, AttributeKeyDataTypeString: + break + default: + return fmt.Errorf("invalid attribute dataType: %s", a.DataType) + } + + switch a.Type { + case AttributeKeyTypeResource, AttributeKeyTypeTag: + break + default: + return fmt.Errorf("invalid attribute type: %s", a.Type) + } + + if a.Key == "" { + return fmt.Errorf("key is empty") + } + + return nil } type FilterAttributeValueResponse struct { @@ -345,9 +367,9 @@ type BuilderQuery struct { QueryName string `json:"queryName"` DataSource DataSource `json:"dataSource"` AggregateOperator AggregateOperator `json:"aggregateOperator"` - AggregateAttribute string `json:"aggregateAttribute,omitempty"` + AggregateAttribute AttributeKey `json:"aggregateAttribute,omitempty"` Filters *FilterSet `json:"filters,omitempty"` - GroupBy []string `json:"groupBy,omitempty"` + GroupBy []AttributeKey `json:"groupBy,omitempty"` Expression string `json:"expression"` Disabled bool `json:"disabled"` Having []Having `json:"having,omitempty"` @@ -356,7 +378,7 @@ type BuilderQuery struct { PageSize uint64 `json:"pageSize"` OrderBy []OrderBy `json:"orderBy,omitempty"` ReduceTo ReduceToOperator `json:"reduceTo,omitempty"` - SelectColumns []string `json:"selectColumns,omitempty"` + SelectColumns []AttributeKey `json:"selectColumns,omitempty"` } func (b *BuilderQuery) Validate() error { @@ -376,7 +398,7 @@ func (b *BuilderQuery) Validate() error { if err := b.AggregateOperator.Validate(); err != nil { return fmt.Errorf("aggregate operator is invalid: %w", err) } - if b.AggregateAttribute == "" && b.AggregateOperator.RequireAttribute() { + if b.AggregateAttribute == (AttributeKey{}) && b.AggregateOperator.RequireAttribute() { return fmt.Errorf("aggregate attribute is required") } } @@ -388,11 +410,20 @@ func (b *BuilderQuery) Validate() error { } if b.GroupBy != nil { for _, groupBy := range b.GroupBy { - if groupBy == "" { - return fmt.Errorf("group by cannot be empty") + if groupBy.Validate() != nil { + return fmt.Errorf("group by is invalid") } } } + + if b.SelectColumns != nil { + for _, selectColumn := range b.SelectColumns { + if selectColumn.Validate() != nil { + return fmt.Errorf("select column is invalid") + } + } + } + if b.Expression == "" { return fmt.Errorf("expression is required") } @@ -411,13 +442,18 @@ func (f *FilterSet) Validate() error { if f.Operator != "" && f.Operator != "AND" && f.Operator != "OR" { return fmt.Errorf("operator must be AND or OR") } + for _, item := range f.Items { + if err := item.Key.Validate(); err != nil { + return fmt.Errorf("filter item key is invalid: %w", err) + } + } return nil } type FilterItem struct { - Key string `json:"key"` - Value interface{} `json:"value"` - Operator string `json:"op"` + Key AttributeKey `json:"key"` + Value interface{} `json:"value"` + Operator string `json:"op"` } type OrderBy struct {