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 }