Skip to content
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

Added metrics support to endpoint aspect #1226

Merged
merged 2 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pbsmetrics/config/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ func (me *MultiMetricsEngine) RecordPrebidCacheRequestTime(success bool, length
}
}

// RecordRequestQueueTime across all engines
func (me *MultiMetricsEngine) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) {
for _, thisME := range *me {
thisME.RecordRequestQueueTime(success, requestType, length)
}
}

// DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests)
type DummyMetricsEngine struct{}

Expand Down Expand Up @@ -251,3 +258,7 @@ func (me *DummyMetricsEngine) RecordStoredImpCacheResult(cacheResult pbsmetrics.
// RecordPrebidCacheRequestTime as a noop
func (me *DummyMetricsEngine) RecordPrebidCacheRequestTime(success bool, length time.Duration) {
}

// RecordRequestQueueTime as a noop
func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) {
}
6 changes: 6 additions & 0 deletions pbsmetrics/config/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ func TestMultiMetricsEngine(t *testing.T) {
for i := 0; i < 3; i++ {
metricsEngine.RecordImps(impTypeLabels)
}

metricsEngine.RecordRequestQueueTime(false, pbsmetrics.ReqTypeVideo, time.Duration(1))

//Make the metrics engine, instantiated here with goEngine, fill its RequestStatuses[RequestType][pbsmetrics.RequestStatusXX] with the new boolean values added to pbsmetrics.Labels
VerifyMetrics(t, "RequestStatuses.OpenRTB2.OK", goEngine.RequestStatuses[pbsmetrics.ReqTypeORTB2Web][pbsmetrics.RequestStatusOK].Count(), 5)
VerifyMetrics(t, "RequestStatuses.Legacy.OK", goEngine.RequestStatuses[pbsmetrics.ReqTypeLegacy][pbsmetrics.RequestStatusOK].Count(), 0)
Expand Down Expand Up @@ -148,6 +151,9 @@ func TestMultiMetricsEngine(t *testing.T) {
}
VerifyMetrics(t, "AdapterMetrics.AppNexus.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GotBidsMeter.Count(), 0)
VerifyMetrics(t, "AdapterMetrics.AppNexus.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].NoBidMeter.Count(), 5)

VerifyMetrics(t, "RecordRequestQueueTime.Video.Rejected", goEngine.RequestsQueueTimer[pbsmetrics.ReqTypeVideo][false].Count(), 1)
VerifyMetrics(t, "RecordRequestQueueTime.Video.Accepted", goEngine.RequestsQueueTimer[pbsmetrics.ReqTypeVideo][true].Count(), 0)
}

func VerifyMetrics(t *testing.T, name string, actual int64, expected int64) {
Expand Down
18 changes: 18 additions & 0 deletions pbsmetrics/go_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Metrics struct {
SafariRequestMeter metrics.Meter
SafariNoCookieMeter metrics.Meter
RequestTimer metrics.Timer
RequestsQueueTimer map[RequestType]map[bool]metrics.Timer
PrebidCacheRequestTimerSuccess metrics.Timer
PrebidCacheRequestTimerError metrics.Timer
StoredReqCacheMeter map[CacheResult]metrics.Meter
Expand Down Expand Up @@ -111,6 +112,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
SafariRequestMeter: blankMeter,
SafariNoCookieMeter: blankMeter,
RequestTimer: blankTimer,
RequestsQueueTimer: make(map[RequestType]map[bool]metrics.Timer),
PrebidCacheRequestTimerSuccess: blankTimer,
PrebidCacheRequestTimerError: blankTimer,
StoredReqCacheMeter: make(map[CacheResult]metrics.Meter),
Expand Down Expand Up @@ -146,6 +148,11 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
}
}

//to minimize memory usage, queuedTimeout metric is now supported for video endpoint only
//boolean value represents 2 general request statuses: accepted and rejected
newMetrics.RequestsQueueTimer["video"] = make(map[bool]metrics.Timer)
newMetrics.RequestsQueueTimer["video"][true] = blankTimer
newMetrics.RequestsQueueTimer["video"][false] = blankTimer
return newMetrics
}

Expand Down Expand Up @@ -191,11 +198,15 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
statusMap[stat] = metrics.GetOrRegisterMeter("requests."+string(stat)+"."+string(typ), registry)
}
}

for _, cacheRes := range CacheResults() {
newMetrics.StoredReqCacheMeter[cacheRes] = metrics.GetOrRegisterMeter(fmt.Sprintf("stored_request_cache_%s", string(cacheRes)), registry)
newMetrics.StoredImpCacheMeter[cacheRes] = metrics.GetOrRegisterMeter(fmt.Sprintf("stored_imp_cache_%s", string(cacheRes)), registry)
}

newMetrics.RequestsQueueTimer["video"][true] = metrics.GetOrRegisterTimer("queued_requests.video.accepted", registry)
newMetrics.RequestsQueueTimer["video"][false] = metrics.GetOrRegisterTimer("queued_requests.video.rejected", registry)

newMetrics.userSyncSet[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.sets", registry)
newMetrics.userSyncGDPRPrevent[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.gdpr_prevent", registry)
return newMetrics
Expand Down Expand Up @@ -526,6 +537,13 @@ func (me *Metrics) RecordPrebidCacheRequestTime(success bool, length time.Durati
}
}

func (me *Metrics) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) {
if requestType == ReqTypeVideo { //remove this check when other request types are supported
me.RequestsQueueTimer[requestType][success].Update(length)
}

}

func doMark(bidder openrtb_ext.BidderName, meters map[openrtb_ext.BidderName]metrics.Meter) {
met, ok := meters[bidder]
if ok {
Expand Down
3 changes: 3 additions & 0 deletions pbsmetrics/go_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ func TestNewMetrics(t *testing.T) {
ensureContains(t, registry, "requests.badinput.video", m.RequestStatuses[ReqTypeVideo][RequestStatusBadInput])
ensureContains(t, registry, "requests.err.video", m.RequestStatuses[ReqTypeVideo][RequestStatusErr])
ensureContains(t, registry, "requests.networkerr.video", m.RequestStatuses[ReqTypeVideo][RequestStatusNetworkErr])

ensureContains(t, registry, "queued_requests.video.rejected", m.RequestsQueueTimer[ReqTypeVideo][false])
ensureContains(t, registry, "queued_requests.video.accepted", m.RequestsQueueTimer[ReqTypeVideo][true])
}

func TestRecordBidType(t *testing.T) {
Expand Down
13 changes: 8 additions & 5 deletions pbsmetrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,12 @@ func CookieTypes() []CookieFlag {

// Request/return status
const (
RequestStatusOK RequestStatus = "ok"
RequestStatusBadInput RequestStatus = "badinput"
RequestStatusErr RequestStatus = "err"
RequestStatusNetworkErr RequestStatus = "networkerr"
RequestStatusBlacklisted RequestStatus = "blacklistedacctorapp"
RequestStatusOK RequestStatus = "ok"
RequestStatusBadInput RequestStatus = "badinput"
RequestStatusErr RequestStatus = "err"
RequestStatusNetworkErr RequestStatus = "networkerr"
RequestStatusBlacklisted RequestStatus = "blacklistedacctorapp"
RequestStatusQueueTimeout RequestStatus = "queuetimeout"
)

func RequestStatuses() []RequestStatus {
Expand All @@ -168,6 +169,7 @@ func RequestStatuses() []RequestStatus {
RequestStatusErr,
RequestStatusNetworkErr,
RequestStatusBlacklisted,
RequestStatusQueueTimeout,
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
}
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down Expand Up @@ -272,4 +274,5 @@ type MetricsEngine interface {
RecordStoredReqCacheResult(cacheResult CacheResult, inc int)
RecordStoredImpCacheResult(cacheResult CacheResult, inc int)
RecordPrebidCacheRequestTime(success bool, length time.Duration)
RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
}
5 changes: 5 additions & 0 deletions pbsmetrics/metrics_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,8 @@ func (me *MetricsEngineMock) RecordStoredImpCacheResult(cacheResult CacheResult,
func (me *MetricsEngineMock) RecordPrebidCacheRequestTime(success bool, length time.Duration) {
me.Called(success, length)
}

// RecordRequestQueueTime mock
func (me *MetricsEngineMock) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) {
me.Called(success, requestType, length)
}
8 changes: 8 additions & 0 deletions pbsmetrics/prometheus/preload.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package prometheusmetrics

import (
"github.com/prebid/prebid-server/pbsmetrics"
"github.com/prometheus/client_golang/prometheus"
)

Expand Down Expand Up @@ -91,6 +92,13 @@ func preloadLabelValues(m *Metrics) {
adapterLabel: adapterValues,
actionLabel: actionValues,
})

//to minimize memory usage, queuedTimeout metric is now supported for video endpoint only
//boolean value represents 2 general request statuses: accepted and rejected
preloadLabelValuesForHistogram(m.requestsQueueTimer, map[string][]string{
requestTypeLabel: {string(pbsmetrics.ReqTypeVideo)},
requestStatusLabel: {requestSuccessLabel, requestRejectLabel},
})
}

func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) {
Expand Down
24 changes: 24 additions & 0 deletions pbsmetrics/prometheus/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Metrics struct {
prebidCacheWriteTimer *prometheus.HistogramVec
requests *prometheus.CounterVec
requestsTimer *prometheus.HistogramVec
requestsQueueTimer *prometheus.HistogramVec
requestsWithoutCookie *prometheus.CounterVec
storedImpressionsCacheResult *prometheus.CounterVec
storedRequestCacheResult *prometheus.CounterVec
Expand Down Expand Up @@ -73,11 +74,17 @@ const (
markupDeliveryNurl = "nurl"
)

const (
requestSuccessLabel = "requestAcceptedLabel"
requestRejectLabel = "requestRejectedLabel"
)

// NewMetrics initializes a new Prometheus metrics instance with preloaded label values.
func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
requestTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1}
cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1}
priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000}
queuedRequestTimeBuckets := []float64{0, 1, 5, 30, 60, 120, 180, 240, 300}

metrics := Metrics{}
metrics.Registry = prometheus.NewRegistry()
Expand Down Expand Up @@ -187,6 +194,12 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
"Count of total requests to Prebid Server labeled by account.",
[]string{accountLabel})

metrics.requestsQueueTimer = newHistogram(cfg, metrics.Registry,
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
"request_queue_time",
"Seconds request was waiting in queue",
[]string{requestTypeLabel, requestStatusLabel},
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
queuedRequestTimeBuckets)

preloadLabelValues(&metrics)

return &metrics
Expand Down Expand Up @@ -374,3 +387,14 @@ func (m *Metrics) RecordPrebidCacheRequestTime(success bool, length time.Duratio
successLabel: strconv.FormatBool(success),
}).Observe(length.Seconds())
}

func (m *Metrics) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) {
successLabelFormatted := requestRejectLabel
if success {
successLabelFormatted = requestSuccessLabel
}
m.requestsQueueTimer.With(prometheus.Labels{
requestTypeLabel: string(requestType),
requestStatusLabel: successLabelFormatted,
}).Observe(length.Seconds())
}
60 changes: 60 additions & 0 deletions pbsmetrics/prometheus/prometheus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,48 @@ func TestMetricAccumulationSpotCheck(t *testing.T) {
expectedValue)
}

func TestRecordRequestQueueTimeMetric(t *testing.T) {
performTest := func(m *Metrics, requestStatus bool, requestType pbsmetrics.RequestType, timeInSec float64) {
m.RecordRequestQueueTime(requestStatus, requestType, time.Duration(timeInSec*float64(time.Second)))
}

testCases := []struct {
description string
status string
testCase func(m *Metrics)
expectedCount uint64
expectedSum float64
}{
{
description: "Success",
status: requestSuccessLabel,
testCase: func(m *Metrics) {
performTest(m, true, pbsmetrics.ReqTypeVideo, 2)
},
expectedCount: 1,
expectedSum: 2,
},
{
description: "TimeoutError",
status: requestRejectLabel,
testCase: func(m *Metrics) {
performTest(m, false, pbsmetrics.ReqTypeVideo, 50)
},
expectedCount: 1,
expectedSum: 50,
},
}

m := createMetricsForTesting()
for _, test := range testCases {

test.testCase(m)

result := getHistogramFromHistogramVecByTwoKeys(m.requestsQueueTimer, requestTypeLabel, "video", requestStatusLabel, test.status)
assertHistogram(t, test.description, result, test.expectedCount, test.expectedSum)
}
}

func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) {
m := dto.Metric{}
counter.Write(&m)
Expand All @@ -906,6 +948,24 @@ func getHistogramFromHistogramVec(histogram *prometheus.HistogramVec, labelKey,
return result
}

func getHistogramFromHistogramVecByTwoKeys(histogram *prometheus.HistogramVec, label1Key, label1Value, label2Key, label2Value string) dto.Histogram {
var result dto.Histogram
processMetrics(histogram, func(m dto.Metric) {
for ind, label := range m.GetLabel() {
if label.GetName() == label1Key && label.GetValue() == label1Value {
valInd := ind
if ind == 1 {
valInd = 0
}
if m.Label[valInd].GetName() == label2Key && m.Label[valInd].GetValue() == label2Value {
result = *m.GetHistogram()
}
}
}
})
return result
}

func processMetrics(collector prometheus.Collector, handler func(m dto.Metric)) {
collectorChan := make(chan prometheus.Metric)
go func() {
Expand Down
8 changes: 7 additions & 1 deletion router/aspects/request_timeout_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package aspects
import (
"github.com/julienschmidt/httprouter"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/pbsmetrics"
"net/http"
"strconv"
"time"
)

func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders) httprouter.Handle {
func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders, metricsEngine pbsmetrics.MetricsEngine, requestType pbsmetrics.RequestType) httprouter.Handle {

return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {

Expand All @@ -30,13 +32,17 @@ func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestT
return
}

reqTimeDuration := time.Duration(reqTimeFloat * float64(time.Second))

//Return HTTP 408 if requests stays too long in queue
if reqTimeFloat >= reqTimeoutFloat {
w.WriteHeader(http.StatusRequestTimeout)
w.Write([]byte("Queued request processing time exceeded maximum"))
metricsEngine.RecordRequestQueueTime(false, requestType, reqTimeDuration)
return
}

metricsEngine.RecordRequestQueueTime(true, requestType, reqTimeDuration)
f(w, r, params)
}

Expand Down
Loading