RTBKit
0.9
Open-source framework to create real-time ad bidding systems.
|
00001 /* rtb_agent_config.cc 00002 Jeremy Barnes, 24 March 2011 00003 Copyright (c) 2011 Datacratic. All rights reserved. 00004 00005 */ 00006 00007 #include "agent_config.h" 00008 #include "jml/arch/exception.h" 00009 #include "jml/utils/string_functions.h" 00010 #include <boost/lexical_cast.hpp> 00011 #include "rtbkit/common/auction.h" 00012 #include "rtbkit/core/router/router_types.h" 00013 #include "rtbkit/common/exchange_connector.h" 00014 00015 #define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 00016 #include "crypto++/md5.h" 00017 00018 00019 using namespace std; 00020 using namespace ML; 00021 00022 namespace RTBKIT { 00023 00024 00025 /*****************************************************************************/ 00026 /* CREATIVE */ 00027 /*****************************************************************************/ 00028 00029 Creative:: 00030 Creative(int width, int height, 00031 std::string name, int id, int tagId) 00032 : format(width,height), tagId(tagId), name(name), id(id) 00033 { 00034 } 00035 00036 void 00037 Creative:: 00038 fromJson(const Json::Value & val) 00039 { 00040 if (val.isMember("format")) { 00041 format.fromJson(val["format"]); 00042 } 00043 else { 00044 format.width = val["width"].asInt(); 00045 format.height = val["height"].asInt(); 00046 } 00047 name = val["name"].asString(); 00048 00049 id = -1; 00050 if (val.isMember("id")) 00051 id = val["id"].asInt(); 00052 if (id == -1) 00053 throw ML::Exception("creatives require an ID to be specified"); 00054 00055 if (val.isMember("tagId")) 00056 tagId = val["tagId"].asInt(); 00057 else tagId = -1; 00058 00059 providerConfig = val["providerConfig"]; 00060 00061 if (tagId == -1) 00062 throw Exception("no tag ID in creative: " + val.toString()); 00063 00064 languageFilter.fromJson(val["languageFilter"], "languageFilter"); 00065 locationFilter.fromJson(val["locationFilter"], "locationFilter"); 00066 exchangeFilter.fromJson(val["exchangeFilter"], "exchangeFilter"); 00067 } 00068 00069 Json::Value 00070 Creative:: 00071 toJson() const 00072 { 00073 Json::Value result; 00074 result["format"] = format.toJson(); 00075 result["name"] = name; 00076 if (id != -1) 00077 result["id"] = id; 00078 if (tagId != -1) 00079 result["tagId"] = tagId; 00080 if (!languageFilter.empty()) 00081 result["languageFilter"] = languageFilter.toJson(); 00082 if (!locationFilter.empty()) 00083 result["locationFilter"] = locationFilter.toJson(); 00084 if (!exchangeFilter.empty()) 00085 result["exchangeFilter"] = exchangeFilter.toJson(); 00086 if (!providerConfig.isNull()) 00087 result["providerConfig"] = providerConfig; 00088 return result; 00089 } 00090 00091 const Creative Creative::sampleWS 00092 (160, 600, "LeaderBoard", 0, 0); 00093 const Creative Creative::sampleBB 00094 (300, 250, "BigBox", 1, 1); 00095 const Creative Creative::sampleLB 00096 (728, 90, "LeaderBoard", 2, 2); 00097 00098 bool 00099 Creative:: 00100 compatible(const AdSpot & adspot) const 00101 { 00102 return adspot.formats.compatible(format); 00103 } 00104 00105 bool 00106 Creative:: 00107 biddable(const std::string & exchange, 00108 const std::string & protocolVersion) const 00109 { 00110 return true; 00111 } 00112 00113 Json::Value jsonPrint(const Creative & c) 00114 { 00115 return c.toJson(); 00116 } 00117 00118 00119 /*****************************************************************************/ 00120 /* USER PARTITION */ 00121 /*****************************************************************************/ 00122 00123 UserPartition:: 00124 UserPartition() 00125 : hashOn(NONE), 00126 modulus(1), 00127 includeRanges(1, Interval(0, 1)) 00128 { 00129 } 00130 00131 void 00132 UserPartition:: 00133 swap(UserPartition & other) 00134 { 00135 std::swap(hashOn, other.hashOn); 00136 std::swap(modulus, other.modulus); 00137 includeRanges.swap(other.includeRanges); 00138 } 00139 00140 void 00141 UserPartition:: 00142 clear() 00143 { 00144 hashOn = NONE; 00145 modulus = 1; 00146 includeRanges.clear(); 00147 } 00148 00149 uint64_t calcMd5(const std::string & str) 00150 { 00151 CryptoPP::Weak::MD5 md5; 00152 00153 union { 00154 uint64_t result; 00155 byte bytes[sizeof(uint64_t)]; 00156 }; 00157 00158 md5.CalculateTruncatedDigest(bytes, sizeof(uint64_t), 00159 (const byte *)str.c_str(), str.size()); 00160 00161 return result; 00162 } 00163 00164 bool 00165 UserPartition:: 00166 matches(const UserIds & ids) const 00167 { 00168 if (hashOn == NONE) 00169 return true; 00170 00171 //cerr << "matches: hashOn " << hashOn << " modulus " 00172 // << modulus << endl; 00173 00174 uint32_t value; 00175 if (hashOn == NONE) 00176 value = 0; 00177 else if (hashOn == RANDOM) 00178 value = random(); 00179 else { 00180 Id id; 00181 00182 switch (hashOn) { 00183 case EXCHANGEID: id = ids.exchangeId; break; 00184 case PROVIDERID: id = ids.providerId; break; 00185 default: 00186 throw Exception("unknown hashOn"); 00187 }; 00188 00189 if (!id) return false; 00190 00191 // TODO: change this to a better hash once we get the chance 00192 // (clean break in campaigns) 00193 value = calcMd5(id.toString()); 00194 00195 //cerr << "s = " << s << " value = " << value << endl; 00196 } 00197 00198 value %= modulus; 00199 00200 //cerr << "mod = " << value << endl; 00201 00202 for (unsigned i = 0; i < includeRanges.size(); ++i) { 00203 //cerr << "checking in range " << includeRanges[i].first 00204 // << "-" << includeRanges[i].last << endl; 00205 if (includeRanges[i].in(value)) return true; 00206 //cerr << " ... not in" << endl; 00207 } 00208 00209 //cerr << "not in anything" << endl; 00210 00211 return false; 00212 } 00213 00214 Json::Value 00215 UserPartition::Interval:: 00216 toJson() const 00217 { 00218 Json::Value result; 00219 result[0u] = first; 00220 result[1] = last; 00221 return result; 00222 } 00223 00224 void 00225 UserPartition:: 00226 fromJson(const Json::Value & json) 00227 { 00228 UserPartition newPartition; 00229 newPartition.clear(); 00230 00231 for (auto it = json.begin(), end = json.end(); it != end; ++it) { 00232 if (it.memberName() == "hashOn") { 00233 string name = it->asString(); 00234 if (name == "null") newPartition.hashOn = NONE; 00235 else if (name == "random") newPartition.hashOn = RANDOM; 00236 else if (name == "exchangeId") newPartition.hashOn = EXCHANGEID; 00237 else if (name == "providerId") newPartition.hashOn = PROVIDERID; 00238 else throw Exception("unknown hashOn value %s", name.c_str()); 00239 } 00240 else if (it.memberName() == "modulus") { 00241 newPartition.modulus = it->asInt(); 00242 } 00243 else if (it.memberName() == "includeRanges") { 00244 const Json::Value & arr = *it; 00245 for (unsigned i = 0; i < arr.size(); ++i) { 00246 const Json::Value & ival = arr[i]; 00247 if (ival.size() != 2) 00248 throw Exception("bad interval"); 00249 int first = ival[0u].asInt(); 00250 int last = ival[1].asInt(); 00251 newPartition.includeRanges.push_back(Interval(first, last)); 00252 } 00253 } 00254 else throw Exception("unknown user partition option: %s", 00255 it.memberName().c_str()); 00256 } 00257 00258 swap(newPartition); 00259 } 00260 00261 Json::Value 00262 UserPartition:: 00263 toJson() const 00264 { 00265 Json::Value result; 00266 string ho; 00267 switch (hashOn) { 00268 case NONE: ho = "null"; break; 00269 case RANDOM: ho = "random"; break; 00270 case EXCHANGEID: ho = "exchangeId"; break; 00271 case PROVIDERID: ho = "providerId"; break; 00272 default: 00273 throw ML::Exception("unknown hashOn"); 00274 } 00275 result["hashOn"] = ho; 00276 result["modulus"] = modulus; 00277 for (unsigned i = 0; i < includeRanges.size(); ++i) 00278 result["includeRanges"][i] = includeRanges[i].toJson(); 00279 00280 return result; 00281 } 00282 00283 00284 /*****************************************************************************/ 00285 /* AGENT CONFIG */ 00286 /*****************************************************************************/ 00287 00288 AgentConfig:: 00289 AgentConfig() 00290 : test(false), roundRobinWeight(0), 00291 bidProbability(1.0), minTimeAvailableMs(5.0), 00292 maxInFlight(100), 00293 blacklistType(BL_OFF), 00294 blacklistScope(BL_ACCOUNT), blacklistTime(15.0), 00295 bidControlType(BC_RELAY), fixedBidCpmInMicros(0), 00296 winFormat(BRF_FULL), 00297 lossFormat(BRF_LIGHTWEIGHT), 00298 errorFormat(BRF_LIGHTWEIGHT) 00299 { 00300 addAugmentation("random"); 00301 } 00302 00303 void 00304 AgentConfig:: 00305 parse(const std::string & jsonStr) 00306 { 00307 Json::Value val = Json::parse(jsonStr); 00308 fromJson(val); 00309 } 00310 00311 void 00312 AgentConfig::SegmentInfo:: 00313 fromJson(const Json::Value & json) 00314 { 00315 for (auto it = json.begin(), end = json.end(); it != end; ++it) { 00316 if (it.memberName() == "excludeIfNotPresent") { 00317 excludeIfNotPresent = it->asBool(); 00318 continue; 00319 } 00320 00321 const Json::Value & val = *it; 00322 00323 if (it.memberName() == "include") { 00324 include = SegmentList::createFromJson(val); 00325 include.sort(); 00326 } 00327 else if (it.memberName() == "exclude") { 00328 exclude = SegmentList::createFromJson(val); 00329 exclude.sort(); 00330 } 00331 else if (it.memberName() == "applyToExchanges") 00332 applyToExchanges.fromJson(val, "segmentFilter applyToExchanges"); 00333 else { 00334 throw Exception("segmentFilter has invalid key: %s", 00335 it.memberName().c_str()); 00336 } 00337 } 00338 } 00339 00340 IncludeExcludeResult 00341 AgentConfig::SegmentInfo:: 00342 process(const SegmentList & segments) const 00343 { 00344 if (segments.empty()) 00345 return IE_NO_DATA; 00346 00347 if (!include.empty() && !include.match(segments)) 00348 return IE_NOT_INCLUDED; 00349 00350 if (exclude.match(segments)) 00351 return IE_EXCLUDED; 00352 00353 return IE_PASSED; 00354 } 00355 00356 AgentConfig::HourOfWeekFilter:: 00357 HourOfWeekFilter() 00358 { 00359 for (unsigned i = 0; i < 168; ++i) 00360 hourBitmap.set(i); 00361 } 00362 00363 bool 00364 AgentConfig::HourOfWeekFilter:: 00365 isDefault() const 00366 { 00367 return hourBitmap.all(); 00368 } 00369 00370 bool 00371 AgentConfig::HourOfWeekFilter:: 00372 isIncluded(Date date) const 00373 { 00374 if (isDefault()) 00375 return true; 00376 if (date == Date()) 00377 throw ML::Exception("null auction date with hour of week filter on"); 00378 int hour = date.hourOfWeek(); 00379 return hourBitmap[hour]; 00380 } 00381 00382 void 00383 AgentConfig::HourOfWeekFilter:: 00384 fromJson(const Json::Value & val) 00385 { 00386 string s = val["hourlyBitmapSundayMidnightUtc"].asString(); 00387 if (s.length() != 168) 00388 throw ML::Exception("Hourly bitmap string needs 168 characters"); 00389 for (unsigned i = 0; i < 168; ++i) { 00390 if (s[i] == '0') 00391 hourBitmap[i] = 0; 00392 else if (s[i] == '1') 00393 hourBitmap[i] = 1; 00394 else throw ML::Exception("Hourly bitmap must contain only 0 or 1 " 00395 "characters"); 00396 } 00397 } 00398 00399 Json::Value 00400 AgentConfig::HourOfWeekFilter:: 00401 toJson() const 00402 { 00403 string bitmap; 00404 for (unsigned i = 0; i < 168; ++i) 00405 bitmap += '0' + int(hourBitmap[i] != 0); 00406 Json::Value result; 00407 result["hourlyBitmapSundayMidnightUtc"] = bitmap; 00408 return result; 00409 } 00410 00411 Json::Value toJson(BidResultFormat fmt) 00412 { 00413 switch (fmt) { 00414 case BRF_FULL: return "full"; 00415 case BRF_LIGHTWEIGHT: return "lightweight"; 00416 case BRF_NONE: return "none"; 00417 default: 00418 throw ML::Exception("unknown BidResultFormat"); 00419 } 00420 } 00421 00422 void fromJson(BidResultFormat & fmt, const Json::Value & j) 00423 { 00424 string s = lowercase(j.asString()); 00425 if (s == "full") 00426 fmt = BRF_FULL; 00427 else if (s == "lightweight") 00428 fmt = BRF_LIGHTWEIGHT; 00429 else if (s == "none") 00430 fmt = BRF_NONE; 00431 else throw ML::Exception("unknown BidResultFormat " + s + ": accepted " 00432 "full, lightweight, none"); 00433 } 00434 00435 void 00436 AgentConfig:: 00437 fromJson(const Json::Value & json) 00438 { 00439 *this = createFromJson(json); 00440 } 00441 00442 AgentConfig 00443 AgentConfig:: 00444 createFromJson(const Json::Value & json) 00445 { 00446 AgentConfig newConfig; 00447 newConfig.augmentations.clear(); 00448 00449 for (auto it = json.begin(), end = json.end(); it != end; ++it) { 00450 //cerr << "parsing " << it.memberName() << " with value " << *it << endl; 00451 00452 if (it.memberName() == "account") 00453 newConfig.account = AccountKey::fromJson(*it); 00454 else if (it.memberName() == "test") { 00455 newConfig.test = it->asBool(); 00456 } 00457 else if (it.memberName() == "requiredIds") { 00458 if (!it->isArray()) 00459 throw Exception("requiredIds must be an array of string"); 00460 for (unsigned i = 0; i < it->size(); ++i) { 00461 const Json::Value & val = (*it)[i]; 00462 newConfig.requiredIds.push_back(val.asString()); 00463 } 00464 } 00465 else if (it.memberName() == "roundRobin") { 00466 for (auto jt = it->begin(), jend = it->end(); 00467 jt != jend; ++jt) { 00468 if (jt.memberName() == "group") 00469 newConfig.roundRobinGroup = jt->asString(); 00470 else if (jt.memberName() == "weight") 00471 newConfig.roundRobinWeight = jt->asInt(); 00472 else throw Exception("roundRobin group had unknown key " 00473 + jt.memberName()); 00474 } 00475 } 00476 else if (it.memberName() == "creatives") { 00477 //cerr << "doing " << it->size() << " creatives" << endl; 00478 00479 newConfig.creatives.resize(it->size()); 00480 00481 for (unsigned i = 0; 00482 i < newConfig.creatives.size(); ++i) { 00483 try { 00484 newConfig.creatives[i].fromJson((*it)[i]); 00485 if (newConfig.creatives[i].tagId == -1) 00486 throw Exception("invalid tag in creative " 00487 + boost::lexical_cast<std::string>((*it)[i])); 00488 ; 00489 } catch (const std::exception & exc) { 00490 throw Exception("parsing creative %d: %s", 00491 i, exc.what()); 00492 } 00493 } 00494 00495 //cerr << "got " << newConfig.creatives.size() << " creatives" << endl; 00496 } 00497 else if (it.memberName() == "bidProbability") { 00498 newConfig.bidProbability = it->asDouble(); 00499 if (newConfig.bidProbability < 0 || newConfig.bidProbability > 1.0) 00500 throw Exception("bidProbability %f not beween 0 and 1", 00501 newConfig.bidProbability); 00502 } 00503 else if (it.memberName() == "minTimeAvailableMs") { 00504 newConfig.minTimeAvailableMs = it->asDouble(); 00505 if (newConfig.minTimeAvailableMs < 0) 00506 throw Exception("minTimeAvailableMs %f not beween 0 and 1", 00507 newConfig.minTimeAvailableMs); 00508 } 00509 else if (it.memberName() == "maxInFlight") { 00510 newConfig.maxInFlight = it->asInt(); 00511 if (newConfig.maxInFlight < 0) 00512 throw Exception("maxInFlight has wrong value: %d", 00513 newConfig.maxInFlight); 00514 } 00515 else if (it.memberName() == "userPartition") { 00516 newConfig.userPartition.fromJson(*it); 00517 } 00518 else if (it.memberName() == "urlFilter") 00519 newConfig.urlFilter.fromJson(*it, "urlFilter"); 00520 else if (it.memberName() == "hostFilter") 00521 newConfig.hostFilter.fromJson(*it, "hostFilter"); 00522 else if (it.memberName() == "locationFilter") 00523 newConfig.locationFilter.fromJson(*it, "locationFilter"); 00524 else if (it.memberName() == "languageFilter") 00525 newConfig.languageFilter.fromJson(*it, "languageFilter"); 00526 else if (it.memberName() == "exchangeFilter") 00527 newConfig.exchangeFilter.fromJson(*it, "exchangeFilter"); 00528 else if (it.memberName() == "segmentFilter") { 00529 for (auto jt = it->begin(), jend = it->end(); 00530 jt != jend; ++jt) { 00531 string source = jt.memberName(); 00532 newConfig.segments[source].fromJson(*jt); 00533 } 00534 } 00535 else if (it.memberName() == "tagFilter") { 00536 newConfig.tagFilter.fromJson(*it); 00537 } 00538 else if (it.memberName() == "foldPositionFilter") { 00539 newConfig.foldPositionFilter.fromJson(*it, "foldPositionFilter"); 00540 } 00541 else if (it.memberName() == "hourOfWeekFilter") { 00542 newConfig.hourOfWeekFilter.fromJson(*it); 00543 } 00544 else if (it.memberName() == "augmentationFilter") { 00545 newConfig.augmentationFilter.fromJson(*it, "augmentationFilter"); 00546 } 00547 else if (it.memberName() == "augment") { 00548 if (!it->isObject()) 00549 throw Exception("augment must be an object of augmentor name to config"); 00550 00551 for (auto jt = (*it).begin(), end = (*it).end(); jt != end; ++jt) 00552 newConfig.addAugmentation(jt.memberName(), *jt); 00553 } 00554 else if (it.memberName() == "blacklist") { 00555 for (auto jt = it->begin(), jend = it->end(); 00556 jt != jend; ++jt) { 00557 const Json::Value & val = *jt; 00558 if (jt.memberName() == "type") { 00559 if (val.isNull()) 00560 newConfig.blacklistType = BL_OFF; 00561 else { 00562 string s = ML::lowercase(val.asString()); 00563 if (s == "off") 00564 newConfig.blacklistType = BL_OFF; 00565 else if (s == "user") 00566 newConfig.blacklistType = BL_USER; 00567 else if (s == "user_site") 00568 newConfig.blacklistType = BL_USER_SITE; 00569 else throw Exception("invalid blacklist type " + s); 00570 } 00571 } 00572 else if (jt.memberName() == "time") { 00573 newConfig.blacklistTime = val.asDouble(); 00574 } 00575 else if (jt.memberName() == "scope") { 00576 string s = ML::lowercase(val.asString()); 00577 if (s == "agent") 00578 newConfig.blacklistScope = BL_AGENT; 00579 else if (s == "account") 00580 newConfig.blacklistScope = BL_ACCOUNT; 00581 else throw Exception("invalid blacklist scope " + s); 00582 } 00583 else throw Exception("blacklist has invalid key: %s", 00584 jt.memberName().c_str()); 00585 } 00586 } 00587 else if (it.memberName() == "visits") { 00588 for (auto jt = it->begin(), jend = it->end(); 00589 jt != jend; ++jt) { 00590 const Json::Value & val = *jt; 00591 if (jt.memberName() == "channels") { 00592 newConfig.visitChannels = SegmentList::createFromJson(val); 00593 } 00594 else if (jt.memberName() == "includeUnmatched") { 00595 newConfig.includeUnmatchedVisits = val.asBool(); 00596 } 00597 else throw Exception("visits has invalid key: %s", 00598 jt.memberName().c_str()); 00599 } 00600 } 00601 else if (it.memberName() == "bidControl") { 00602 for (auto jt = it->begin(), jend = it->end(); 00603 jt != jend; ++jt) { 00604 const Json::Value & val = *jt; 00605 if (jt.memberName() == "type") { 00606 string s = ML::lowercase(val.asString()); 00607 if (s == "relay") 00608 newConfig.bidControlType = BC_RELAY; 00609 else if (s == "relay_fixed") 00610 newConfig.bidControlType = BC_RELAY_FIXED; 00611 else if (s == "fixed") 00612 newConfig.bidControlType = BC_FIXED; 00613 else throw Exception("invalid bid control value " + s); 00614 } 00615 else if (jt.memberName() == "fixedBidCpmInMicros") { 00616 newConfig.fixedBidCpmInMicros = val.asInt(); 00617 } 00618 else throw Exception("bidControl has invalid key: %s", 00619 jt.memberName().c_str()); 00620 } 00621 } 00622 else if (it.memberName() == "providerConfig") { 00623 newConfig.providerConfig = *it; 00624 } 00625 else if (it.memberName() == "winFormat") { 00626 RTBKIT::fromJson(newConfig.winFormat, *it); 00627 } 00628 else if (it.memberName() == "lossFormat") { 00629 RTBKIT::fromJson(newConfig.lossFormat, *it); 00630 } 00631 else if (it.memberName() == "errorFormat") { 00632 RTBKIT::fromJson(newConfig.errorFormat, *it); 00633 } 00634 else throw Exception("unknown config option: %s", 00635 it.memberName().c_str()); 00636 } 00637 00638 if (newConfig.account.empty()) 00639 throw Exception("each agent must have an account specified"); 00640 00641 if (newConfig.creatives.empty()) 00642 throw Exception("can't configure a agent with no creatives"); 00643 00644 return newConfig; 00645 } 00646 00647 Json::Value 00648 AgentConfig::SegmentInfo:: 00649 toJson() const 00650 { 00651 Json::Value result; 00652 if (!include.empty()) 00653 result["include"] = include.toJson(); 00654 if (!exclude.empty()) 00655 result["exclude"] = exclude.toJson(); 00656 result["excludeIfNotPresent"] = excludeIfNotPresent; 00657 if (!applyToExchanges.empty()) 00658 result["applyToExchanges"] = applyToExchanges.toJson(); 00659 return result; 00660 } 00661 00662 Json::Value 00663 AgentConfig:: 00664 toJson(bool includeCreatives) const 00665 { 00666 Json::Value result; 00667 result["account"] = account.toJson(); 00668 result["test"] = test; 00669 if (roundRobinGroup != "") { 00670 result["roundRobin"]["group"] = roundRobinGroup; 00671 if (roundRobinWeight != 0) 00672 result["roundRobin"]["weight"] = roundRobinWeight; 00673 } 00674 if (bidProbability != 1.0) 00675 result["bidProbability"] = bidProbability; 00676 result["minTimeAvailableMs"] = minTimeAvailableMs; 00677 if (maxInFlight != 100) 00678 result["maxInFlight"] = maxInFlight; 00679 00680 if (!urlFilter.empty()) 00681 result["urlFilter"] = urlFilter.toJson(); 00682 if (!hostFilter.empty()) 00683 result["hostFilter"] = hostFilter.toJson(); 00684 if (!locationFilter.empty()) 00685 result["locationFilter"] = locationFilter.toJson(); 00686 if (!languageFilter.empty()) 00687 result["languageFilter"] = languageFilter.toJson(); 00688 if (!exchangeFilter.empty()) 00689 result["exchangeFilter"] = exchangeFilter.toJson(); 00690 if (!augmentationFilter.empty()) 00691 result["augmentationFilter"] = augmentationFilter.toJson(); 00692 if (!requiredIds.empty()) { 00693 for (unsigned i = 0; i < requiredIds.size(); ++i) 00694 result["requiredIds"][i] = requiredIds[i]; 00695 } 00696 if (!userPartition.empty()) 00697 result["userPartition"] = userPartition.toJson(); 00698 if (!creatives.empty() && includeCreatives) 00699 result["creatives"] = collectionToJson(creatives, JsonPrint()); 00700 else if (!creatives.empty()) { 00701 Json::Value creativeInfo; 00702 for (unsigned i = 0; i < creatives.size(); ++i) 00703 creativeInfo[i] = creatives[i].format.print(); 00704 result["creatives"] = creativeInfo; 00705 } 00706 if (!segments.empty()) { 00707 Json::Value segmentInfo; 00708 for (auto it = segments.begin(), end = segments.end(); 00709 it != end; ++it) { 00710 segmentInfo[it->first] = it->second.toJson(); 00711 } 00712 result["segmentFilter"] = segmentInfo; 00713 } 00714 00715 if (!augmentations.empty()) { 00716 Json::Value aug; 00717 for (unsigned i = 0; i < augmentations.size(); ++i) 00718 aug[augmentations[i].name] = augmentations[i].config; 00719 result["augment"] = aug; 00720 } 00721 if (!hourOfWeekFilter.isDefault()) { 00722 result["hourOfWeekFilter"] = hourOfWeekFilter.toJson(); 00723 } 00724 00725 if (!foldPositionFilter.empty()) { 00726 result["foldPositionFilter"] = foldPositionFilter.toJson(); 00727 } 00728 if (hasBlacklist()) { 00729 Json::Value & bl = result["blacklist"]; 00730 if (blacklistTime != 0.0) 00731 bl["time"] = blacklistTime; 00732 switch (blacklistType) { 00733 case BL_OFF: bl["type"] = "OFF"; break; 00734 case BL_USER: bl["type"] = "USER"; break; 00735 case BL_USER_SITE: bl["type"] = "USER_SITE"; break; 00736 default: 00737 throw ML::Exception("unknown blacklist type"); 00738 } 00739 00740 switch (blacklistScope) { 00741 case BL_AGENT: bl["scope"] = "AGENT"; break; 00742 case BL_ACCOUNT: bl["scope"] = "ACCOUNT"; break; 00743 default: 00744 throw ML::Exception("unknown blacklist scope"); 00745 } 00746 } 00747 00748 if (!visitChannels.empty()) { 00749 Json::Value & v = result["visits"]; 00750 v["channels"] = visitChannels.toJson(); 00751 v["includeUnmatched"] = includeUnmatchedVisits; 00752 } 00753 00754 if (true) { 00755 Json::Value & bc = result["bidControl"]; 00756 switch (bidControlType) { 00757 case BC_RELAY: bc["type"] = "RELAY"; break; 00758 case BC_RELAY_FIXED: bc["type"] = "RELAY_FIXED"; break; 00759 case BC_FIXED: bc["type"] = "FIXED"; break; 00760 default: 00761 throw ML::Exception("unknown bid control type"); 00762 } 00763 bc["fixedBidCpmInMicros"] = fixedBidCpmInMicros; 00764 } 00765 00766 if (!providerConfig.isNull()) { 00767 result["providerConfig"] = providerConfig; 00768 } 00769 00770 result["winFormat"] = RTBKIT::toJson(winFormat); 00771 result["lossFormat"] = RTBKIT::toJson(lossFormat); 00772 result["errorFormat"] = RTBKIT::toJson(errorFormat); 00773 00774 return result; 00775 } 00776 00777 BiddableSpots 00778 AgentConfig:: 00779 canBid(const ExchangeConnector * exchangeConnector, 00780 const std::vector<AdSpot> & imp, 00781 const std::string & exchange, 00782 const std::string & protocolVersion, 00783 const std::string & language, 00784 const Utf8String & location, uint64_t locationHash, 00785 ML::Lightweight_Hash<uint64_t, int> & locationCache) const 00786 { 00787 BiddableSpots result; 00788 00789 // TODO: do a lookup, not an exhaustive scan 00790 for (unsigned i = 0; i < imp.size(); ++i) { 00791 //cerr << "trying spot " << i << endl; 00792 00793 // Check that the fold position matches 00794 if (!foldPositionFilter.isIncluded(imp[i].position)) 00795 continue; 00796 00797 SmallIntVector matching; 00798 for (unsigned j = 0; j < creatives.size(); ++j) { 00799 // cerr << "spot: " << imp[i].width << "x" << imp[i].height 00800 // << " creative: " << creatives[j].width << "x" 00801 // << creatives[j].height << endl; 00802 if (creatives[j].compatible(imp[i]) 00803 && creatives[j].biddable(exchange, protocolVersion) 00804 && creatives[j].exchangeFilter.isIncluded(exchange) 00805 && creatives[j].languageFilter.isIncluded(language) 00806 && creatives[j].locationFilter.isIncluded(location, locationHash, locationCache)) 00807 matching.push_back(j); 00808 } 00809 if (!matching.empty()) 00810 result.push_back(make_pair(i, matching)); 00811 } 00812 00813 return result; 00814 } 00815 00816 BiddableSpots 00817 AgentConfig:: 00818 isBiddableRequest(const ExchangeConnector * exchangeConnector, 00819 const BidRequest& request, 00820 AgentStats& stats, 00821 RequestFilterCache& cache, 00822 const FilterStatFn & doFilterStat) const 00823 { 00824 /* First, check that the exchange has blessed this campaign as being 00825 biddable. If not, we don't go any further. */ 00826 const void * exchangeInfo = NULL; 00827 std::string exchangeName; 00828 00829 if (exchangeConnector) { 00830 exchangeName = exchangeConnector->exchangeName(); 00831 00832 //cerr << "exchangeName = " << exchangeName 00833 // << " providerData.size() = " << providerData.size() 00834 // << endl; 00835 00836 { 00837 std::lock_guard<ML::Spinlock> guard(lock); 00838 auto it = providerData.find(exchangeName); 00839 if (it == providerData.end()) { 00840 if (doFilterStat) 00841 doFilterStat("static.001_exchangeRejectedCampaign"); 00842 return BiddableSpots(); 00843 } 00844 00845 exchangeInfo = it->second.get(); 00846 } 00847 00848 /* Now we know the exchange connector likes the campaign, we 00849 should check that the campaign and bid request are compatible. 00850 00851 First we pre-filter 00852 */ 00853 if (!exchangeConnector->bidRequestPreFilter(request, *this, exchangeInfo)) { 00854 if (doFilterStat) 00855 doFilterStat("static.001_exchangeMismatchBidRequestCampaignPre"); 00856 return BiddableSpots(); 00857 } 00858 } 00859 00860 /* Find matching creatives for the agent. This includes fold position 00861 filtering. 00862 */ 00863 BiddableSpots biddableSpots = canBid( 00864 exchangeConnector, 00865 request.imp, 00866 request.exchange, 00867 request.protocolVersion, 00868 cache.language, 00869 cache.location, 00870 cache.locationHash, 00871 cache.locationFilter); 00872 00873 //cerr << "agent " << it->first << " imp " 00874 // << biddableSpots.size() << endl; 00875 00876 if (biddableSpots.empty()) { 00877 //cerr << "no biddable imp" << endl; 00878 ML::atomic_inc(stats.noSpots); 00879 if (doFilterStat) doFilterStat("static.010_noSpots"); 00880 return BiddableSpots(); 00881 } 00882 00883 /* Check for generic ID filtering. */ 00884 if (!requiredIds.empty()) { 00885 for (unsigned i = 0; i < requiredIds.size(); ++i) { 00886 if (!request.userIds.count(requiredIds[i])) { 00887 ML::atomic_inc(stats.requiredIdMissing); 00888 if (doFilterStat) 00889 doFilterStat(("static.030_missingRequiredId_" 00890 + requiredIds[i]).c_str()); 00891 return BiddableSpots(); 00892 } 00893 } 00894 } 00895 00896 /* Check for hour of week. */ 00897 if (!hourOfWeekFilter.isIncluded(request.timestamp)) { 00898 ML::atomic_inc(stats.hourOfWeekFiltered); 00899 if (doFilterStat) doFilterStat("static.040_hourOfWeek"); 00900 return BiddableSpots(); 00901 } 00902 00903 /* Check for the exchange. */ 00904 if (!exchangeFilter.isIncluded(request.exchange)) { 00905 ML::atomic_inc(stats.exchangeFiltered); 00906 if (doFilterStat) doFilterStat("static.050_exchangeFiltered"); 00907 return BiddableSpots(); 00908 } 00909 00910 ML::atomic_inc(stats.passedStaticPhase1); 00911 00912 /* Check for the location. */ 00913 if (!locationFilter.isIncluded( 00914 cache.location, cache.locationHash, cache.locationFilter)) 00915 { 00916 ML::atomic_inc(stats.locationFiltered); 00917 if (doFilterStat) doFilterStat("static.060_locationFiltered"); 00918 return BiddableSpots(); 00919 } 00920 00921 /* Check for language. */ 00922 if (!languageFilter.isIncluded( 00923 cache.language, cache.languageHash, cache.languageFilter)) 00924 { 00925 ML::atomic_inc(stats.languageFiltered); 00926 if (doFilterStat) doFilterStat("static.070_languageFiltered"); 00927 return BiddableSpots(); 00928 } 00929 00930 ML::atomic_inc(stats.passedStaticPhase2); 00931 00932 /* Check for segment inclusion/exclusion. */ 00933 bool exclude = false; 00934 int segNum = 0; 00935 for (auto it = segments.begin(), end = segments.end(); 00936 !exclude && it != end; ++it, ++segNum) 00937 { 00938 // Check if the exchange applies to this segment filter 00939 if (!it->second.applyToExchanges.isIncluded(request.exchange)) 00940 continue; 00941 00942 // Look up this segment source in the bid request 00943 auto segs = request.segments.find(it->first); 00944 00945 // If not found, then check what the default response is 00946 if (segs == request.segments.end()) { 00947 exclude = it->second.excludeIfNotPresent; 00948 ML::atomic_inc(stats.segmentsMissing); 00949 if (exclude) { 00950 if (doFilterStat) doFilterStat( 00951 ("static.080_segmentInfoMissing_" + it->first).c_str()); 00952 } 00953 } 00954 else { 00955 const auto & segments = segs->second; 00956 00957 // Check what the include/exclude list says 00958 IncludeExcludeResult inc = it->second.process(*segments); 00959 00960 switch (inc) { 00961 case IE_NO_DATA: 00962 exclude = it->second.excludeIfNotPresent; 00963 if (doFilterStat) doFilterStat( 00964 ("static.080_segmentHasNoData_" + it->first).c_str()); 00965 break; 00966 case IE_NOT_INCLUDED: 00967 case IE_EXCLUDED: 00968 if (doFilterStat) doFilterStat( 00969 ("static.080_segmentExcluded_" + it->first).c_str()); 00970 exclude = true; 00971 break; 00972 case IE_PASSED: 00973 break; 00974 } 00975 } 00976 00977 if (segNum == 0 && exclude) 00978 ML::atomic_inc(stats.filter1Excluded); 00979 else if (segNum == 1 && exclude) 00980 ML::atomic_inc(stats.filter2Excluded); 00981 else if (exclude) 00982 ML::atomic_inc(stats.filternExcluded); 00983 } 00984 00985 if (exclude) { 00986 ML::atomic_inc(stats.segmentFiltered); 00987 return BiddableSpots(); 00988 } 00989 00990 ML::atomic_inc(stats.passedStaticPhase3); 00991 00992 /* Check that the user partition matches. */ 00993 if (!userPartition.matches(request.userIds)) { 00994 ML::atomic_inc(stats.userPartitionFiltered); 00995 if (doFilterStat) doFilterStat("static.080_userPartitionFiltered"); 00996 return BiddableSpots(); 00997 } 00998 00999 /* Check for blacklisted domains. */ 01000 if (!hostFilter.isIncluded(request.url, cache.urlHash, cache.urlFilter)) { 01001 ML::atomic_inc(stats.urlFiltered); 01002 if (doFilterStat) doFilterStat("static.085_hostFiltered"); 01003 return BiddableSpots(); 01004 } 01005 01006 /* Check for blacklisted URLs. */ 01007 if (!urlFilter.isIncluded( 01008 request.url.toString(), cache.urlHash, cache.urlFilter)) 01009 { 01010 ML::atomic_inc(stats.urlFiltered); 01011 if (doFilterStat) doFilterStat("static.090_urlFiltered"); 01012 return BiddableSpots(); 01013 } 01014 01015 /* Finally, perform any expensive filtering in the exchange connector. */ 01016 if (exchangeConnector) { 01017 01018 if (!exchangeConnector->bidRequestPostFilter(request, *this, exchangeInfo)) { 01019 if (doFilterStat) 01020 doFilterStat("static.099_exchangeMismatchBidRequestCampaignPost"); 01021 return BiddableSpots(); 01022 } 01023 } 01024 01025 return biddableSpots; 01026 } 01027 01028 void 01029 AgentConfig:: 01030 addAugmentation(const std::string & name, Json::Value config) 01031 { 01032 01033 AugmentationInfo info; 01034 info.name = name; 01035 info.config = std::move(config); 01036 01037 addAugmentation(info); 01038 } 01039 01040 void 01041 AgentConfig:: 01042 addAugmentation(AugmentationInfo info) 01043 { 01044 for (auto & a: augmentations) 01045 if (a.name == info.name) 01046 throw ML::Exception("augmentor " + a.name + " is specified twice"); 01047 01048 augmentations.push_back(std::move(info)); 01049 01050 std::sort(augmentations.begin(), augmentations.end()); 01051 } 01052 01053 } // namespace RTBKIT 01054