@@ -25,6 +25,7 @@ import (
25
25
"github.com/grafana/dskit/user"
26
26
"github.com/opentracing/opentracing-go"
27
27
"github.com/pkg/errors"
28
+ promRemote "github.com/prometheus/prometheus/storage/remote"
28
29
29
30
"github.com/grafana/mimir/pkg/mimirpb"
30
31
"github.com/grafana/mimir/pkg/util"
@@ -36,6 +37,13 @@ import (
36
37
// PushFunc defines the type of the push. It is similar to http.HandlerFunc.
37
38
type PushFunc func (ctx context.Context , req * Request ) error
38
39
40
+ // The PushFunc might store promRemote.WriteResponseStats in the context.
41
+ type pushResponseStatsContextMarker struct {}
42
+
43
+ var (
44
+ PushResponseStatsContextKey = & pushResponseStatsContextMarker {}
45
+ )
46
+
39
47
// parserFunc defines how to read the body the request from an HTTP request. It takes an optional RequestBuffers.
40
48
type parserFunc func (ctx context.Context , r * http.Request , maxSize int , buffers * util.RequestBuffers , req * mimirpb.PreallocWriteRequest , logger log.Logger ) error
41
49
@@ -151,65 +159,68 @@ func handler(
151
159
}
152
160
}
153
161
154
- var supplier supplierFunc
155
162
isRW2 , err := isRemoteWrite2 (r )
156
163
if err != nil {
157
164
http .Error (w , err .Error (), http .StatusBadRequest )
158
165
}
159
- if isRW2 {
160
- supplier = func () (* mimirpb.WriteRequest , func (), error ) {
161
- // Return 415 Unsupported Media Type for remote-write v2 requests for now. This is not retryable
162
- // unless the client switches to remote-write v1.
163
- return nil , nil , httpgrpc .Error (http .StatusUnsupportedMediaType , "remote-write v2 is not supported" )
164
- }
165
- } else {
166
- supplier = func () (* mimirpb.WriteRequest , func (), error ) {
167
- rb := util .NewRequestBuffers (requestBufferPool )
168
- var req mimirpb.PreallocWriteRequest
166
+ supplier := func () (* mimirpb.WriteRequest , func (), error ) {
167
+ rb := util .NewRequestBuffers (requestBufferPool )
168
+ var req mimirpb.PreallocWriteRequest
169
169
170
- userID , err := tenant .TenantID (ctx )
171
- if err != nil && ! errors .Is (err , user .ErrNoOrgID ) { // ignore user.ErrNoOrgID
172
- return nil , nil , errors .Wrap (err , "failed to get tenant ID" )
173
- }
170
+ req .UnmarshalFromRW2 = isRW2
174
171
175
- // userID might be empty if none was in the ctx, in this case just use the default setting.
176
- if limits .MaxGlobalExemplarsPerUser (userID ) == 0 {
177
- // The user is not allowed to send exemplars, so there is no need to unmarshal them.
178
- // Optimization to avoid the allocations required for unmarshaling exemplars.
179
- req .SkipUnmarshalingExemplars = true
180
- }
172
+ userID , err := tenant .TenantID (ctx )
173
+ if err != nil && ! errors .Is (err , user .ErrNoOrgID ) { // ignore user.ErrNoOrgID
174
+ return nil , nil , errors .Wrap (err , "failed to get tenant ID" )
175
+ }
181
176
182
- if err := parser (ctx , r , maxRecvMsgSize , rb , & req , logger ); err != nil {
183
- // Check for httpgrpc error, default to client error if parsing failed
184
- if _ , ok := httpgrpc .HTTPResponseFromError (err ); ! ok {
185
- err = httpgrpc .Error (http .StatusBadRequest , err .Error ())
186
- }
177
+ // userID might be empty if none was in the ctx, in this case just use the default setting.
178
+ if limits .MaxGlobalExemplarsPerUser (userID ) == 0 {
179
+ // The user is not allowed to send exemplars, so there is no need to unmarshal them.
180
+ // Optimization to avoid the allocations required for unmarshaling exemplars.
181
+ req .SkipUnmarshalingExemplars = true
182
+ }
187
183
188
- rb .CleanUp ()
189
- return nil , nil , err
184
+ if err := parser (ctx , r , maxRecvMsgSize , rb , & req , logger ); err != nil {
185
+ // Check for httpgrpc error, default to client error if parsing failed
186
+ if _ , ok := httpgrpc .HTTPResponseFromError (err ); ! ok {
187
+ err = httpgrpc .Error (http .StatusBadRequest , err .Error ())
190
188
}
191
189
192
- if allowSkipLabelNameValidation {
193
- req .SkipLabelValidation = req .SkipLabelValidation && r .Header .Get (SkipLabelNameValidationHeader ) == "true"
194
- } else {
195
- req .SkipLabelValidation = false
196
- }
190
+ rb .CleanUp ()
191
+ return nil , nil , err
192
+ }
197
193
198
- if allowSkipLabelCountValidation {
199
- req .SkipLabelCountValidation = req .SkipLabelCountValidation && r .Header .Get (SkipLabelCountValidationHeader ) == "true"
200
- } else {
201
- req .SkipLabelCountValidation = false
202
- }
194
+ if allowSkipLabelNameValidation {
195
+ req .SkipLabelValidation = req .SkipLabelValidation && r .Header .Get (SkipLabelNameValidationHeader ) == "true"
196
+ } else {
197
+ req .SkipLabelValidation = false
198
+ }
203
199
204
- cleanup := func () {
205
- mimirpb .ReuseSlice (req .Timeseries )
206
- rb .CleanUp ()
207
- }
208
- return & req .WriteRequest , cleanup , nil
200
+ if allowSkipLabelCountValidation {
201
+ req .SkipLabelCountValidation = req .SkipLabelCountValidation && r .Header .Get (SkipLabelCountValidationHeader ) == "true"
202
+ } else {
203
+ req .SkipLabelCountValidation = false
204
+ }
205
+
206
+ cleanup := func () {
207
+ mimirpb .ReuseSlice (req .Timeseries )
208
+ rb .CleanUp ()
209
209
}
210
+ return & req .WriteRequest , cleanup , nil
210
211
}
211
212
req := newRequest (supplier )
212
- if err := push (ctx , req ); err != nil {
213
+ ctx = contextWithWriteResponseStats (ctx )
214
+ err = push (ctx , req )
215
+ rsValue := ctx .Value (PushResponseStatsContextKey )
216
+ if rsValue != nil {
217
+ rs := rsValue .(* promRemote.WriteResponseStats )
218
+ addWriteResponseStats (w , rs )
219
+ } else {
220
+ // This should not happen, but if it does, we should not panic.
221
+ addWriteResponseStats (w , & promRemote.WriteResponseStats {})
222
+ }
223
+ if err != nil {
213
224
if errors .Is (err , context .Canceled ) {
214
225
http .Error (w , err .Error (), statusClientClosedRequest )
215
226
level .Warn (logger ).Log ("msg" , "push request canceled" , "err" , err )
@@ -277,6 +288,35 @@ func isRemoteWrite2(r *http.Request) (bool, error) {
277
288
return false , nil
278
289
}
279
290
291
+ // Consts from https://github.com/prometheus/prometheus/blob/main/storage/remote/stats.go
292
+ const (
293
+ rw20WrittenSamplesHeader = "X-Prometheus-Remote-Write-Samples-Written"
294
+ rw20WrittenHistogramsHeader = "X-Prometheus-Remote-Write-Histograms-Written"
295
+ rw20WrittenExemplarsHeader = "X-Prometheus-Remote-Write-Exemplars-Written"
296
+ )
297
+
298
+ func contextWithWriteResponseStats (ctx context.Context ) context.Context {
299
+ return context .WithValue (ctx , PushResponseStatsContextKey , & promRemote.WriteResponseStats {})
300
+ }
301
+
302
+ func addWriteResponseStats (w http.ResponseWriter , rs * promRemote.WriteResponseStats ) {
303
+ headers := w .Header ()
304
+ headers .Set (rw20WrittenSamplesHeader , strconv .Itoa (rs .Samples ))
305
+ headers .Set (rw20WrittenHistogramsHeader , strconv .Itoa (rs .Histograms ))
306
+ headers .Set (rw20WrittenExemplarsHeader , strconv .Itoa (rs .Exemplars ))
307
+ }
308
+
309
+ func updateWriteResponseStatsCtx (ctx context.Context , samples , histograms , exemplars int ) {
310
+ prs := ctx .Value (PushResponseStatsContextKey )
311
+ if prs == nil {
312
+ // Should not happen, but we should not panic anyway.
313
+ return
314
+ }
315
+ prs .(* promRemote.WriteResponseStats ).Samples += samples
316
+ prs .(* promRemote.WriteResponseStats ).Histograms += histograms
317
+ prs .(* promRemote.WriteResponseStats ).Exemplars += exemplars
318
+ }
319
+
280
320
func calculateRetryAfter (retryAttemptHeader string , minBackoff , maxBackoff time.Duration ) string {
281
321
const jitterFactor = 0.5
282
322
0 commit comments