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

Metrics for TCF 2 adoption #1360

Merged
merged 11 commits into from
Jul 2, 2020
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(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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. So we're logging when enforcement is in play, but not if the enforcement resulted in action. Privacy enforcement still could be avoided for some bidders depending on permission setup. The same will soon be true for CCPA. I don't have a good suggestion here, just wanted to call it out.

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 int) {
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 int) {
}
25 changes: 25 additions & 0 deletions pbsmetrics/go_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,15 @@ type Metrics struct {
ImpsTypeAudio metrics.Meter
ImpsTypeNative metrics.Meter

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

// TCF adaption metrics
TCF1ReqMeter metrics.Meter
TCF2ReqMeter metrics.Meter
TCFReqErrMeter 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 +143,10 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
TimeoutNotificationSuccess: blankMeter,
TimeoutNotificationFailure: blankMeter,

TCF1ReqMeter: blankMeter,
TCF2ReqMeter: blankMeter,
TCFReqErrMeter: blankMeter,

AdapterMetrics: make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)),
accountMetrics: make(map[string]*accountMetrics),
MetricsDisabled: disableMetrics,
Expand Down Expand Up @@ -218,6 +228,10 @@ 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)

newMetrics.TCF1ReqMeter = metrics.GetOrRegisterMeter("privacy.request.tcf.v1", registry)
newMetrics.TCF2ReqMeter = metrics.GetOrRegisterMeter("privacy.request.tcf.v2", registry)
newMetrics.TCFReqErrMeter = metrics.GetOrRegisterMeter("privacy.request.tcf.err", registry)
return newMetrics
}

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

func (me *Metrics) RecordTCFReq(version int) {
if version == 1 {
me.TCF1ReqMeter.Mark(1)
} else if version == 2 {
me.TCF2ReqMeter.Mark(1)
} else {
me.TCFReqErrMeter.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.TCF1ReqMeter)
ensureContains(t, registry, "privacy.request.tcf.v2", m.TCF2ReqMeter)
ensureContains(t, registry, "privacy.request.tcf.err", m.TCFReqErrMeter)

}

func TestRecordBidType(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions pbsmetrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,5 @@ type MetricsEngine interface {
RecordPrebidCacheRequestTime(success bool, length time.Duration)
RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
RecordTimeoutNotice(sucess bool)
RecordTCFReq(version int)
}
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)
}

// RecordTCF mock
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: RecordTCFReq mock

func (me *MetricsEngineMock) RecordTCFReq(version int) {
me.Called(version)
}
32 changes: 28 additions & 4 deletions pbsmetrics/prometheus/prometheus.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package prometheusmetrics

import (
"fmt"
"strconv"
"time"

Expand Down Expand Up @@ -28,7 +29,8 @@ type Metrics struct {
requestsWithoutCookie *prometheus.CounterVec
storedImpressionsCacheResult *prometheus.CounterVec
storedRequestCacheResult *prometheus.CounterVec
timeout_notifications *prometheus.CounterVec
timeoutNotifications *prometheus.CounterVec
tcfMetrics *prometheus.CounterVec
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please call this tcfVersion or something along those lines so that we're consistent with the rest of the metric names?


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

const (
Expand All @@ -85,6 +88,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 +161,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.tcfMetrics = newCounter(cfg, metrics.Registry,
"privacy_tcf",
hhhjort marked this conversation as resolved.
Show resolved Hide resolved
"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 +425,23 @@ 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 int) {
var value string = "err"
if version > 0 {
value = fmt.Sprintf("v%d", version)
}
m.tcfMetrics.With(prometheus.Labels{
versionLabel: value,
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(0)
m.RecordTCFReq(1)
m.RecordTCFReq(2)
m.RecordTCFReq(1)

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

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

assertCounterVecValue(t, "", "privacy_tcf:v2", m.tcfMetrics,
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