Skip to content

Commit

Permalink
Metrics: Add per adapter metric indicating when buyeruid was scrubbed (
Browse files Browse the repository at this point in the history
  • Loading branch information
bsardo committed Apr 16, 2024
1 parent 5bfa34d commit 7635519
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 13 deletions.
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"`

// 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 @@ -945,6 +948,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 @@ -158,6 +158,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 @@ -445,6 +446,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 @@ -858,6 +860,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 @@ -200,20 +200,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)
}

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 @@ -4523,6 +4533,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 @@ -4570,6 +4581,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 @@ -4670,10 +4682,13 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
}

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 @@ -4690,6 +4705,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

0 comments on commit 7635519

Please sign in to comment.