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