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: Add per adapter metric indicating when buyeruid was scrubbed #3623

Merged
merged 8 commits into from
Apr 16, 2024
Merged
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,9 @@ type DisabledMetrics struct {
// that were created or reused.
AdapterConnectionMetrics bool `mapstructure:"adapter_connections_metrics"`

// True if we don't want to collect the per adapter buyer UID scrubbed metric
AdapterBuyerUIDScrubbed bool `mapstructure:"adapter_buyeruid_scrubbed"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add this field to config_test.go, with TestDefaults and TestFullConfig?


// True if we don't want to collect the per adapter GDPR request blocked metric
AdapterGDPRRequestBlocked bool `mapstructure:"adapter_gdpr_request_blocked"`

Expand Down Expand Up @@ -933,6 +936,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) {
v.SetDefault("metrics.disabled_metrics.account_debug", true)
v.SetDefault("metrics.disabled_metrics.account_stored_responses", true)
v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true)
v.SetDefault("metrics.disabled_metrics.adapter_buyeruid_scrubbed", true)
v.SetDefault("metrics.disabled_metrics.adapter_gdpr_request_blocked", false)
v.SetDefault("metrics.influxdb.host", "")
v.SetDefault("metrics.influxdb.database", "")
Expand Down
3 changes: 3 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func TestDefaults(t *testing.T) {
cmpBools(t, "account_debug", true, cfg.Metrics.Disabled.AccountDebug)
cmpBools(t, "account_stored_responses", true, cfg.Metrics.Disabled.AccountStoredResponses)
cmpBools(t, "adapter_connections_metrics", true, cfg.Metrics.Disabled.AdapterConnectionMetrics)
cmpBools(t, "adapter_buyeruid_scrubbed", true, cfg.Metrics.Disabled.AdapterBuyerUIDScrubbed)
cmpBools(t, "adapter_gdpr_request_blocked", false, cfg.Metrics.Disabled.AdapterGDPRRequestBlocked)
cmpStrings(t, "certificates_file", "", cfg.PemCertsFile)
cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled)
Expand Down Expand Up @@ -444,6 +445,7 @@ metrics:
account_debug: false
account_stored_responses: false
adapter_connections_metrics: true
adapter_buyeruid_scrubbed: false
adapter_gdpr_request_blocked: true
account_modules_metrics: true
blacklisted_apps: ["spamAppID","sketchy-app-id"]
Expand Down Expand Up @@ -836,6 +838,7 @@ func TestFullConfig(t *testing.T) {
cmpBools(t, "account_debug", false, cfg.Metrics.Disabled.AccountDebug)
cmpBools(t, "account_stored_responses", false, cfg.Metrics.Disabled.AccountStoredResponses)
cmpBools(t, "adapter_connections_metrics", true, cfg.Metrics.Disabled.AdapterConnectionMetrics)
cmpBools(t, "adapter_buyeruid_scrubbed", false, cfg.Metrics.Disabled.AdapterBuyerUIDScrubbed)
cmpBools(t, "adapter_gdpr_request_blocked", true, cfg.Metrics.Disabled.AdapterGDPRRequestBlocked)
cmpStrings(t, "certificates_file", "/etc/ssl/cert.pem", cfg.PemCertsFile)
cmpStrings(t, "request_validation.ipv4_private_networks", "1.1.1.0/24", cfg.RequestValidation.IPv4PrivateNetworks[0])
Expand Down
9 changes: 8 additions & 1 deletion exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,20 +188,27 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
}

passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName, privacy.NewRequestFromBidRequest(*req))
buyerUIDSet := reqWrapper.User != nil && reqWrapper.User.BuyerUID != ""
buyerUIDRemoved := false
if !passIDActivityAllowed {
//UFPD
privacy.ScrubUserFPD(reqWrapper)
buyerUIDRemoved = true
} else {
// run existing policies (GDPR, CCPA, COPPA, LMT)
// potentially block passing IDs based on GDPR
if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassID) {
privacy.ScrubGdprID(reqWrapper)
buyerUIDRemoved = true
}
// potentially block passing IDs based on CCPA
if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) {
privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
buyerUIDRemoved = true
}
}
if buyerUIDSet && buyerUIDRemoved {
rs.me.RecordAdapterBuyerUIDScrubbed(bidderRequest.BidderCoreName)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This works. Alternatively, we could track a second bool buyerIDRemoved which is set true alongside calls to any of the above privacy.Scrub calls.


passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scopedName, privacy.NewRequestFromBidRequest(*req))
if !passGeoActivityAllowed {
Expand Down
26 changes: 23 additions & 3 deletions exchange/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1015,10 +1015,13 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
},
}.Builder

metricsMock := metrics.MetricsEngineMock{}
metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return()

bidderToSyncerKey := map[string]string{}
reqSplitter := &requestSplitter{
bidderToSyncerKey: bidderToSyncerKey,
me: &metrics.MetricsEngineMock{},
me: &metricsMock,
privacyConfig: privacyConfig,
gdprPermsBuilder: gdprPermissionsBuilder,
hostSChainNode: nil,
Expand All @@ -1032,9 +1035,11 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
if test.expectDataScrub {
assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
} else {
assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
}
assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
}
Expand Down Expand Up @@ -2141,9 +2146,12 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
gdprDefaultValue = gdpr.SignalNo
}

metricsMock := metrics.MetricsEngineMock{}
metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return()

reqSplitter := &requestSplitter{
bidderToSyncerKey: map[string]string{},
me: &metrics.MetricsEngineMock{},
me: &metricsMock,
privacyConfig: privacyConfig,
gdprPermsBuilder: gdprPermissionsBuilder,
hostSChainNode: nil,
Expand All @@ -2162,9 +2170,11 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
if test.gdprScrub {
assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
} else {
assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
}
assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
}
Expand Down Expand Up @@ -4522,6 +4532,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
allow bool
expectedReqNumber int
expectedUser openrtb2.User
expectUserScrub bool
expectedDevice openrtb2.Device
expectedSource openrtb2.Source
expectedImpExt json.RawMessage
Expand Down Expand Up @@ -4569,6 +4580,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
Ext: json.RawMessage(`{"test":2}`),
Data: nil,
},
expectUserScrub: true,
expectedDevice: openrtb2.Device{
UA: deviceUA,
Language: "EN",
Expand Down Expand Up @@ -4668,10 +4680,13 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
}},
}

metricsMock := metrics.MetricsEngineMock{}
metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return()

bidderToSyncerKey := map[string]string{}
reqSplitter := &requestSplitter{
bidderToSyncerKey: bidderToSyncerKey,
me: &metrics.MetricsEngineMock{},
me: &metricsMock,
hostSChainNode: nil,
bidderInfo: config.BidderInfos{},
}
Expand All @@ -4688,6 +4703,11 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
if len(test.expectedImpExt) > 0 {
assert.JSONEq(t, string(test.expectedImpExt), string(bidderRequests[0].BidRequest.Imp[0].Ext))
}
if test.expectUserScrub {
metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
} else {
metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
}
}
})
}
Expand Down
11 changes: 11 additions & 0 deletions metrics/config/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ func (me *MultiMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels
}
}

// RecordAdapterBuyerUIDScrubbed across all engines
func (me *MultiMetricsEngine) RecordAdapterBuyerUIDScrubbed(adapter openrtb_ext.BidderName) {
for _, thisME := range *me {
thisME.RecordAdapterBuyerUIDScrubbed(adapter)
}
}

// RecordAdapterGDPRRequestBlocked across all engines
func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) {
for _, thisME := range *me {
Expand Down Expand Up @@ -484,6 +491,10 @@ func (me *NilMetricsEngine) RecordTimeoutNotice(success bool) {
func (me *NilMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels) {
}

// RecordAdapterBuyerUIDScrubbed as a noop
func (me *NilMetricsEngine) RecordAdapterBuyerUIDScrubbed(adapter openrtb_ext.BidderName) {
}

// RecordAdapterGDPRRequestBlocked as a noop
func (me *NilMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) {
}
Expand Down
2 changes: 2 additions & 0 deletions metrics/config/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func TestMultiMetricsEngine(t *testing.T) {
metricsEngine.RecordStoredImpCacheResult(metrics.CacheHit, 5)
metricsEngine.RecordAccountCacheResult(metrics.CacheHit, 6)

metricsEngine.RecordAdapterBuyerUIDScrubbed(openrtb_ext.BidderAppnexus)
metricsEngine.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus)

metricsEngine.RecordRequestQueueTime(false, metrics.ReqTypeVideo, time.Duration(1))
Expand Down Expand Up @@ -188,6 +189,7 @@ func TestMultiMetricsEngine(t *testing.T) {
VerifyMetrics(t, "StoredImpCache.Hit", goEngine.StoredImpCacheMeter[metrics.CacheHit].Count(), 5)
VerifyMetrics(t, "AccountCache.Hit", goEngine.AccountCacheMeter[metrics.CacheHit].Count(), 6)

VerifyMetrics(t, "AdapterMetrics.appNexus.BuyerUIDScrubbed", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].BuyerUIDScrubbed.Count(), 1)
VerifyMetrics(t, "AdapterMetrics.appNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].GDPRRequestBlocked.Count(), 1)

// verify that each module has its own metric recorded
Expand Down
20 changes: 20 additions & 0 deletions metrics/go_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type AdapterMetrics struct {
ConnCreated metrics.Counter
ConnReused metrics.Counter
ConnWaitTime metrics.Timer
BuyerUIDScrubbed metrics.Meter
GDPRRequestBlocked metrics.Meter

BidValidationCreativeSizeErrorMeter metrics.Meter
Expand Down Expand Up @@ -406,6 +407,9 @@ func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMet
newAdapter.ConnReused = metrics.NilCounter{}
newAdapter.ConnWaitTime = &metrics.NilTimer{}
}
if !disabledMetrics.AdapterBuyerUIDScrubbed {
newAdapter.BuyerUIDScrubbed = blankMeter
}
if !disabledMetrics.AdapterGDPRRequestBlocked {
newAdapter.GDPRRequestBlocked = blankMeter
}
Expand Down Expand Up @@ -484,6 +488,7 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string,
am.BidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.bids_received", adapterOrAccount, exchange), registry)
}
am.PanicMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.requests.panic", adapterOrAccount, exchange), registry)
am.BuyerUIDScrubbed = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.buyeruid_scrubbed", adapterOrAccount, exchange), registry)
am.GDPRRequestBlocked = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.gdpr_request_blocked", adapterOrAccount, exchange), registry)

am.BidValidationCreativeSizeErrorMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.response.validation.size.err", adapterOrAccount, exchange), registry)
Expand Down Expand Up @@ -926,6 +931,21 @@ func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) {
}
}

func (me *Metrics) RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName) {
adapterStr := adapterName.String()
if me.MetricsDisabled.AdapterBuyerUIDScrubbed {
return
}

am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)]
if !ok {
glog.Errorf("Trying to log adapter buyeruid scrubbed metric for %s: adapter not found", adapterStr)
return
}

am.BuyerUIDScrubbed.Mark(1)
}

func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) {
adapterStr := string(adapterName)
if me.MetricsDisabled.AdapterGDPRRequestBlocked {
Expand Down
60 changes: 52 additions & 8 deletions metrics/go_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -790,43 +790,87 @@ func TestRecordRequestPrivacy(t *testing.T) {
assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2")
}

func TestRecordAdapterBuyerUIDScrubbed(t *testing.T) {
var fakeBidder openrtb_ext.BidderName = "fooAdvertising"
adapter := "AnyName"
lowerCaseAdapterName := "anyname"

tests := []struct {
name string
metricsDisabled bool
adapterName openrtb_ext.BidderName
expectedCount int64
}{
{
name: "enabled_bidder_found",
metricsDisabled: false,
adapterName: openrtb_ext.BidderName(adapter),
expectedCount: 1,
},
{
name: "enabled_bidder_not_found",
metricsDisabled: false,
adapterName: fakeBidder,
expectedCount: 0,
},
{
name: "disabled",
metricsDisabled: true,
adapterName: openrtb_ext.BidderName(adapter),
expectedCount: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
registry := metrics.NewRegistry()
m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterBuyerUIDScrubbed: tt.metricsDisabled}, nil, nil)

m.RecordAdapterBuyerUIDScrubbed(tt.adapterName)

assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].BuyerUIDScrubbed.Count())
})
}
}

func TestRecordAdapterGDPRRequestBlocked(t *testing.T) {
var fakeBidder openrtb_ext.BidderName = "fooAdvertising"
adapter := "AnyName"
lowerCaseAdapterName := "anyname"

tests := []struct {
description string
name string
metricsDisabled bool
adapterName openrtb_ext.BidderName
expectedCount int64
}{
{
description: "",
name: "enabled_bidder_found",
metricsDisabled: false,
adapterName: openrtb_ext.BidderName(adapter),
expectedCount: 1,
},
{
description: "",
name: "enabled_bidder_not_found",
metricsDisabled: false,
adapterName: fakeBidder,
expectedCount: 0,
},
{
description: "",
name: "disabled",
metricsDisabled: true,
adapterName: openrtb_ext.BidderName(adapter),
expectedCount: 0,
},
}
for _, tt := range tests {
registry := metrics.NewRegistry()
m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil)
t.Run(tt.name, func(t *testing.T) {
registry := metrics.NewRegistry()
m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil)

m.RecordAdapterGDPRRequestBlocked(tt.adapterName)
m.RecordAdapterGDPRRequestBlocked(tt.adapterName)

assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].GDPRRequestBlocked.Count(), tt.description)
assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].GDPRRequestBlocked.Count())
})
}
}

Expand Down
1 change: 1 addition & 0 deletions metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ type MetricsEngine interface {
RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
RecordTimeoutNotice(success bool)
RecordRequestPrivacy(privacy PrivacyLabels)
RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName)
RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName)
RecordDebugRequest(debugEnabled bool, pubId string)
RecordStoredResponse(pubId string)
Expand Down
5 changes: 5 additions & 0 deletions metrics/metrics_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) {
me.Called(privacy)
}

// RecordAdapterBuyerUIDScrubbed mock
func (me *MetricsEngineMock) RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName) {
me.Called(adapterName)
}

// RecordAdapterGDPRRequestBlocked mock
func (me *MetricsEngineMock) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) {
me.Called(adapterName)
Expand Down
6 changes: 6 additions & 0 deletions metrics/prometheus/preload.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[st
versionLabel: tcfVersionValues,
})

if !m.metricsDisabled.AdapterBuyerUIDScrubbed {
preloadLabelValuesForCounter(m.adapterScrubbedBuyerUIDs, map[string][]string{
adapterLabel: adapterValues,
})
}

if !m.metricsDisabled.AdapterGDPRRequestBlocked {
preloadLabelValuesForCounter(m.adapterGDPRBlockedRequests, map[string][]string{
adapterLabel: adapterValues,
Expand Down
Loading
Loading