Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/logql/optimize.go

108 lines
2.8 KiB

package logql
// optimizeSampleExpr Attempt to optimize the SampleExpr to another that will run faster but will produce the same result.
func optimizeSampleExpr(expr SampleExpr) (SampleExpr, error) {
var skip bool
// we skip sharding AST for now, it's not easy to clone them since they are not part of the language.
walkSampleExpr(expr, func(e SampleExpr) {
switch e.(type) {
case *ConcatSampleExpr, *DownstreamSampleExpr:
skip = true
}
})
if skip {
return expr, nil
}
// clone the expr.
q := expr.String()
expr, err := ParseSampleExpr(q)
if err != nil {
return nil, err
}
removeLineformat(expr)
return expr, nil
}
// removeLineformat removes unnecessary line_format within a SampleExpr.
func removeLineformat(expr SampleExpr) {
walkSampleExpr(expr, func(e SampleExpr) {
rangeExpr, ok := e.(*rangeAggregationExpr)
if !ok {
return
}
// bytes operation count bytes of the log line so line_format changes the result.
if rangeExpr.operation == OpRangeTypeBytes ||
rangeExpr.operation == OpRangeTypeBytesRate {
return
}
pipelineExpr, ok := rangeExpr.left.left.(*pipelineExpr)
if !ok {
return
}
temp := pipelineExpr.pipeline[:0]
for i, s := range pipelineExpr.pipeline {
_, ok := s.(*lineFmtExpr)
if !ok {
temp = append(temp, s)
continue
}
// we found a lineFmtExpr, we need to check if it's followed by a labelParser or lineFilter
// in which case it could be useful for further processing.
var found bool
for j := i; j < len(pipelineExpr.pipeline); j++ {
if _, ok := pipelineExpr.pipeline[j].(*labelParserExpr); ok {
found = true
break
}
if _, ok := pipelineExpr.pipeline[j].(*lineFilterExpr); ok {
found = true
break
}
}
if found {
// we cannot remove safely the linefmtExpr.
temp = append(temp, s)
}
}
pipelineExpr.pipeline = temp
// transform into a matcherExpr if there's no more pipeline.
if len(pipelineExpr.pipeline) == 0 {
rangeExpr.left.left = &matchersExpr{
matchers: rangeExpr.left.left.Matchers(),
}
}
})
}
// walkSampleExpr traverses in depth-first order a SampleExpr.
func walkSampleExpr(expr SampleExpr, visitor func(e SampleExpr)) {
visitor(expr)
for _, c := range children(expr) {
walkSampleExpr(c, visitor)
}
}
// children returns children node of a SampleExpr.
func children(expr SampleExpr) []SampleExpr {
switch e := expr.(type) {
case *rangeAggregationExpr:
return []SampleExpr{}
case *vectorAggregationExpr:
return []SampleExpr{e.left}
case *binOpExpr:
return []SampleExpr{e.SampleExpr, e.RHS}
case *literalExpr:
return []SampleExpr{}
case *labelReplaceExpr:
return []SampleExpr{e.left}
case *DownstreamSampleExpr:
return []SampleExpr{e.SampleExpr}
case *ConcatSampleExpr:
if e.next != nil {
return []SampleExpr{e.next}
}
return []SampleExpr{}
default:
panic("unknown sample expression")
}
}