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

     1  package log
     2  
     3  import (
     4  	"sort"
     5  	"strconv"
     6  	"time"
     7  
     8  	"github.com/pkg/errors"
     9  	"github.com/prometheus/prometheus/model/labels"
    10  
    11  	"github.com/dustin/go-humanize"
    12  )
    13  
    14  const (
    15  	ConvertBytes    = "bytes"
    16  	ConvertDuration = "duration"
    17  	ConvertFloat    = "float"
    18  )
    19  
    20  // LineExtractor extracts a float64 from a log line.
    21  type LineExtractor func([]byte) float64
    22  
    23  var (
    24  	CountExtractor LineExtractor = func(line []byte) float64 { return 1. }
    25  	BytesExtractor LineExtractor = func(line []byte) float64 { return float64(len(line)) }
    26  )
    27  
    28  // SampleExtractor creates StreamSampleExtractor that can extract samples for a given log stream.
    29  type SampleExtractor interface {
    30  	ForStream(labels labels.Labels) StreamSampleExtractor
    31  }
    32  
    33  // StreamSampleExtractor extracts sample for a log line.
    34  // A StreamSampleExtractor never mutate the received line.
    35  type StreamSampleExtractor interface {
    36  	BaseLabels() LabelsResult
    37  	Process(ts int64, line []byte) (float64, LabelsResult, bool)
    38  	ProcessString(ts int64, line string) (float64, LabelsResult, bool)
    39  }
    40  
    41  type lineSampleExtractor struct {
    42  	Stage
    43  	LineExtractor
    44  
    45  	baseBuilder      *BaseLabelsBuilder
    46  	streamExtractors map[uint64]StreamSampleExtractor
    47  }
    48  
    49  // NewLineSampleExtractor creates a SampleExtractor from a LineExtractor.
    50  // Multiple log stages are run before converting the log line.
    51  func NewLineSampleExtractor(ex LineExtractor, stages []Stage, groups []string, without, noLabels bool) (SampleExtractor, error) {
    52  	s := ReduceStages(stages)
    53  	hints := newParserHint(s.RequiredLabelNames(), groups, without, noLabels, "")
    54  	return &lineSampleExtractor{
    55  		Stage:            s,
    56  		LineExtractor:    ex,
    57  		baseBuilder:      NewBaseLabelsBuilderWithGrouping(groups, hints, without, noLabels),
    58  		streamExtractors: make(map[uint64]StreamSampleExtractor),
    59  	}, nil
    60  }
    61  
    62  func (l *lineSampleExtractor) ForStream(labels labels.Labels) StreamSampleExtractor {
    63  	hash := l.baseBuilder.Hash(labels)
    64  	if res, ok := l.streamExtractors[hash]; ok {
    65  		return res
    66  	}
    67  
    68  	res := &streamLineSampleExtractor{
    69  		Stage:         l.Stage,
    70  		LineExtractor: l.LineExtractor,
    71  		builder:       l.baseBuilder.ForLabels(labels, hash),
    72  	}
    73  	l.streamExtractors[hash] = res
    74  	return res
    75  }
    76  
    77  type streamLineSampleExtractor struct {
    78  	Stage
    79  	LineExtractor
    80  	builder *LabelsBuilder
    81  }
    82  
    83  func (l *streamLineSampleExtractor) Process(ts int64, line []byte) (float64, LabelsResult, bool) {
    84  	// short circuit.
    85  	if l.Stage == NoopStage {
    86  		return l.LineExtractor(line), l.builder.GroupedLabels(), true
    87  	}
    88  	l.builder.Reset()
    89  	line, ok := l.Stage.Process(ts, line, l.builder)
    90  	if !ok {
    91  		return 0, nil, false
    92  	}
    93  	return l.LineExtractor(line), l.builder.GroupedLabels(), true
    94  }
    95  
    96  func (l *streamLineSampleExtractor) ProcessString(ts int64, line string) (float64, LabelsResult, bool) {
    97  	// unsafe get bytes since we have the guarantee that the line won't be mutated.
    98  	return l.Process(ts, unsafeGetBytes(line))
    99  }
   100  
   101  func (l *streamLineSampleExtractor) BaseLabels() LabelsResult { return l.builder.currentResult }
   102  
   103  type convertionFn func(value string) (float64, error)
   104  
   105  type labelSampleExtractor struct {
   106  	preStage     Stage
   107  	postFilter   Stage
   108  	labelName    string
   109  	conversionFn convertionFn
   110  
   111  	baseBuilder      *BaseLabelsBuilder
   112  	streamExtractors map[uint64]StreamSampleExtractor
   113  }
   114  
   115  // LabelExtractorWithStages creates a SampleExtractor that will extract metrics from a labels.
   116  // A set of log stage is executed before the conversion. A Filtering stage is executed after the conversion allowing
   117  // to remove sample containing the __error__ label.
   118  func LabelExtractorWithStages(
   119  	labelName, conversion string,
   120  	groups []string, without, noLabels bool,
   121  	preStages []Stage,
   122  	postFilter Stage,
   123  ) (SampleExtractor, error) {
   124  	var convFn convertionFn
   125  	switch conversion {
   126  	case ConvertBytes:
   127  		convFn = convertBytes
   128  	case ConvertDuration:
   129  		convFn = convertDuration
   130  	case ConvertFloat:
   131  		convFn = convertFloat
   132  	default:
   133  		return nil, errors.Errorf("unsupported conversion operation %s", conversion)
   134  	}
   135  	if len(groups) == 0 || without {
   136  		without = true
   137  		groups = append(groups, labelName)
   138  		sort.Strings(groups)
   139  	}
   140  	preStage := ReduceStages(preStages)
   141  	hints := newParserHint(append(preStage.RequiredLabelNames(), postFilter.RequiredLabelNames()...), groups, without, noLabels, labelName)
   142  	return &labelSampleExtractor{
   143  		preStage:         preStage,
   144  		conversionFn:     convFn,
   145  		labelName:        labelName,
   146  		postFilter:       postFilter,
   147  		baseBuilder:      NewBaseLabelsBuilderWithGrouping(groups, hints, without, noLabels),
   148  		streamExtractors: make(map[uint64]StreamSampleExtractor),
   149  	}, nil
   150  }
   151  
   152  type streamLabelSampleExtractor struct {
   153  	*labelSampleExtractor
   154  	builder *LabelsBuilder
   155  }
   156  
   157  func (l *labelSampleExtractor) ForStream(labels labels.Labels) StreamSampleExtractor {
   158  	hash := l.baseBuilder.Hash(labels)
   159  	if res, ok := l.streamExtractors[hash]; ok {
   160  		return res
   161  	}
   162  
   163  	res := &streamLabelSampleExtractor{
   164  		labelSampleExtractor: l,
   165  		builder:              l.baseBuilder.ForLabels(labels, hash),
   166  	}
   167  	l.streamExtractors[hash] = res
   168  	return res
   169  }
   170  
   171  func (l *streamLabelSampleExtractor) Process(ts int64, line []byte) (float64, LabelsResult, bool) {
   172  	// Apply the pipeline first.
   173  	l.builder.Reset()
   174  	line, ok := l.preStage.Process(ts, line, l.builder)
   175  	if !ok {
   176  		return 0, nil, false
   177  	}
   178  	// convert the label value.
   179  	var v float64
   180  	stringValue, _ := l.builder.Get(l.labelName)
   181  	if stringValue == "" {
   182  		// NOTE: It's totally fine for log line to not have this particular label.
   183  		// See Issue: https://github.com/grafana/loki/issues/6713
   184  		return 0, nil, false
   185  	}
   186  
   187  	var err error
   188  	v, err = l.conversionFn(stringValue)
   189  	if err != nil {
   190  		l.builder.SetErr(errSampleExtraction)
   191  		l.builder.SetErrorDetails(err.Error())
   192  	}
   193  
   194  	// post filters
   195  	if _, ok = l.postFilter.Process(ts, line, l.builder); !ok {
   196  		return 0, nil, false
   197  	}
   198  	return v, l.builder.GroupedLabels(), true
   199  }
   200  
   201  func (l *streamLabelSampleExtractor) ProcessString(ts int64, line string) (float64, LabelsResult, bool) {
   202  	// unsafe get bytes since we have the guarantee that the line won't be mutated.
   203  	return l.Process(ts, unsafeGetBytes(line))
   204  }
   205  
   206  func (l *streamLabelSampleExtractor) BaseLabels() LabelsResult { return l.builder.currentResult }
   207  
   208  // NewFilteringSampleExtractor creates a sample extractor where entries from
   209  // the underlying log stream are filtered by pipeline filters before being
   210  // passed to extract samples. Filters are always upstream of the extractor.
   211  func NewFilteringSampleExtractor(f []PipelineFilter, e SampleExtractor) SampleExtractor {
   212  	return &filteringSampleExtractor{
   213  		filters:   f,
   214  		extractor: e,
   215  	}
   216  }
   217  
   218  type filteringSampleExtractor struct {
   219  	filters   []PipelineFilter
   220  	extractor SampleExtractor
   221  }
   222  
   223  func (p *filteringSampleExtractor) ForStream(labels labels.Labels) StreamSampleExtractor {
   224  	var streamFilters []streamFilter
   225  	for _, f := range p.filters {
   226  		if allMatch(f.Matchers, labels) {
   227  			streamFilters = append(streamFilters, streamFilter{
   228  				start:    f.Start,
   229  				end:      f.End,
   230  				pipeline: f.Pipeline.ForStream(labels),
   231  			})
   232  		}
   233  	}
   234  
   235  	return &filteringStreamExtractor{
   236  		filters:   streamFilters,
   237  		extractor: p.extractor.ForStream(labels),
   238  	}
   239  }
   240  
   241  type filteringStreamExtractor struct {
   242  	filters   []streamFilter
   243  	extractor StreamSampleExtractor
   244  }
   245  
   246  func (sp *filteringStreamExtractor) BaseLabels() LabelsResult {
   247  	return sp.extractor.BaseLabels()
   248  }
   249  
   250  func (sp *filteringStreamExtractor) Process(ts int64, line []byte) (float64, LabelsResult, bool) {
   251  	for _, filter := range sp.filters {
   252  		if ts < filter.start || ts > filter.end {
   253  			continue
   254  		}
   255  
   256  		_, _, matches := filter.pipeline.Process(ts, line)
   257  		if matches { //When the filter matches, don't run the next step
   258  			return 0, nil, false
   259  		}
   260  	}
   261  
   262  	return sp.extractor.Process(ts, line)
   263  }
   264  
   265  func (sp *filteringStreamExtractor) ProcessString(ts int64, line string) (float64, LabelsResult, bool) {
   266  	for _, filter := range sp.filters {
   267  		if ts < filter.start || ts > filter.end {
   268  			continue
   269  		}
   270  
   271  		_, _, matches := filter.pipeline.ProcessString(ts, line)
   272  		if matches { //When the filter matches, don't run the next step
   273  			return 0, nil, false
   274  		}
   275  	}
   276  
   277  	return sp.extractor.ProcessString(ts, line)
   278  }
   279  
   280  func convertFloat(v string) (float64, error) {
   281  	return strconv.ParseFloat(v, 64)
   282  }
   283  
   284  func convertDuration(v string) (float64, error) {
   285  	d, err := time.ParseDuration(v)
   286  	if err != nil {
   287  		return 0, err
   288  	}
   289  	return d.Seconds(), nil
   290  }
   291  
   292  func convertBytes(v string) (float64, error) {
   293  	b, err := humanize.ParseBytes(v)
   294  	if err != nil {
   295  		return 0, err
   296  	}
   297  	return float64(b), nil
   298  }