RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
core/agent_configuration/agent_config.cc
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 
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator