RTBKit
0.9
Open-source framework to create real-time ad bidding systems.
|
00001 /* account.cc 00002 Jeremy Barnes, 16 November 2012 00003 Copyright (c) 2012 Datacratic. All rights reserved. 00004 00005 Implementation of the Account class. 00006 */ 00007 00008 #include "account.h" 00009 #include "banker.h" 00010 00011 using namespace std; 00012 using namespace ML; 00013 00014 00015 namespace RTBKIT { 00016 00017 00018 /*****************************************************************************/ 00019 /* ACCOUNT TYPE */ 00020 /*****************************************************************************/ 00021 00022 AccountType AccountTypeFromString(const std::string & param) { 00023 return restDecode(param, NULL); 00024 } 00025 00026 const std::string AccountTypeToString(enum AccountType type) { 00027 switch (type) { 00028 case AT_NONE: 00029 return "none"; 00030 break; 00031 case AT_BUDGET: 00032 return "budget"; 00033 break; 00034 case AT_SPEND: 00035 return "spend"; 00036 break; 00037 default: 00038 throw ML::Exception("unknown account type " + type); 00039 } 00040 } 00041 00042 /*****************************************************************************/ 00043 /* ACCOUNT KEY */ 00044 /*****************************************************************************/ 00045 00046 00047 /*****************************************************************************/ 00048 /* ACCOUNT */ 00049 /*****************************************************************************/ 00050 void 00051 Account:: 00052 setBudget(const CurrencyPool & newBudget) 00053 { 00054 /* totalBudget = budgetIncreases - budgetDecreases */ 00055 CurrencyPool totalBudget = budgetIncreases - budgetDecreases; 00056 /* extraBudget = amount to add to members to obtain new total budget */ 00057 CurrencyPool extraBudget = newBudget - totalBudget; 00058 00059 #if 0 00060 cerr << "totalBudget: " << totalBudget << endl; 00061 cerr << "balance: " << balance 00062 << "; extraBudget: " << extraBudget << endl; 00063 #endif 00064 ExcAssert((balance + extraBudget).isNonNegative()); 00065 00066 if (extraBudget.isNonNegative()) { 00067 budgetIncreases += extraBudget; 00068 } 00069 else { 00070 budgetDecreases -= extraBudget; 00071 } 00072 balance += extraBudget; 00073 00074 checkInvariants(); 00075 } 00076 00077 CurrencyPool 00078 Account:: 00079 importSpend(const CurrencyPool & spend) 00080 { 00081 ExcAssert(type == AT_SPEND); 00082 ExcAssert(spend.isNonNegative()); 00083 00084 checkInvariants(); 00085 00086 /* make sure we have enough balance to spend */ 00087 CurrencyPool newBalance = balance - spend; 00088 /* FIXME: we might choose a different policy than just crashing here */ 00089 ExcAssert(newBalance.isNonNegative()); 00090 00091 balance = newBalance; 00092 spent += spend; 00093 00094 checkInvariants(); 00095 00096 return spend; 00097 } 00098 00099 CurrencyPool 00100 Account:: 00101 getBudget() 00102 const 00103 { 00104 return (budgetIncreases - budgetDecreases); 00105 // + allocatedIn - allocatedOut 00106 // + recycledIn - recycledOut 00107 // + adjustmentsIn - adjustmentsOut); 00108 } 00109 00110 CurrencyPool 00111 Account:: 00112 getNetBudget() 00113 const 00114 { 00115 return (budgetIncreases - budgetDecreases 00116 + allocatedIn - allocatedOut 00117 + recycledIn - recycledOut 00118 + adjustmentsIn - adjustmentsOut); 00119 } 00120 00121 std::ostream & operator << (std::ostream & stream, const Account & account) 00122 { 00123 std::set<CurrencyCode> currencies; 00124 00125 auto addCurrencies = [&] (const CurrencyPool & c) 00126 { 00127 for (auto a: c.currencyAmounts) { 00128 if (a) currencies.insert(a.currencyCode); 00129 } 00130 }; 00131 00132 addCurrencies(account.budgetIncreases); 00133 addCurrencies(account.budgetDecreases); 00134 addCurrencies(account.recycledIn); 00135 addCurrencies(account.recycledOut); 00136 addCurrencies(account.commitmentsMade); 00137 addCurrencies(account.commitmentsRetired); 00138 addCurrencies(account.spent); 00139 addCurrencies(account.balance); 00140 addCurrencies(account.adjustmentsIn); 00141 addCurrencies(account.adjustmentsOut); 00142 addCurrencies(account.allocatedIn); 00143 addCurrencies(account.allocatedOut); 00144 00145 for (const auto & li: account.lineItems.entries) 00146 addCurrencies(li.second); 00147 00148 stream << endl << " "; 00149 for (auto c: currencies) { 00150 string s = Amount::getCurrencyStr(c); 00151 int spaces = 21 - s.size(); 00152 int before = spaces / 2; 00153 int after = spaces - before; 00154 stream << ' ' 00155 << string(before, '-') << ' ' << s << ' ' << string(after, '-'); 00156 } 00157 stream << endl; 00158 00159 stream << " "; 00160 for (unsigned i = 0; i < currencies.size(); ++i) 00161 stream << " credit debit"; 00162 stream << endl; 00163 00164 auto printCurrency = [&] (const char * label, 00165 const CurrencyPool & credit, 00166 const CurrencyPool & debit) 00167 { 00168 if (credit.isZero() && debit.isZero()) 00169 return; 00170 00171 stream << ML::format("%-20s", label); 00172 00173 auto printAmount = [&] (const Amount & a) 00174 { 00175 if (!a.isZero()) 00176 stream << ML::format("%12lld", (long long)a.value); 00177 else stream << " "; 00178 }; 00179 00180 for (auto c: currencies) { 00181 printAmount(credit.getAvailable(c)); 00182 printAmount(debit.getAvailable(c)); 00183 } 00184 00185 stream << endl; 00186 }; 00187 00188 CurrencyPool z; 00189 00190 printCurrency(" budget in/out", account.budgetIncreases, 00191 account.budgetDecreases); 00192 printCurrency(" none/spent", CurrencyPool(), account.spent); 00193 printCurrency(" recycled in/out", account.recycledIn, account.recycledOut); 00194 printCurrency(" allocated in/out", account.allocatedIn, account.allocatedOut); 00195 printCurrency(" commit ret/made", account.commitmentsRetired, account.commitmentsMade); 00196 printCurrency(" adj in/out", account.adjustmentsIn, account.adjustmentsOut); 00197 stream << "--------------------------------------------" << endl; 00198 printCurrency(" balance", account.balance, z); 00199 stream << endl; 00200 00201 auto printLineItems = [&] (const std::string & name, 00202 const LineItems & lineItems) 00203 { 00204 if (!lineItems.isZero()) { 00205 stream << name << endl; 00206 for (const auto & li: lineItems.entries) { 00207 printCurrency(li.first.c_str(), z, li.second); 00208 } 00209 } 00210 }; 00211 00212 printLineItems("Spend Line Items:", account.lineItems); 00213 printLineItems("Adjustment Line Items:", account.adjustmentLineItems); 00214 00215 return stream; 00216 } 00217 00218 00219 /*****************************************************************************/ 00220 /* SHADOW ACCOUNT */ 00221 /*****************************************************************************/ 00222 00223 void 00224 ShadowAccount:: 00225 logBidEvents(const Datacratic::EventRecorder & eventRecorder, 00226 const string & accountKey) 00227 { 00228 eventRecorder.recordLevel(attachedBids, 00229 "banker.accounts." + accountKey + ".attachedBids"); 00230 attachedBids = 0; 00231 00232 eventRecorder.recordLevel(detachedBids, 00233 "banker.accounts." + accountKey + ".detachedBids"); 00234 detachedBids = 0; 00235 00236 eventRecorder.recordLevel(commitments.size(), 00237 "banker.accounts." + accountKey + ".pendingCommitments"); 00238 00239 Date now = Date::now(); 00240 lastExpiredCommitments = 0; 00241 for (auto & it: commitments) { 00242 Commitment & commitment = it.second; 00243 if (now >= commitment.timestamp.plusSeconds(15.0)) { 00244 lastExpiredCommitments++; 00245 } 00246 } 00247 eventRecorder.recordLevel(lastExpiredCommitments, 00248 "banker.accounts." + accountKey + ".expiredCommitments"); 00249 } 00250 00251 std::ostream & 00252 operator << (std::ostream & stream, const ShadowAccount & account) 00253 { 00254 std::set<CurrencyCode> currencies; 00255 00256 auto addCurrencies = [&] (const CurrencyPool & c) 00257 { 00258 for (auto a: c.currencyAmounts) { 00259 if (a) currencies.insert(a.currencyCode); 00260 } 00261 }; 00262 00263 addCurrencies(account.netBudget); 00264 addCurrencies(account.commitmentsMade); 00265 addCurrencies(account.commitmentsRetired); 00266 addCurrencies(account.spent); 00267 addCurrencies(account.balance); 00268 00269 for (const auto & li: account.lineItems.entries) 00270 addCurrencies(li.second); 00271 00272 stream << endl << " "; 00273 for (auto c: currencies) { 00274 string s = Amount::getCurrencyStr(c); 00275 int spaces = 21 - s.size(); 00276 int before = spaces / 2; 00277 int after = spaces - before; 00278 stream << ' ' 00279 << string(before, '-') << ' ' << s << ' ' << string(after, '-'); 00280 } 00281 stream << endl; 00282 00283 stream << " "; 00284 for (unsigned i = 0; i < currencies.size(); ++i) 00285 stream << " credit debit"; 00286 stream << endl; 00287 00288 auto printCurrency = [&] (const char * label, 00289 const CurrencyPool & credit, 00290 const CurrencyPool & debit) 00291 { 00292 if (credit.isZero() && debit.isZero()) 00293 return; 00294 00295 stream << ML::format("%-20s", label); 00296 00297 auto printAmount = [&] (const Amount & a) 00298 { 00299 if (!a.isZero()) 00300 stream << ML::format("%12lld", (long long)a.value); 00301 else stream << " "; 00302 }; 00303 00304 for (auto c: currencies) { 00305 printAmount(credit.getAvailable(c)); 00306 printAmount(debit.getAvailable(c)); 00307 } 00308 00309 stream << endl; 00310 }; 00311 00312 CurrencyPool z; 00313 00314 printCurrency(" netBudget/spent", account.netBudget, account.spent); 00315 printCurrency(" commit ret/made", account.commitmentsRetired, account.commitmentsMade); 00316 stream << "--------------------------------------------" << endl; 00317 printCurrency(" balance", account.balance, z); 00318 stream << endl; 00319 00320 auto printLineItems = [&] (const std::string & name, 00321 const LineItems & lineItems) 00322 { 00323 if (!lineItems.isZero()) { 00324 stream << name << endl; 00325 for (const auto & li: lineItems.entries) { 00326 printCurrency(li.first.c_str(), z, li.second); 00327 } 00328 } 00329 }; 00330 00331 printLineItems("Spend Line Items:", account.lineItems); 00332 00333 return stream; 00334 } 00335 00336 /*****************************************************************************/ 00337 /* SHADOW ACCOUNTS */ 00338 /*****************************************************************************/ 00339 void 00340 ShadowAccounts:: 00341 logBidEvents(const Datacratic::EventRecorder & eventRecorder) 00342 { 00343 Guard guard(lock); 00344 00345 uint32_t attachedBids(0), detachedBids(0), commitments(0), expired(0); 00346 00347 for (auto & it: accounts) { 00348 ShadowAccount & account = it.second; 00349 attachedBids += account.attachedBids; 00350 detachedBids += account.detachedBids; 00351 commitments += account.commitments.size(); 00352 account.logBidEvents(eventRecorder, it.first.toString('.')); 00353 expired += account.lastExpiredCommitments; 00354 } 00355 00356 eventRecorder.recordLevel(attachedBids, 00357 "banker.total.attachedBids"); 00358 eventRecorder.recordLevel(detachedBids, 00359 "banker.total.setachedBids"); 00360 eventRecorder.recordLevel(commitments, 00361 "banker.total.pendingCommitments"); 00362 eventRecorder.recordLevel(expired, 00363 "banker.total.expiredCommitments"); 00364 } 00365 00366 /*****************************************************************************/ 00367 /* ACCOUNTS */ 00368 /*****************************************************************************/ 00369 00370 void 00371 Accounts:: 00372 ensureInterAccountConsistency() 00373 { 00374 Guard guard(lock); 00375 00376 for (const auto & it: accounts) { 00377 if (it.first.size() == 1) { 00378 if (!checkBudgetConsistencyImpl(it.first, -1, 0)) { 00379 // cerr << "budget of account " << it.first 00380 // << " is not consistent\n"; 00381 inconsistentAccounts.insert(it.first); 00382 } 00383 CurrencyPool recycledInUp, recycledOutUp, nullPool; 00384 getRecycledUp(it.first, recycledInUp, recycledOutUp); 00385 if (recycledInUp != nullPool) { 00386 cerr << "upward recycledIn of account " << it.first 00387 << " is not null: " << recycledInUp 00388 << "\n"; 00389 } 00390 if (recycledOutUp != nullPool) { 00391 cerr << "upward recycledOut of account " << it.first 00392 << " is not null: " << recycledOutUp 00393 << "\n"; 00394 } 00395 } 00396 } 00397 } 00398 00399 bool 00400 Accounts:: 00401 checkBudgetConsistency(const AccountKey & accountKey, int maxRecursion) 00402 const 00403 { 00404 Guard guard(lock); 00405 00406 ExcAssertEqual(accountKey.size(), 1); 00407 00408 return checkBudgetConsistencyImpl(accountKey, maxRecursion, 0); 00409 } 00410 00411 bool 00412 Accounts:: 00413 checkBudgetConsistencyImpl(const AccountKey & accountKey, int maxRecursion, 00414 int level) 00415 const 00416 { 00417 const AccountInfo & account = getAccountImpl(accountKey); 00418 CurrencyPool sumBudgetInc; 00419 00420 for (const AccountKey & childKey: account.children) { 00421 const Account & childAccount = getAccountImpl(childKey); 00422 sumBudgetInc += childAccount.budgetIncreases; 00423 } 00424 00425 if (account.allocatedOut != sumBudgetInc) { 00426 cerr << "budget of account " << accountKey 00427 << " is not consistent:\n " 00428 << account.allocatedOut 00429 << " != " << sumBudgetInc 00430 << " (delta = " 00431 << (account.allocatedOut - sumBudgetInc) 00432 << ")\n"; 00433 return false; 00434 } 00435 00436 if (maxRecursion == -1 || level < maxRecursion) { 00437 for (const AccountKey & childKey: account.children) { 00438 if (!checkBudgetConsistencyImpl(childKey, 00439 maxRecursion, level + 1)) 00440 return false; 00441 } 00442 } 00443 00444 return true; 00445 } 00446 00447 void 00448 Accounts:: 00449 getRecycledUp(const AccountKey & accountKey, 00450 CurrencyPool & recycledInUp, 00451 CurrencyPool & recycledOutUp) 00452 const 00453 { 00454 CurrencyPool sumSubRecycledIn, sumSubRecycledOut; 00455 00456 const AccountInfo & account = getAccountImpl(accountKey); 00457 00458 for (const AccountKey & childKey: account.children) { 00459 CurrencyPool subRecycledIn, subRecycledOut; 00460 getRecycledUp(childKey, subRecycledIn, subRecycledOut); 00461 sumSubRecycledIn += subRecycledIn; 00462 sumSubRecycledOut += subRecycledOut; 00463 } 00464 00465 recycledInUp = account.recycledIn - sumSubRecycledOut; 00466 recycledOutUp = account.recycledOut - sumSubRecycledIn; 00467 } 00468 00469 } // namespace RTBKIT