-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add metadata to push payload #9694
Changes from 37 commits
b799556
b0810c8
b7ae131
9bec73a
d876c21
9c6fdef
b4ed160
8c6f22e
c854d06
95ba8a2
7db3e54
ea8f3f6
73cf5dc
7b77c69
44b0932
617e864
f43dfab
384d556
4088112
0b0af55
d4e311c
10d404e
fcd1e66
64d3d6c
96c804d
7f25bb8
752ae6b
4b67145
aa58916
4c60a9e
4028758
8c76150
fb505b4
ccaf192
fb42e9c
eaf235c
7438809
f6e6918
f8c33aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -8,14 +8,14 @@ import ( | |||
"time" | ||||
"unsafe" | ||||
|
||||
"github.com/grafana/loki/pkg/storage/stores/index/seriesvolume" | ||||
|
||||
"github.com/buger/jsonparser" | ||||
json "github.com/json-iterator/go" | ||||
"github.com/prometheus/common/model" | ||||
"github.com/prometheus/prometheus/model/labels" | ||||
|
||||
"github.com/grafana/loki/pkg/logproto" | ||||
"github.com/grafana/loki/pkg/logqlmodel/stats" | ||||
"github.com/grafana/loki/pkg/storage/stores/index/seriesvolume" | ||||
) | ||||
|
||||
var ( | ||||
|
@@ -57,9 +57,129 @@ func (q *QueryResponse) UnmarshalJSON(data []byte) error { | |||
}) | ||||
} | ||||
|
||||
// PushRequest models a log stream push | ||||
// PushRequest models a log stream push but is unmarshalled to proto push format. | ||||
type PushRequest struct { | ||||
Streams []*Stream `json:"streams"` | ||||
Streams []LogProtoStream `json:"streams"` | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously this stored a pointer, now it stores a struct. Not sure we want this (would need to be tested independently for performance) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comes from this PR: The only reason why I think this is a pointer is so we don't make a copy of the stream when calling NewStream which receives a ptr to With Sandeep's changes, we no longer use that since now we decode from json to loki/pkg/util/unmarshal/unmarshal.go Line 22 in 44b0932
So having that as a ptr or not shouldn't make much difference. |
||||
} | ||||
|
||||
// LogProtoStream helps with unmarshalling of each log stream for push request. | ||||
// This might look un-necessary but without it the CPU usage in benchmarks was increasing by ~25% :shrug: | ||||
type LogProtoStream logproto.Stream | ||||
|
||||
func (s *LogProtoStream) UnmarshalJSON(data []byte) error { | ||||
err := jsonparser.ObjectEach(data, func(key, val []byte, ty jsonparser.ValueType, _ int) error { | ||||
switch string(key) { | ||||
case "stream": | ||||
labels := make(LabelSet) | ||||
err := jsonparser.ObjectEach(val, func(key, val []byte, dataType jsonparser.ValueType, _ int) error { | ||||
if dataType != jsonparser.String { | ||||
return jsonparser.MalformedStringError | ||||
} | ||||
labels[yoloString(key)] = yoloString(val) | ||||
return nil | ||||
}) | ||||
if err != nil { | ||||
return err | ||||
} | ||||
s.Labels = labels.String() | ||||
case "values": | ||||
if ty == jsonparser.Null { | ||||
return nil | ||||
} | ||||
entries, err := unmarshalHTTPToLogProtoEntries(val) | ||||
if err != nil { | ||||
return err | ||||
} | ||||
s.Entries = entries | ||||
} | ||||
return nil | ||||
}) | ||||
return err | ||||
} | ||||
|
||||
func unmarshalHTTPToLogProtoEntries(data []byte) ([]logproto.Entry, error) { | ||||
var ( | ||||
entries []logproto.Entry | ||||
parseError error | ||||
) | ||||
if _, err := jsonparser.ArrayEach(data, func(value []byte, ty jsonparser.ValueType, _ int, err error) { | ||||
if err != nil || parseError != nil { | ||||
return | ||||
} | ||||
if ty == jsonparser.Null { | ||||
return | ||||
} | ||||
e, err := unmarshalHTTPToLogProtoEntry(value) | ||||
if err != nil { | ||||
parseError = err | ||||
return | ||||
} | ||||
entries = append(entries, e) | ||||
}); err != nil { | ||||
parseError = err | ||||
} | ||||
|
||||
if parseError != nil { | ||||
return nil, parseError | ||||
} | ||||
|
||||
return entries, nil | ||||
} | ||||
|
||||
func unmarshalHTTPToLogProtoEntry(data []byte) (logproto.Entry, error) { | ||||
var ( | ||||
i int | ||||
parseError error | ||||
e logproto.Entry | ||||
) | ||||
_, err := jsonparser.ArrayEach(data, func(value []byte, t jsonparser.ValueType, _ int, _ error) { | ||||
// assert that both items in array are of type string | ||||
if (i == 0 || i == 1) && t != jsonparser.String { | ||||
parseError = jsonparser.MalformedStringError | ||||
return | ||||
} else if i == 2 && t != jsonparser.Object { | ||||
parseError = jsonparser.MalformedObjectError | ||||
return | ||||
} | ||||
switch i { | ||||
case 0: // timestamp | ||||
ts, err := jsonparser.ParseInt(value) | ||||
if err != nil { | ||||
parseError = err | ||||
return | ||||
} | ||||
e.Timestamp = time.Unix(0, ts) | ||||
case 1: // value | ||||
v, err := jsonparser.ParseString(value) | ||||
if err != nil { | ||||
parseError = err | ||||
return | ||||
} | ||||
e.Line = v | ||||
case 2: // nonIndexedLabels | ||||
var nonIndexedLabels labels.Labels | ||||
err := jsonparser.ObjectEach(value, func(key, val []byte, dataType jsonparser.ValueType, _ int) error { | ||||
if dataType != jsonparser.String { | ||||
return jsonparser.MalformedStringError | ||||
} | ||||
nonIndexedLabels = append(nonIndexedLabels, labels.Label{ | ||||
Name: yoloString(key), | ||||
Value: yoloString(val), | ||||
salvacorts marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
}) | ||||
return nil | ||||
}) | ||||
if err != nil { | ||||
parseError = err | ||||
return | ||||
} | ||||
e.NonIndexedLabels = nonIndexedLabels | ||||
} | ||||
i++ | ||||
}) | ||||
if parseError != nil { | ||||
return e, parseError | ||||
} | ||||
return e, err | ||||
} | ||||
|
||||
// ResultType holds the type of the result | ||||
|
@@ -441,3 +561,7 @@ func labelVolumeLimit(r *http.Request) error { | |||
|
||||
return nil | ||||
} | ||||
|
||||
func yoloString(buf []byte) string { | ||||
return *((*string)(unsafe.Pointer(&buf))) | ||||
} | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the scariest thing in the PR ) we should use it only if we check that the buffer is not updated, however, a lot of functions in stack trace mean that somebody can do some optimization to reuse the slice and it will break all our implementation. Why do we need it here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed in Slack, removed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have doubts about using yoloString here.
if underlying array of
value
is reused , the values here will be also updatedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should remove
yoloString
here. I had used it earlier because the labels were anyways converted to string immediately. The references to string are now being retained longer so it is not safe to useyoloString
here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in Slack, removed.