github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/logql/log/pipeline_test.go (about)

     1  package log
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/prometheus/prometheus/model/labels"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/grafana/loki/pkg/logqlmodel"
    11  )
    12  
    13  func TestNoopPipeline(t *testing.T) {
    14  	lbs := labels.Labels{{Name: "foo", Value: "bar"}}
    15  	l, lbr, matches := NewNoopPipeline().ForStream(lbs).Process(0, []byte(""))
    16  	require.Equal(t, []byte(""), l)
    17  	require.Equal(t, NewLabelsResult(lbs, lbs.Hash()), lbr)
    18  	require.Equal(t, true, matches)
    19  
    20  	ls, lbr, matches := NewNoopPipeline().ForStream(lbs).ProcessString(0, "")
    21  	require.Equal(t, "", ls)
    22  	require.Equal(t, NewLabelsResult(lbs, lbs.Hash()), lbr)
    23  	require.Equal(t, true, matches)
    24  }
    25  
    26  func TestPipeline(t *testing.T) {
    27  	lbs := labels.Labels{{Name: "foo", Value: "bar"}}
    28  	p := NewPipeline([]Stage{
    29  		NewStringLabelFilter(labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")),
    30  		newMustLineFormatter("lbs {{.foo}}"),
    31  	})
    32  	l, lbr, matches := p.ForStream(lbs).Process(0, []byte("line"))
    33  	require.Equal(t, []byte("lbs bar"), l)
    34  	require.Equal(t, NewLabelsResult(lbs, lbs.Hash()), lbr)
    35  	require.Equal(t, true, matches)
    36  
    37  	ls, lbr, matches := p.ForStream(lbs).ProcessString(0, "line")
    38  	require.Equal(t, "lbs bar", ls)
    39  	require.Equal(t, NewLabelsResult(lbs, lbs.Hash()), lbr)
    40  	require.Equal(t, true, matches)
    41  
    42  	l, lbr, matches = p.ForStream(labels.Labels{}).Process(0, []byte("line"))
    43  	require.Equal(t, []byte(nil), l)
    44  	require.Equal(t, nil, lbr)
    45  	require.Equal(t, false, matches)
    46  
    47  	ls, lbr, matches = p.ForStream(labels.Labels{}).ProcessString(0, "line")
    48  	require.Equal(t, "", ls)
    49  	require.Equal(t, nil, lbr)
    50  	require.Equal(t, false, matches)
    51  }
    52  
    53  func TestFilteringPipeline(t *testing.T) {
    54  	p := NewFilteringPipeline([]PipelineFilter{
    55  		newPipelineFilter(2, 4, labels.Labels{{Name: "foo", Value: "bar"}, {Name: "bar", Value: "baz"}}, "e"),
    56  		newPipelineFilter(3, 5, labels.Labels{{Name: "baz", Value: "foo"}}, "e"),
    57  	}, newStubPipeline())
    58  
    59  	tt := []struct {
    60  		name              string
    61  		ts                int64
    62  		line              string
    63  		inputStreamLabels labels.Labels
    64  		ok                bool
    65  	}{
    66  		{"it is before the timerange", 1, "line", labels.Labels{{Name: "baz", Value: "foo"}}, true},
    67  		{"it is after the timerange", 6, "line", labels.Labels{{Name: "baz", Value: "foo"}}, true},
    68  		{"it doesn't match the filter", 3, "all good", labels.Labels{{Name: "baz", Value: "foo"}}, true},
    69  		{"it doesn't match all the selectors", 3, "line", labels.Labels{{Name: "foo", Value: "bar"}}, true},
    70  		{"it doesn't match any selectors", 3, "line", labels.Labels{{Name: "beep", Value: "boop"}}, true},
    71  		{"it matches all selectors", 3, "line", labels.Labels{{Name: "foo", Value: "bar"}, {Name: "bar", Value: "baz"}}, false},
    72  		{"it tries all the filters", 5, "line", labels.Labels{{Name: "baz", Value: "foo"}}, false},
    73  	}
    74  
    75  	for _, test := range tt {
    76  		t.Run(test.name, func(t *testing.T) {
    77  			_, _, matches := p.ForStream(test.inputStreamLabels).Process(test.ts, []byte(test.line))
    78  			require.Equal(t, test.ok, matches)
    79  
    80  			_, _, matches = p.ForStream(test.inputStreamLabels).ProcessString(test.ts, test.line)
    81  			require.Equal(t, test.ok, matches)
    82  		})
    83  	}
    84  }
    85  
    86  //nolint:unparam
    87  func newPipelineFilter(start, end int64, lbls labels.Labels, filter string) PipelineFilter {
    88  	var stages []Stage
    89  	var matchers []*labels.Matcher
    90  	for _, l := range lbls {
    91  		m := labels.MustNewMatcher(labels.MatchEqual, l.Name, l.Value)
    92  		matchers = append(matchers, m)
    93  	}
    94  	stages = append(stages, mustFilter(NewFilter(filter, labels.MatchEqual)).ToStage())
    95  
    96  	return PipelineFilter{start, end, matchers, NewPipeline(stages)}
    97  }
    98  
    99  func newStubPipeline() *stubPipeline {
   100  	return &stubPipeline{
   101  		sp: &stubStreamPipeline{},
   102  	}
   103  }
   104  
   105  // A stub always returns the same data
   106  type stubPipeline struct {
   107  	sp *stubStreamPipeline
   108  }
   109  
   110  func (p *stubPipeline) ForStream(labels labels.Labels) StreamPipeline {
   111  	return p.sp
   112  }
   113  
   114  // A stub always returns the same data
   115  type stubStreamPipeline struct{}
   116  
   117  func (p *stubStreamPipeline) BaseLabels() LabelsResult {
   118  	return nil
   119  }
   120  
   121  func (p *stubStreamPipeline) Process(ts int64, line []byte) ([]byte, LabelsResult, bool) {
   122  	return nil, nil, true
   123  }
   124  
   125  func (p *stubStreamPipeline) ProcessString(ts int64, line string) (string, LabelsResult, bool) {
   126  	return "", nil, true
   127  }
   128  
   129  var (
   130  	resMatches    bool
   131  	resLine       []byte
   132  	resLineString string
   133  	resLbs        LabelsResult
   134  	resSample     float64
   135  )
   136  
   137  func Benchmark_Pipeline(b *testing.B) {
   138  	b.ReportAllocs()
   139  
   140  	stages := []Stage{
   141  		mustFilter(NewFilter("metrics.go", labels.MatchEqual)).ToStage(),
   142  		NewLogfmtParser(),
   143  		NewAndLabelFilter(
   144  			NewDurationLabelFilter(LabelFilterGreaterThan, "duration", 10*time.Millisecond),
   145  			NewNumericLabelFilter(LabelFilterEqual, "status", 200.0),
   146  		),
   147  		mustNewLabelsFormatter([]LabelFmt{NewRenameLabelFmt("caller_foo", "caller"), NewTemplateLabelFmt("new", "{{.query_type}}:{{.range_type}}")}),
   148  		NewJSONParser(),
   149  		NewStringLabelFilter(labels.MustNewMatcher(labels.MatchEqual, logqlmodel.ErrorLabel, errJSON)),
   150  		newMustLineFormatter("Q=>{{.query}},D=>{{.duration}}"),
   151  	}
   152  	p := NewPipeline(stages)
   153  	line := []byte(`level=info ts=2020-10-18T18:04:22.147378997Z caller=metrics.go:81 org_id=29 traceID=29a0f088b047eb8c latency=fast query="{stream=\"stdout\",pod=\"loki-canary-xmjzp\"}" query_type=limited range_type=range length=20s step=1s duration=58.126671ms status=200 throughput_mb=2.496547 total_bytes_mb=0.145116`)
   154  	lineString := string(line)
   155  	lbs := labels.Labels{
   156  		{Name: "cluster", Value: "ops-tool1"},
   157  		{Name: "name", Value: "querier"},
   158  		{Name: "pod", Value: "querier-5896759c79-q7q9h"},
   159  		{Name: "stream", Value: "stderr"},
   160  		{Name: "container", Value: "querier"},
   161  		{Name: "namespace", Value: "loki-dev"},
   162  		{Name: "job", Value: "loki-dev/querier"},
   163  		{Name: "pod_template_hash", Value: "5896759c79"},
   164  	}
   165  
   166  	sp := p.ForStream(lbs)
   167  
   168  	b.Run("pipeline bytes", func(b *testing.B) {
   169  		b.ResetTimer()
   170  		for n := 0; n < b.N; n++ {
   171  			resLine, resLbs, resMatches = sp.Process(0, line)
   172  		}
   173  	})
   174  	b.Run("pipeline string", func(b *testing.B) {
   175  		b.ResetTimer()
   176  		for n := 0; n < b.N; n++ {
   177  			resLineString, resLbs, resMatches = sp.ProcessString(0, lineString)
   178  		}
   179  	})
   180  
   181  	extractor, err := NewLineSampleExtractor(CountExtractor, stages, []string{"cluster", "level"}, false, false)
   182  	require.NoError(b, err)
   183  	ex := extractor.ForStream(lbs)
   184  	b.Run("line extractor bytes", func(b *testing.B) {
   185  		b.ResetTimer()
   186  		for n := 0; n < b.N; n++ {
   187  			resSample, resLbs, resMatches = ex.Process(0, line)
   188  		}
   189  	})
   190  	b.Run("line extractor string", func(b *testing.B) {
   191  		b.ResetTimer()
   192  		for n := 0; n < b.N; n++ {
   193  			resSample, resLbs, resMatches = ex.ProcessString(0, lineString)
   194  		}
   195  	})
   196  
   197  	extractor, err = LabelExtractorWithStages("duration", "duration", []string{"cluster", "level"}, false, false, stages, NoopStage)
   198  	require.NoError(b, err)
   199  	ex = extractor.ForStream(lbs)
   200  
   201  	b.Run("label extractor bytes", func(b *testing.B) {
   202  		b.ResetTimer()
   203  		for n := 0; n < b.N; n++ {
   204  			resSample, resLbs, resMatches = ex.Process(0, line)
   205  		}
   206  	})
   207  	b.Run("label extractor string", func(b *testing.B) {
   208  		b.ResetTimer()
   209  		for n := 0; n < b.N; n++ {
   210  			resSample, resLbs, resMatches = ex.ProcessString(0, lineString)
   211  		}
   212  	})
   213  }
   214  
   215  func mustFilter(f Filterer, err error) Filterer {
   216  	if err != nil {
   217  		panic(err)
   218  	}
   219  	return f
   220  }
   221  
   222  func jsonBenchmark(b *testing.B, parser Stage) {
   223  	b.ReportAllocs()
   224  
   225  	p := NewPipeline([]Stage{
   226  		mustFilter(NewFilter("metrics.go", labels.MatchEqual)).ToStage(),
   227  		parser,
   228  	})
   229  	line := []byte(`{"ts":"2020-12-27T09:15:54.333026285Z","error":"action could not be completed", "context":{"file": "metrics.go"}}`)
   230  	lbs := labels.Labels{
   231  		{Name: "cluster", Value: "ops-tool1"},
   232  		{Name: "name", Value: "querier"},
   233  		{Name: "pod", Value: "querier-5896759c79-q7q9h"},
   234  		{Name: "stream", Value: "stderr"},
   235  		{Name: "container", Value: "querier"},
   236  		{Name: "namespace", Value: "loki-dev"},
   237  		{Name: "job", Value: "loki-dev/querier"},
   238  		{Name: "pod_template_hash", Value: "5896759c79"},
   239  	}
   240  	b.ResetTimer()
   241  	sp := p.ForStream(lbs)
   242  	for n := 0; n < b.N; n++ {
   243  		resLine, resLbs, resMatches = sp.Process(0, line)
   244  
   245  		if !resMatches {
   246  			b.Fatalf("resulting line not ok: %s\n", line)
   247  		}
   248  
   249  		if resLbs.Labels().Get("context_file") != "metrics.go" {
   250  			b.Fatalf("label was not extracted correctly! %+v\n", resLbs)
   251  		}
   252  	}
   253  }
   254  
   255  func invalidJSONBenchmark(b *testing.B, parser Stage) {
   256  	b.ReportAllocs()
   257  
   258  	p := NewPipeline([]Stage{
   259  		mustFilter(NewFilter("invalid json", labels.MatchEqual)).ToStage(),
   260  		parser,
   261  	})
   262  	line := []byte(`invalid json`)
   263  	b.ResetTimer()
   264  	sp := p.ForStream(labels.Labels{})
   265  	for n := 0; n < b.N; n++ {
   266  		resLine, resLbs, resMatches = sp.Process(0, line)
   267  
   268  		if !resMatches {
   269  			b.Fatalf("resulting line not ok: %s\n", line)
   270  		}
   271  
   272  		if resLbs.Labels().Get(logqlmodel.ErrorLabel) != errJSON {
   273  			b.Fatalf("no %s label found: %+v\n", logqlmodel.ErrorLabel, resLbs.Labels())
   274  		}
   275  	}
   276  }
   277  
   278  func BenchmarkJSONParser(b *testing.B) {
   279  	jsonBenchmark(b, NewJSONParser())
   280  }
   281  
   282  func BenchmarkJSONParserInvalidLine(b *testing.B) {
   283  	invalidJSONBenchmark(b, NewJSONParser())
   284  }
   285  
   286  func BenchmarkJSONExpressionParser(b *testing.B) {
   287  	parser, err := NewJSONExpressionParser([]JSONExpression{
   288  		NewJSONExpr("context_file", "context.file"),
   289  	})
   290  	if err != nil {
   291  		b.Fatal("cannot create new JSON expression parser")
   292  	}
   293  
   294  	jsonBenchmark(b, parser)
   295  }
   296  
   297  func BenchmarkJSONExpressionParserInvalidLine(b *testing.B) {
   298  	parser, err := NewJSONExpressionParser([]JSONExpression{
   299  		NewJSONExpr("context_file", "some.expression"),
   300  	})
   301  	if err != nil {
   302  		b.Fatal("cannot create new JSON expression parser")
   303  	}
   304  
   305  	invalidJSONBenchmark(b, parser)
   306  }