Skip to content

Commit

Permalink
Metrics for TCF 2 adoption (#1360)
Browse files Browse the repository at this point in the history
  • Loading branch information
hhhjort authored Jul 2, 2020
1 parent 74af63b commit 47c7a6b
Show file tree
Hide file tree
Showing 13 changed files with 178 additions and 11 deletions.
5 changes: 4 additions & 1 deletion exchange/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque

// Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder
blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels)
cleanRequests, aliases, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig)
cleanRequests, aliases, cleanMetrics, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig)

if cleanMetrics.gdprEnforced {
e.me.RecordTCFReq(pbsmetrics.TCFVersionToValue(cleanMetrics.gdprTcfVersion))
}
// List of bidders we have requests for.
liveAdapters := listBiddersWithRequests(cleanRequests)

Expand Down
20 changes: 19 additions & 1 deletion exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"math/rand"

"github.com/prebid/go-gdpr/vendorconsent"

"github.com/buger/jsonparser"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/config"
Expand All @@ -17,6 +19,15 @@ import (
"github.com/prebid/prebid-server/privacy/lmt"
)

// cleanMetrics is a struct to export any metrics data resulting from cleanOpenRTBRequests(). It starts with just
// the TCF version, but made a struct to facilitate future expansion
type cleanMetrics struct {
// A simple flag if GDPR is being enforced on this request.
gdprEnforced bool
// a zero value means a missing or invalid GDPR string
gdprTcfVersion int
}

// cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is:
//
// 1. BidRequest.Imp[].Ext will only contain the "prebid" field and a "bidder" field which has the params for the intended Bidder.
Expand All @@ -29,7 +40,7 @@ func cleanOpenRTBRequests(ctx context.Context,
labels pbsmetrics.Labels,
gDPR gdpr.Permissions,
usersyncIfAmbiguous bool,
privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, errs []error) {
privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, cleanMetrics cleanMetrics, errs []error) {

impsByBidder, errs := splitImps(orig.Imp)
if len(errs) > 0 {
Expand Down Expand Up @@ -64,6 +75,13 @@ func cleanOpenRTBRequests(ctx context.Context,
LMT: lmtPolicy.ShouldEnforce(),
}

if gdpr == 1 {
cleanMetrics.gdprEnforced = true
parsedConsent, err := vendorconsent.ParseString(consent)
if err == nil {
cleanMetrics.gdprTcfVersion = int(parsedConsent.Version())
}
}
// bidder level privacy policies
for bidder, bidReq := range requestsByBidder {

Expand Down
6 changes: 3 additions & 3 deletions exchange/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func TestCleanOpenRTBRequests(t *testing.T) {
}

for _, test := range testCases {
reqByBidders, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
if test.hasError {
assert.NotNil(t, err, "Error shouldn't be nil")
} else {
Expand Down Expand Up @@ -120,7 +120,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
},
}

results, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
result := results["appnexus"]

assert.Nil(t, errs)
Expand Down Expand Up @@ -182,7 +182,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
},
}

results, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
result := results["appnexus"]

assert.Nil(t, errs)
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtW
github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98=
github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg=
github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A=
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54=
Expand Down Expand Up @@ -126,6 +128,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
11 changes: 11 additions & 0 deletions pbsmetrics/config/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,13 @@ func (me *MultiMetricsEngine) RecordTimeoutNotice(success bool) {
}
}

// RecordTCFReq across all engines
func (me *MultiMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) {
for _, thisME := range *me {
thisME.RecordTCFReq(version)
}
}

// 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 @@ -273,3 +280,7 @@ func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType p
// RecordTimeoutNotice as a noop
func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) {
}

// RecordReq as a noop
func (me *DummyMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) {
}
30 changes: 30 additions & 0 deletions pbsmetrics/go_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ type Metrics struct {
ImpsTypeAudio metrics.Meter
ImpsTypeNative metrics.Meter

// Notification timeout metrics
TimeoutNotificationSuccess metrics.Meter
TimeoutNotificationFailure metrics.Meter

// TCF adaption metrics
TCFReqVersion map[TCFVersionValue]metrics.Meter

AdapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics
// Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically
accountMetrics map[string]*accountMetrics
Expand Down Expand Up @@ -137,6 +141,8 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
TimeoutNotificationSuccess: blankMeter,
TimeoutNotificationFailure: blankMeter,

TCFReqVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())),

AdapterMetrics: make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)),
accountMetrics: make(map[string]*accountMetrics),
MetricsDisabled: disableMetrics,
Expand All @@ -154,6 +160,15 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
}
}

for _, c := range CacheResults() {
newMetrics.StoredReqCacheMeter[c] = blankMeter
newMetrics.StoredImpCacheMeter[c] = blankMeter
}

for _, v := range TCFVersions() {
newMetrics.TCFReqVersion[v] = blankMeter
}

//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)
Expand Down Expand Up @@ -218,6 +233,11 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d

newMetrics.TimeoutNotificationSuccess = metrics.GetOrRegisterMeter("timeout_notification.ok", registry)
newMetrics.TimeoutNotificationFailure = metrics.GetOrRegisterMeter("timeout_notification.failed", registry)

for _, version := range TCFVersions() {
newMetrics.TCFReqVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry)
}

return newMetrics
}

Expand Down Expand Up @@ -562,6 +582,16 @@ func (me *Metrics) RecordTimeoutNotice(success bool) {
return
}

func (me *Metrics) RecordTCFReq(version TCFVersionValue) {
met, ok := me.TCFReqVersion[version]
if ok {
met.Mark(1)
} else {
me.TCFReqVersion[TCFVersionErr].Mark(1)
}
return
}

func doMark(bidder openrtb_ext.BidderName, meters map[openrtb_ext.BidderName]metrics.Meter) {
met, ok := meters[bidder]
if ok {
Expand Down
4 changes: 4 additions & 0 deletions pbsmetrics/go_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func TestNewMetrics(t *testing.T) {

ensureContains(t, registry, "timeout_notification.ok", m.TimeoutNotificationSuccess)
ensureContains(t, registry, "timeout_notification.failed", m.TimeoutNotificationFailure)
ensureContains(t, registry, "privacy.request.tcf.v1", m.TCFReqVersion[TCFVersionV1])
ensureContains(t, registry, "privacy.request.tcf.v2", m.TCFReqVersion[TCFVersionV2])
ensureContains(t, registry, "privacy.request.tcf.err", m.TCFReqVersion[TCFVersionErr])

}

func TestRecordBidType(t *testing.T) {
Expand Down
30 changes: 30 additions & 0 deletions pbsmetrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,35 @@ func RequestActions() []RequestAction {
}
}

// TCFVersionValue : The possible values for TCF versions
type TCFVersionValue string

const (
TCFVersionErr TCFVersionValue = "err"
TCFVersionV1 TCFVersionValue = "v1"
TCFVersionV2 TCFVersionValue = "v2"
)

// TCFVersions rtuens the possible values for the TCF version
func TCFVersions() []TCFVersionValue {
return []TCFVersionValue{
TCFVersionErr,
TCFVersionV1,
TCFVersionV2,
}
}

// TCFVersionToValue takes an integer TCF version and returns the corresponding TCFVersionValue
func TCFVersionToValue(version int) TCFVersionValue {
switch {
case version == 1:
return TCFVersionV1
case version == 2:
return TCFVersionV2
}
return TCFVersionErr
}

// MetricsEngine is a generic interface to record PBS metrics into the desired backend
// The first three metrics function fire off once per incoming request, so total metrics
// will equal the total number of incoming requests. The remaining 5 fire off per outgoing
Expand Down Expand Up @@ -276,4 +305,5 @@ type MetricsEngine interface {
RecordPrebidCacheRequestTime(success bool, length time.Duration)
RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
RecordTimeoutNotice(sucess bool)
RecordTCFReq(version TCFVersionValue)
}
5 changes: 5 additions & 0 deletions pbsmetrics/metrics_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,8 @@ func (me *MetricsEngineMock) RecordRequestQueueTime(success bool, requestType Re
func (me *MetricsEngineMock) RecordTimeoutNotice(success bool) {
me.Called(success)
}

// RecordTCFReq mock
func (me *MetricsEngineMock) RecordTCFReq(version TCFVersionValue) {
me.Called(version)
}
5 changes: 5 additions & 0 deletions pbsmetrics/prometheus/preload.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ func preloadLabelValues(m *Metrics) {
requestTypeLabel: {string(pbsmetrics.ReqTypeVideo)},
requestStatusLabel: {requestSuccessLabel, requestRejectLabel},
})

preloadLabelValuesForCounter(m.tcfVersion, map[string][]string{
versionLabel: tcfVersionsAsString(),
sourceLabel: {string(sourceRequest)},
})
}

func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) {
Expand Down
27 changes: 23 additions & 4 deletions pbsmetrics/prometheus/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ type Metrics struct {
requestsWithoutCookie *prometheus.CounterVec
storedImpressionsCacheResult *prometheus.CounterVec
storedRequestCacheResult *prometheus.CounterVec
timeout_notifications *prometheus.CounterVec
timeoutNotifications *prometheus.CounterVec
tcfVersion *prometheus.CounterVec

// Adapter Metrics
adapterBids *prometheus.CounterVec
Expand Down Expand Up @@ -63,6 +64,7 @@ const (
requestStatusLabel = "request_status"
requestTypeLabel = "request_type"
successLabel = "success"
versionLabel = "version"
)

const (
Expand All @@ -85,6 +87,11 @@ const (
requestFailed = "failed"
)

const (
sourceLabel = "source"
sourceRequest = "request"
)

// 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}
Expand Down Expand Up @@ -153,11 +160,16 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
"Count of stored request cache requests attempts by hits or miss.",
[]string{cacheResultLabel})

metrics.timeout_notifications = newCounter(cfg, metrics.Registry,
metrics.timeoutNotifications = newCounter(cfg, metrics.Registry,
"timeout_notification",
"Count of timeout notifications triggered, and if they were successfully sent.",
[]string{successLabel})

metrics.tcfVersion = newCounter(cfg, metrics.Registry,
"privacy_tcf",
"Count of TCF versions for requests where GDPR was enforced.",
[]string{versionLabel, sourceLabel})

metrics.adapterBids = newCounter(cfg, metrics.Registry,
"adapter_bids",
"Count of bids labeled by adapter and markup delivery type (adm or nurl).",
Expand Down Expand Up @@ -412,12 +424,19 @@ func (m *Metrics) RecordRequestQueueTime(success bool, requestType pbsmetrics.Re

func (m *Metrics) RecordTimeoutNotice(success bool) {
if success {
m.timeout_notifications.With(prometheus.Labels{
m.timeoutNotifications.With(prometheus.Labels{
successLabel: requestSuccessful,
}).Inc()
} else {
m.timeout_notifications.With(prometheus.Labels{
m.timeoutNotifications.With(prometheus.Labels{
successLabel: requestFailed,
}).Inc()
}
}

func (m *Metrics) RecordTCFReq(version pbsmetrics.TCFVersionValue) {
m.tcfVersion.With(prometheus.Labels{
versionLabel: string(version),
sourceLabel: sourceRequest,
}).Inc()
}
34 changes: 32 additions & 2 deletions pbsmetrics/prometheus/prometheus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -930,20 +930,50 @@ func TestTimeoutNotifications(t *testing.T) {
m.RecordTimeoutNotice(true)
m.RecordTimeoutNotice(false)

assertCounterVecValue(t, "", "timeout_notifications:ok", m.timeout_notifications,
assertCounterVecValue(t, "", "timeout_notifications:ok", m.timeoutNotifications,
float64(2),
prometheus.Labels{
successLabel: requestSuccessful,
})

assertCounterVecValue(t, "", "timeout_notifications:fail", m.timeout_notifications,
assertCounterVecValue(t, "", "timeout_notifications:fail", m.timeoutNotifications,
float64(1),
prometheus.Labels{
successLabel: requestFailed,
})

}

func TestTCFMetrics(t *testing.T) {
m := createMetricsForTesting()

m.RecordTCFReq(pbsmetrics.TCFVersionToValue(0))
m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1))
m.RecordTCFReq(pbsmetrics.TCFVersionToValue(2))
m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1))

assertCounterVecValue(t, "", "privacy_tcf:err", m.tcfVersion,
float64(1),
prometheus.Labels{
versionLabel: "err",
sourceLabel: sourceRequest,
})

assertCounterVecValue(t, "", "privacy_tcf:v1", m.tcfVersion,
float64(2),
prometheus.Labels{
versionLabel: "v1",
sourceLabel: sourceRequest,
})

assertCounterVecValue(t, "", "privacy_tcf:v2", m.tcfVersion,
float64(1),
prometheus.Labels{
versionLabel: "v2",
sourceLabel: sourceRequest,
})
}

func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) {
m := dto.Metric{}
counter.Write(&m)
Expand Down
Loading

0 comments on commit 47c7a6b

Please sign in to comment.