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 }