github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/logproto/compat.go (about)

     1  package logproto
     2  
     3  import (
     4  	stdjson "encoding/json"
     5  	"fmt"
     6  	"math"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  	"unsafe"
    12  
    13  	jsoniter "github.com/json-iterator/go"
    14  	"github.com/opentracing/opentracing-go"
    15  	otlog "github.com/opentracing/opentracing-go/log"
    16  	"github.com/prometheus/common/model"
    17  	"github.com/prometheus/prometheus/model/labels"
    18  	"github.com/prometheus/prometheus/model/timestamp"
    19  
    20  	"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase/definitions"
    21  	"github.com/grafana/loki/pkg/util"
    22  )
    23  
    24  // ToWriteRequest converts matched slices of Labels, Samples and Metadata into a WriteRequest proto.
    25  // It gets timeseries from the pool, so ReuseSlice() should be called when done.
    26  func ToWriteRequest(lbls []labels.Labels, samples []LegacySample, metadata []*MetricMetadata, source WriteRequest_SourceEnum) *WriteRequest {
    27  	req := &WriteRequest{
    28  		Timeseries: PreallocTimeseriesSliceFromPool(),
    29  		Metadata:   metadata,
    30  		Source:     source,
    31  	}
    32  
    33  	for i, s := range samples {
    34  		ts := TimeseriesFromPool()
    35  		ts.Labels = append(ts.Labels, FromLabelsToLabelAdapters(lbls[i])...)
    36  		ts.Samples = append(ts.Samples, s)
    37  		req.Timeseries = append(req.Timeseries, PreallocTimeseries{TimeSeries: ts})
    38  	}
    39  
    40  	return req
    41  }
    42  
    43  // FromLabelAdaptersToLabels casts []LabelAdapter to labels.Labels.
    44  // It uses unsafe, but as LabelAdapter == labels.Label this should be safe.
    45  // This allows us to use labels.Labels directly in protos.
    46  //
    47  // Note: while resulting labels.Labels is supposedly sorted, this function
    48  // doesn't enforce that. If input is not sorted, output will be wrong.
    49  func FromLabelAdaptersToLabels(ls []LabelAdapter) labels.Labels {
    50  	return *(*labels.Labels)(unsafe.Pointer(&ls))
    51  }
    52  
    53  // FromLabelAdaptersToLabelsWithCopy converts []LabelAdapter to labels.Labels.
    54  // Do NOT use unsafe to convert between data types because this function may
    55  // get in input labels whose data structure is reused.
    56  func FromLabelAdaptersToLabelsWithCopy(input []LabelAdapter) labels.Labels {
    57  	return CopyLabels(FromLabelAdaptersToLabels(input))
    58  }
    59  
    60  // Efficiently copies labels input slice. To be used in cases where input slice
    61  // can be reused, but long-term copy is needed.
    62  func CopyLabels(input []labels.Label) labels.Labels {
    63  	result := make(labels.Labels, len(input))
    64  
    65  	size := 0
    66  	for _, l := range input {
    67  		size += len(l.Name)
    68  		size += len(l.Value)
    69  	}
    70  
    71  	// Copy all strings into the buffer, and use 'yoloString' to convert buffer
    72  	// slices to strings.
    73  	buf := make([]byte, size)
    74  
    75  	for i, l := range input {
    76  		result[i].Name, buf = copyStringToBuffer(l.Name, buf)
    77  		result[i].Value, buf = copyStringToBuffer(l.Value, buf)
    78  	}
    79  	return result
    80  }
    81  
    82  // Copies string to buffer (which must be big enough), and converts buffer slice containing
    83  // the string copy into new string.
    84  func copyStringToBuffer(in string, buf []byte) (string, []byte) {
    85  	l := len(in)
    86  	c := copy(buf, in)
    87  	if c != l {
    88  		panic("not copied full string")
    89  	}
    90  
    91  	return yoloString(buf[0:l]), buf[l:]
    92  }
    93  
    94  // FromLabelsToLabelAdapters casts labels.Labels to []LabelAdapter.
    95  // It uses unsafe, but as LabelAdapter == labels.Label this should be safe.
    96  // This allows us to use labels.Labels directly in protos.
    97  func FromLabelsToLabelAdapters(ls labels.Labels) []LabelAdapter {
    98  	return *(*[]LabelAdapter)(unsafe.Pointer(&ls))
    99  }
   100  
   101  // FromLabelAdaptersToMetric converts []LabelAdapter to a model.Metric.
   102  // Don't do this on any performance sensitive paths.
   103  func FromLabelAdaptersToMetric(ls []LabelAdapter) model.Metric {
   104  	return util.LabelsToMetric(FromLabelAdaptersToLabels(ls))
   105  }
   106  
   107  // FromMetricsToLabelAdapters converts model.Metric to []LabelAdapter.
   108  // Don't do this on any performance sensitive paths.
   109  // The result is sorted.
   110  func FromMetricsToLabelAdapters(metric model.Metric) []LabelAdapter {
   111  	result := make([]LabelAdapter, 0, len(metric))
   112  	for k, v := range metric {
   113  		result = append(result, LabelAdapter{
   114  			Name:  string(k),
   115  			Value: string(v),
   116  		})
   117  	}
   118  	sort.Sort(byLabel(result)) // The labels should be sorted upon initialisation.
   119  	return result
   120  }
   121  
   122  type byLabel []LabelAdapter
   123  
   124  func (s byLabel) Len() int           { return len(s) }
   125  func (s byLabel) Less(i, j int) bool { return strings.Compare(s[i].Name, s[j].Name) < 0 }
   126  func (s byLabel) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   127  
   128  // isTesting is only set from tests to get special behaviour to verify that custom sample encode and decode is used,
   129  // both when using jsonitor or standard json package.
   130  var isTesting = false
   131  
   132  // MarshalJSON implements json.Marshaler.
   133  func (s LegacySample) MarshalJSON() ([]byte, error) {
   134  	if isTesting && math.IsNaN(s.Value) {
   135  		return nil, fmt.Errorf("test sample")
   136  	}
   137  
   138  	t, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(model.Time(s.TimestampMs))
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	v, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(model.SampleValue(s.Value))
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil
   147  }
   148  
   149  // UnmarshalJSON implements json.Unmarshaler.
   150  func (s *LegacySample) UnmarshalJSON(b []byte) error {
   151  	var t model.Time
   152  	var v model.SampleValue
   153  	vs := [...]stdjson.Unmarshaler{&t, &v}
   154  	if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(b, &vs); err != nil {
   155  		return err
   156  	}
   157  	s.TimestampMs = int64(t)
   158  	s.Value = float64(v)
   159  
   160  	if isTesting && math.IsNaN(float64(v)) {
   161  		return fmt.Errorf("test sample")
   162  	}
   163  	return nil
   164  }
   165  
   166  func SampleJsoniterEncode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
   167  	legacySample := (*LegacySample)(ptr)
   168  
   169  	if isTesting && math.IsNaN(legacySample.Value) {
   170  		stream.Error = fmt.Errorf("test sample")
   171  		return
   172  	}
   173  
   174  	stream.WriteArrayStart()
   175  	stream.WriteFloat64(float64(legacySample.TimestampMs) / float64(time.Second/time.Millisecond))
   176  	stream.WriteMore()
   177  	stream.WriteString(model.SampleValue(legacySample.Value).String())
   178  	stream.WriteArrayEnd()
   179  }
   180  
   181  func SampleJsoniterDecode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
   182  	if !iter.ReadArray() {
   183  		iter.ReportError("logproto.LegacySample", "expected [")
   184  		return
   185  	}
   186  
   187  	t := model.Time(iter.ReadFloat64() * float64(time.Second/time.Millisecond))
   188  
   189  	if !iter.ReadArray() {
   190  		iter.ReportError("logproto.LegacySample", "expected ,")
   191  		return
   192  	}
   193  
   194  	bs := iter.ReadStringAsSlice()
   195  	ss := *(*string)(unsafe.Pointer(&bs))
   196  	v, err := strconv.ParseFloat(ss, 64)
   197  	if err != nil {
   198  		iter.ReportError("logproto.LegacySample", err.Error())
   199  		return
   200  	}
   201  
   202  	if isTesting && math.IsNaN(v) {
   203  		iter.Error = fmt.Errorf("test sample")
   204  		return
   205  	}
   206  
   207  	if iter.ReadArray() {
   208  		iter.ReportError("logproto.LegacySample", "expected ]")
   209  	}
   210  
   211  	*(*LegacySample)(ptr) = LegacySample{
   212  		TimestampMs: int64(t),
   213  		Value:       v,
   214  	}
   215  }
   216  
   217  func init() {
   218  	jsoniter.RegisterTypeEncoderFunc("logproto.LegacySample", SampleJsoniterEncode, func(unsafe.Pointer) bool { return false })
   219  	jsoniter.RegisterTypeDecoderFunc("logproto.LegacySample", SampleJsoniterDecode)
   220  }
   221  
   222  // Combine unique values from multiple LabelResponses into a single, sorted LabelResponse.
   223  func MergeLabelResponses(responses []*LabelResponse) (*LabelResponse, error) {
   224  	if len(responses) == 0 {
   225  		return &LabelResponse{}, nil
   226  	} else if len(responses) == 1 {
   227  		return responses[0], nil
   228  	}
   229  
   230  	unique := map[string]struct{}{}
   231  
   232  	for _, r := range responses {
   233  		for _, v := range r.Values {
   234  			if _, ok := unique[v]; !ok {
   235  				unique[v] = struct{}{}
   236  			} else {
   237  				continue
   238  			}
   239  		}
   240  	}
   241  
   242  	result := &LabelResponse{Values: make([]string, 0, len(unique))}
   243  
   244  	for value := range unique {
   245  		result.Values = append(result.Values, value)
   246  	}
   247  
   248  	// Sort the unique values before returning because we can't rely on map key ordering
   249  	sort.Strings(result.Values)
   250  
   251  	return result, nil
   252  }
   253  
   254  // Combine unique label sets from multiple SeriesResponse and return a single SeriesResponse.
   255  func MergeSeriesResponses(responses []*SeriesResponse) (*SeriesResponse, error) {
   256  	if len(responses) == 0 {
   257  		return &SeriesResponse{}, nil
   258  	} else if len(responses) == 1 {
   259  		return responses[0], nil
   260  	}
   261  
   262  	result := &SeriesResponse{
   263  		Series: make([]SeriesIdentifier, 0, len(responses)),
   264  	}
   265  
   266  	for _, r := range responses {
   267  		result.Series = append(result.Series, r.Series...)
   268  	}
   269  
   270  	return result, nil
   271  }
   272  
   273  // Satisfy definitions.Request
   274  
   275  // GetStart returns the start timestamp of the request in milliseconds.
   276  func (m *IndexStatsRequest) GetStart() int64 {
   277  	return int64(m.From)
   278  }
   279  
   280  // GetEnd returns the end timestamp of the request in milliseconds.
   281  func (m *IndexStatsRequest) GetEnd() int64 {
   282  	return int64(m.Through)
   283  }
   284  
   285  // GetStep returns the step of the request in milliseconds.
   286  func (m *IndexStatsRequest) GetStep() int64 { return 0 }
   287  
   288  // GetQuery returns the query of the request.
   289  func (m *IndexStatsRequest) GetQuery() string {
   290  	return m.Matchers
   291  }
   292  
   293  // GetCachingOptions returns the caching options.
   294  func (m *IndexStatsRequest) GetCachingOptions() (res definitions.CachingOptions) { return }
   295  
   296  // WithStartEnd clone the current request with different start and end timestamp.
   297  func (m *IndexStatsRequest) WithStartEnd(startTime int64, endTime int64) definitions.Request {
   298  	new := *m
   299  	new.From = model.TimeFromUnixNano(startTime * int64(time.Millisecond))
   300  	new.Through = model.TimeFromUnixNano(endTime * int64(time.Millisecond))
   301  	return &new
   302  }
   303  
   304  // WithQuery clone the current request with a different query.
   305  func (m *IndexStatsRequest) WithQuery(query string) definitions.Request {
   306  	new := *m
   307  	new.Matchers = query
   308  	return &new
   309  }
   310  
   311  // LogToSpan writes information about this request to an OpenTracing span
   312  func (m *IndexStatsRequest) LogToSpan(sp opentracing.Span) {
   313  	sp.LogFields(
   314  		otlog.String("query", m.GetQuery()),
   315  		otlog.String("start", timestamp.Time(m.GetStart()).String()),
   316  		otlog.String("end", timestamp.Time(m.GetEnd()).String()),
   317  	)
   318  }