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
-
-
-
-
-
-
-
+👉 Record exceptions automatically in Python, Java, Ruby, and Javascript
+👉 Easy to set alerts with DIY query builder
+
+
+### Application Metrics
+
+
+
+### Distributed Tracing
+
+
+
+
+### Logs Management
+
+
+
+### Infrastructure Monitoring
+
+
+
+### Alerts
+
+
@@ -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 {