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

Correct GetCpmStringValue's second return value #1520

Merged
merged 9 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 1 addition & 4 deletions endpoints/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,7 @@ func sortBidsAddKeywordsMobile(bids pbs.PBSBidSlice, pbs_req *pbs.PBSRequest, pr
// after sorting we need to add the ad targeting keywords
for i, bid := range bar {
// We should eventually check for the error and do something.
roundedCpm, err := exchange.GetCpmStringValue(bid.Price, openrtb_ext.PriceGranularityFromString(priceGranularitySetting))
if err != nil {
glog.Error(err.Error())
}
roundedCpm := exchange.GetPriceBucket(bid.Price, openrtb_ext.PriceGranularityFromString(priceGranularitySetting))

hbSize := ""
if bid.Width != 0 && bid.Height != 0 {
Expand Down
7 changes: 1 addition & 6 deletions exchange/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"time"

uuid "github.com/gofrs/uuid"
"github.com/golang/glog"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/openrtb_ext"
Expand Down Expand Up @@ -129,11 +128,7 @@ func (a *auction) setRoundedPrices(priceGranularity openrtb_ext.PriceGranularity
roundedPrices := make(map[*pbsOrtbBid]string, 5*len(a.winningBids))
for _, topBidsPerImp := range a.winningBidsByBidder {
for _, topBidPerBidder := range topBidsPerImp {
roundedPrice, err := GetCpmStringValue(topBidPerBidder.bid.Price, priceGranularity)
if err != nil {
glog.Errorf(`Error rounding price according to granularity. This shouldn't happen unless /openrtb2 input validation is buggy. Granularity was "%v".`, priceGranularity)
}
roundedPrices[topBidPerBidder] = roundedPrice
roundedPrices[topBidPerBidder] = GetPriceBucket(topBidPerBidder.bid.Price, priceGranularity)
}
}
a.roundedPrices = roundedPrices
Expand Down
2 changes: 1 addition & 1 deletion exchange/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques

// TODO: consider should we remove bids with zero duration here?

pb, _ = GetCpmStringValue(bid.bid.Price, targData.priceGranularity)
pb = GetPriceBucket(bid.bid.Price, targData.priceGranularity)

newDur := duration
if len(requestExt.Prebid.Targeting.DurationRangeSec) > 0 {
Expand Down
25 changes: 12 additions & 13 deletions exchange/price_granularity.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,32 @@ import (
"github.com/prebid/prebid-server/openrtb_ext"
)

// DEFAULT_PRECISION should be taken care of in openrtb_ext/request.go, but throwing an additional safety check here.

// GetCpmStringValue is the externally facing function for computing CPM buckets
func GetCpmStringValue(cpm float64, config openrtb_ext.PriceGranularity) (string, error) {
// GetPriceBucket is the externally facing function for computing CPM buckets
func GetPriceBucket(cpm float64, config openrtb_ext.PriceGranularity) string {
cpmStr := ""
bucketMax := 0.0
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
increment := 0.0
precision := config.Precision
// calculate max of highest bucket

for i := 0; i < len(config.Ranges); i++ {
if config.Ranges[i].Max > bucketMax {
bucketMax = config.Ranges[i].Max
}
} // calculate which bucket cpm is in
if cpm > bucketMax {
// If we are over max, just return that
return strconv.FormatFloat(bucketMax, 'f', precision, 64), nil
}
for i := 0; i < len(config.Ranges); i++ {
// find what range cpm is in
if cpm >= config.Ranges[i].Min && cpm <= config.Ranges[i].Max {
increment = config.Ranges[i].Increment
}
}
if increment > 0 {

if cpm > bucketMax {
// We are over max, just return that
cpmStr = strconv.FormatFloat(bucketMax, 'f', precision, 64)
} else if increment > 0 {
// If increment exists, get cpm string value
cpmStr = getCpmTarget(cpm, increment, precision)
}
return cpmStr, nil

return cpmStr
}

func getCpmTarget(cpm float64, increment float64, precision int) string {
Expand Down
126 changes: 101 additions & 25 deletions exchange/price_granularity_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package exchange

import (
"math"
"strings"
"testing"

"github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
)

func TestGetPriceBucketString(t *testing.T) {
Expand All @@ -28,32 +31,105 @@ func TestGetPriceBucketString(t *testing.T) {
},
}

price := 1.87
getOnePriceBucket(t, "low", low, price, "1.50")
getOnePriceBucket(t, "medium", medium, price, "1.80")
getOnePriceBucket(t, "high", high, price, "1.87")
getOnePriceBucket(t, "auto", auto, price, "1.85")
getOnePriceBucket(t, "dense", dense, price, "1.87")
getOnePriceBucket(t, "custom1", custom1, price, "1.86")

// test a cpm above the max in low price bucket
price = 5.72
getOnePriceBucket(t, "low", low, price, "5.00")
getOnePriceBucket(t, "medium", medium, price, "5.70")
getOnePriceBucket(t, "high", high, price, "5.72")
getOnePriceBucket(t, "auto", auto, price, "5.70")
getOnePriceBucket(t, "dense", dense, price, "5.70")
getOnePriceBucket(t, "custom1", custom1, price, "5.70")
// Define test cases
type aTest struct {
granularityId string
granularity openrtb_ext.PriceGranularity
expectedPriceBucket string
}
testGroups := []struct {
groupDesc string
cpm float64
testCases []aTest
}{
{
groupDesc: "cpm below the max in every price bucket",
cpm: 1.87,
testCases: []aTest{
{"low", low, "1.50"},
{"medium", medium, "1.80"},
{"high", high, "1.87"},
{"auto", auto, "1.85"},
{"dense", dense, "1.87"},
{"custom1", custom1, "1.86"},
},
},
{
groupDesc: "cpm above the max in low price bucket",
cpm: 5.72,
testCases: []aTest{
{"low", low, "5.00"},
{"medium", medium, "5.70"},
{"high", high, "5.72"},
{"auto", auto, "5.70"},
{"dense", dense, "5.70"},
{"custom1", custom1, "5.70"},
},
},
{
groupDesc: "Precision value corner cases",
cpm: 1.876,
testCases: []aTest{
{
"Negative precision defaults to number of digits already in CPM float",
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

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

Where is the code that deals with a negative precision?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Function strconv.FormatFloat(roundedCPM, 'f', precision, 64) deals with negative precision values. More details in the function definition itself.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Are you testing that strconv.FormatFloat() is working properly, or that something is handling the case properly when FormatFloat() does what it is supposed to do, or that your code is subsequently handling that output correctly? I don't see any code that would react to anything particular about a negative number.

Copy link
Contributor

Choose a reason for hiding this comment

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

if pgraw.Precision < 0 {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Given that PriceGranularity has its own func (pg *PriceGranularity) UnmarshalJSON(b []byte) error inside openrtb_ext/request.go that sets a bunch of default values if found missing in the Json string, I thought it'd be better to cap the precision value there instead.

openrtb_ext.PriceGranularity{Precision: -1, Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: 0.05}}},
"1.85",
},
{
"Precision value equals zero, we expect to round up to the nearest integer",
openrtb_ext.PriceGranularity{Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: 0.05}}},
"2",
},
{
"Largest precision value PBS supports (2^15 = 32768)",
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to change this to 15 from 32768 to match the other change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected

openrtb_ext.PriceGranularity{Precision: int(math.Pow(2, 15)), Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: 0.05}}},
"1.85000000000000008881784",
},
},
},
{
groupDesc: "Increment value corner cases",
cpm: 1.876,
testCases: []aTest{
{
"Negative increment, return empty string",
openrtb_ext.PriceGranularity{Precision: 2, Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: -0.05}}},
"",
},
{
"Zero increment, return empty string",
openrtb_ext.PriceGranularity{Precision: 2, Ranges: []openrtb_ext.GranularityRange{{Max: 5}}},
"",
},
{
"Increment value is greater than CPM itself, return zero float value",
openrtb_ext.PriceGranularity{Precision: 2, Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: 1.877}}},
"0.00",
},
},
},
{
groupDesc: "Negative Cpm, return empty string since it does not belong into any range",
cpm: -1.876,
testCases: []aTest{{"low", low, ""}},
},
{
groupDesc: "Zero value Cpm, return the same, only in string format",
cpm: 0,
testCases: []aTest{{"low", low, "0.00"}},
},
{
groupDesc: "Large Cpm, return bucket Max",
cpm: math.MaxFloat64,
testCases: []aTest{{"low", low, "5.00"}},
},
}

}
for _, testGroup := range testGroups {
for _, test := range testGroup.testCases {
priceBucket := GetPriceBucket(testGroup.cpm, test.granularity)

func getOnePriceBucket(t *testing.T, name string, granularity openrtb_ext.PriceGranularity, price float64, expected string) {
t.Helper()
priceBucket, err := GetCpmStringValue(price, granularity)
if err != nil {
t.Errorf("Granularity: %s :: GetPriceBucketString: %s", name, err.Error())
}
if priceBucket != expected {
t.Errorf("Granularity: %s :: Expected %s, got %s from %f", name, expected, priceBucket, price)
assert.True(t, strings.HasPrefix(priceBucket, test.expectedPriceBucket), "Group: %s Granularity: %s :: Expected %s, got %s from %f", testGroup.groupDesc, test.granularityId, test.expectedPriceBucket, priceBucket, testGroup.cpm)
}
}
}
3 changes: 3 additions & 0 deletions openrtb_ext/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
// FirstPartyDataContextExtKey defines the field name within bidrequest.ext reserved
// for first party data support.
const FirstPartyDataContextExtKey string = "context"
const MaxDecimalFigures int = 32768 // math.Pow(2, 15)
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's just change this to 15 from 32768. Per wikipedia on the significant digits of a double:

The 53-bit significand [sic] precision gives from 15 to 17 significant decimal digits precision


// ExtRequest defines the contract for bidrequest.ext
type ExtRequest struct {
Expand Down Expand Up @@ -177,6 +178,8 @@ func (pg *PriceGranularity) UnmarshalJSON(b []byte) error {
}
if pgraw.Precision < 0 {
return errors.New("Price granularity error: precision must be non-negative")
} else if pgraw.Precision > MaxDecimalFigures {
pgraw.Precision = MaxDecimalFigures
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to return an error here? .. or keep with the auto max size.

}
if len(pgraw.Ranges) > 0 {
var prevMax float64 = 0
Expand Down