RTBKit
0.9
Open-source framework to create real-time ad bidding systems.
|
00001 /* account.h -*- C++ -*- 00002 Jeremy Barnes, 16 November 2012 00003 Copyright (c) 2012 Datacratic Inc. All rights reserved. 00004 00005 */ 00006 00007 #pragma once 00008 00009 #include <string> 00010 #include <vector> 00011 #include <unordered_map> 00012 #include <memory> 00013 #include <unordered_set> 00014 #include "rtbkit/common/currency.h" 00015 #include "rtbkit/common/account_key.h" 00016 #include "soa/types/date.h" 00017 #include "jml/utils/string_functions.h" 00018 #include <mutex> 00019 #include <thread> 00020 #include "jml/arch/spinlock.h" 00021 00022 namespace Datacratic { 00023 struct EventRecorder; 00024 } 00025 00026 namespace RTBKIT { 00027 using namespace Datacratic; 00028 00029 struct Account; 00030 00031 std::ostream & operator << (std::ostream & stream, const Account & account); 00032 00033 struct ShadowAccount; 00034 00035 std::ostream & 00036 operator << (std::ostream & stream, const ShadowAccount & account); 00037 00038 00039 /*****************************************************************************/ 00040 /* ACCOUNT TYPE */ 00041 /*****************************************************************************/ 00042 00043 enum AccountType { 00044 AT_NONE, 00045 AT_BUDGET, 00046 AT_SPEND 00047 }; 00048 00049 inline AccountType restDecode(const std::string & param, AccountType *) 00050 { 00051 if (param == "none") 00052 return AT_NONE; 00053 else if (param == "budget") 00054 return AT_BUDGET; 00055 else if (param == "spend") 00056 return AT_SPEND; 00057 else throw ML::Exception("unknown account type " + param); 00058 } 00059 00060 extern AccountType AccountTypeFromString(const std::string & param); 00061 extern const std::string AccountTypeToString(enum AccountType type); 00062 00063 /*****************************************************************************/ 00064 /* ACCOUNT */ 00065 /*****************************************************************************/ 00066 00069 struct Account { 00070 Account() 00071 : type(AT_NONE) 00072 { 00073 } 00074 00075 AccountType type; 00076 00077 //mutable Date lastAccess; 00078 //Date lastModification; 00079 00080 // On the Credit side... 00081 CurrencyPool budgetIncreases; 00082 CurrencyPool budgetDecreases; 00083 CurrencyPool recycledIn; 00084 CurrencyPool allocatedIn; 00085 CurrencyPool commitmentsRetired; 00086 CurrencyPool adjustmentsIn; 00087 00088 // On the Debit side... 00089 CurrencyPool recycledOut; 00090 CurrencyPool allocatedOut; 00091 CurrencyPool commitmentsMade; 00092 CurrencyPool adjustmentsOut; 00093 CurrencyPool spent; 00094 00095 CurrencyPool balance; 00096 00097 // Extra information tracked, but not used in any calculations 00098 LineItems lineItems; 00099 LineItems adjustmentLineItems; 00100 00101 // Invariant: sum(Credit Side) = sum(Debit Side) 00102 00103 public: 00104 bool isSameOrPastVersion(const Account & otherAccount) const 00105 { 00106 /* All the amounts in the storage accounts must have a counterpart in 00107 * the banker accounts and their value must be inferior or equal to 00108 * the corresponding amounts in the banker. */ 00109 return (budgetIncreases.isSameOrPastVersion(otherAccount.budgetIncreases) 00110 && budgetDecreases.isSameOrPastVersion(otherAccount.budgetDecreases) 00111 && recycledIn.isSameOrPastVersion(otherAccount.recycledIn) 00112 && allocatedIn.isSameOrPastVersion(otherAccount.allocatedIn) 00113 && commitmentsRetired.isSameOrPastVersion(otherAccount.commitmentsRetired) 00114 && adjustmentsIn.isSameOrPastVersion(otherAccount.adjustmentsIn) 00115 00116 && recycledOut.isSameOrPastVersion(otherAccount.recycledOut) 00117 && allocatedOut.isSameOrPastVersion(otherAccount.allocatedOut) 00118 && commitmentsMade.isSameOrPastVersion(otherAccount.commitmentsMade) 00119 && adjustmentsOut.isSameOrPastVersion(otherAccount.adjustmentsOut) 00120 && spent.isSameOrPastVersion(otherAccount.spent)); 00121 } 00122 00123 Json::Value toJson() const 00124 { 00125 // checkInvariants(); 00126 00127 Json::Value result(Json::objectValue); 00128 result["md"]["objectType"] = "Account"; 00129 result["md"]["version"] = 1; 00130 result["type"] = AccountTypeToString(type); 00131 result["budgetIncreases"] = budgetIncreases.toJson(); 00132 result["budgetDecreases"] = budgetDecreases.toJson(); 00133 result["spent"] = spent.toJson(); 00134 result["recycledIn"] = recycledIn.toJson(); 00135 result["recycledOut"] = recycledOut.toJson(); 00136 result["allocatedIn"] = allocatedIn.toJson(); 00137 result["allocatedOut"] = allocatedOut.toJson(); 00138 result["commitmentsMade"] = commitmentsMade.toJson(); 00139 result["commitmentsRetired"] = commitmentsRetired.toJson(); 00140 result["adjustmentsIn"] = adjustmentsIn.toJson(); 00141 result["adjustmentsOut"] = adjustmentsOut.toJson(); 00142 result["lineItems"] = lineItems.toJson(); 00143 result["adjustmentLineItems"] = adjustmentLineItems.toJson(); 00144 00145 return result; 00146 } 00147 00148 static const Account fromJson(const Json::Value & json) 00149 { 00150 Account result; 00151 ExcAssertEqual(json["md"]["objectType"].asString(), "Account"); 00152 ExcAssertEqual(json["md"]["version"].asInt(), 1); 00153 00154 result.type = AccountTypeFromString(json["type"].asString()); 00155 if (json.isMember("budget")) { 00156 result.budgetIncreases = CurrencyPool::fromJson(json["budget"]); 00157 result.budgetDecreases = CurrencyPool::fromJson(json["adjustmentsOut"]); 00158 result.adjustmentsOut = CurrencyPool(); 00159 } 00160 else { 00161 result.budgetIncreases = CurrencyPool::fromJson(json["budgetIncreases"]); 00162 result.budgetDecreases = CurrencyPool::fromJson(json["budgetDecreases"]); 00163 result.adjustmentsOut = CurrencyPool::fromJson(json["adjustmentsOut"]); 00164 } 00165 result.spent = CurrencyPool::fromJson(json["spent"]); 00166 result.recycledIn = CurrencyPool::fromJson(json["recycledIn"]); 00167 result.recycledOut = CurrencyPool::fromJson(json["recycledOut"]); 00168 result.allocatedIn = CurrencyPool::fromJson(json["allocatedIn"]); 00169 result.allocatedOut = CurrencyPool::fromJson(json["allocatedOut"]); 00170 result.commitmentsMade = CurrencyPool::fromJson(json["commitmentsMade"]); 00171 result.commitmentsRetired = CurrencyPool::fromJson(json["commitmentsRetired"]); 00172 result.adjustmentsIn = CurrencyPool::fromJson(json["adjustmentsIn"]); 00173 result.lineItems = LineItems::fromJson(json["lineItems"]); 00174 result.adjustmentLineItems = LineItems::fromJson(json["adjustmentLineItems"]); 00175 result.balance = ((result.budgetIncreases 00176 + result.recycledIn 00177 + result.commitmentsRetired 00178 + result.adjustmentsIn 00179 + result.allocatedIn) 00180 - (result.budgetDecreases 00181 + result.recycledOut 00182 + result.commitmentsMade 00183 + result.spent 00184 + result.adjustmentsOut 00185 + result.balance 00186 + result.allocatedOut)); 00187 00188 result.checkInvariants(); 00189 00190 return result; 00191 } 00192 00193 /*************************************************************************/ 00194 /* DERIVED QUANTITIES */ 00195 /*************************************************************************/ 00196 00199 CurrencyPool getRecycledAvail() const 00200 { 00201 return (recycledIn - recycledOut).nonNegative(); 00202 } 00203 00204 CurrencyPool getBudget() const; 00205 00209 CurrencyPool getNetBudget() const; 00210 00211 /*************************************************************************/ 00212 /* INVARIANTS */ 00213 /*************************************************************************/ 00214 00215 void checkInvariants(const char * whereFrom = "") const 00216 { 00217 try { 00218 00219 // Everything but balance must be positive 00220 ExcAssert(budgetIncreases.isNonNegative()); 00221 ExcAssert(budgetDecreases.isNonNegative()); 00222 ExcAssert(recycledIn.isNonNegative()); 00223 ExcAssert(recycledOut.isNonNegative()); 00224 ExcAssert(commitmentsRetired.isNonNegative()); 00225 ExcAssert(recycledOut.isNonNegative()); 00226 ExcAssert(commitmentsMade.isNonNegative()); 00227 ExcAssert(spent.isNonNegative()); 00228 ExcAssert(adjustmentsIn.isNonNegative()); 00229 ExcAssert(adjustmentsOut.isNonNegative()); 00230 ExcAssert(allocatedIn.isNonNegative()); 00231 ExcAssert(allocatedOut.isNonNegative()); 00232 00233 // Credit and debit sides must balance out 00234 CurrencyPool credit = (budgetIncreases + recycledIn + commitmentsRetired 00235 + adjustmentsIn + allocatedIn); 00236 CurrencyPool debit = (budgetDecreases + recycledOut + commitmentsMade + spent 00237 + adjustmentsOut + balance 00238 + allocatedOut); 00239 ExcAssertEqual(credit, debit); 00240 } catch (...) { 00241 using namespace std; 00242 cerr << "error on account " << *this << " checking invariants at " 00243 << whereFrom << endl; 00244 throw; 00245 } 00246 //... 00247 } 00248 00249 /*************************************************************************/ 00250 /* TRANSFER OPERATIONS (ONE SIDED) */ 00251 /*************************************************************************/ 00252 00253 /* These operations need to be paired with a corresponding operation on 00254 the other side. 00255 */ 00256 00260 CurrencyPool recuperate() 00261 { 00262 auto result = balance; 00263 recycledOut += balance; 00264 balance.clear(); 00265 00266 checkInvariants(); 00267 00268 return result; 00269 } 00270 00274 void recycle(const CurrencyPool & recuperated) 00275 { 00276 ExcAssert(recuperated.isNonNegative()); 00277 00278 recycledIn += recuperated; 00279 balance += recuperated; 00280 00281 checkInvariants(); 00282 } 00283 00287 void setBudget(const CurrencyPool & newBudget); 00288 00293 CurrencyPool setBalance(Account & parentAccount, 00294 const CurrencyPool & newBalance) 00295 { 00296 checkInvariants("entry to setBalance"); 00297 00298 auto before = *this; 00299 auto parentBefore = parentAccount; 00300 00301 CurrencyPool requiredTotal = newBalance - balance; 00302 00303 // Some amount needs to be transferred in, and some amount out 00304 CurrencyPool requiredIn = requiredTotal.nonNegative(); 00305 CurrencyPool requiredOut = requiredIn - requiredTotal; 00306 00307 ExcAssert(requiredIn.isNonNegative()); 00308 ExcAssert(requiredOut.isNonNegative()); 00309 00310 CurrencyPool toTransfer = parentAccount.balance.limit(requiredIn); 00311 00312 using namespace std; 00313 00314 bool debug = false; 00315 00316 // First take it from the recycled... 00317 CurrencyPool parentRecycledAvail 00318 = parentAccount.getRecycledAvail(); 00319 CurrencyPool fromRecycled = parentRecycledAvail.limit(toTransfer); 00320 CurrencyPool toRecycled = requiredOut; 00321 00322 if (debug) { 00323 cerr << "newBalance = " << newBalance << endl; 00324 cerr << "balance = " << balance << endl; 00325 cerr << "requiredTotal = " << requiredTotal << endl; 00326 cerr << "requiredIn = " << requiredIn << endl; 00327 cerr << "requiredOut = " << requiredOut << endl; 00328 cerr << "toTransfer = " << toTransfer << endl; 00329 cerr << "parentRecycledAvail = " << parentRecycledAvail << endl; 00330 cerr << "fromRecycled = " << fromRecycled << endl; 00331 } 00332 00333 // And then as a commitment 00334 CurrencyPool fromBudget = toTransfer - fromRecycled; 00335 00336 if (debug) 00337 cerr << "fromBudget = " << fromBudget << endl; 00338 00339 // Take from parent recycled 00340 parentAccount.recycledOut += fromRecycled; 00341 parentAccount.balance -= fromRecycled; 00342 recycledIn += fromRecycled; 00343 balance += fromRecycled; 00344 00345 // Give back to budget 00346 parentAccount.allocatedOut += fromBudget; 00347 parentAccount.balance -= fromBudget; 00348 budgetIncreases += fromBudget; 00349 balance += fromBudget; 00350 00351 // Give to parent recycled 00352 parentAccount.recycledIn += toRecycled; 00353 parentAccount.balance += toRecycled; 00354 recycledOut += toRecycled; 00355 balance -= toRecycled; 00356 00357 try { 00358 checkInvariants("exiting from setBalance"); 00359 parentAccount.checkInvariants("parent check invariants"); 00360 } catch (...) { 00361 cerr << "before: " << before << endl; 00362 cerr << "after: " << *this << endl; 00363 00364 cerr << "parent before: " << parentBefore << endl; 00365 cerr << "parent after: " << parentAccount << endl; 00366 00367 cerr << "newBalance = " << newBalance << endl; 00368 cerr << "balance = " << balance << endl; 00369 cerr << "requiredTotal = " << requiredTotal << endl; 00370 cerr << "requiredIn = " << requiredIn << endl; 00371 cerr << "requiredOut = " << requiredOut << endl; 00372 cerr << "toTransfer = " << toTransfer << endl; 00373 cerr << "parentRecycledAvail = " << parentRecycledAvail << endl; 00374 cerr << "fromRecycled = " << fromRecycled << endl; 00375 00376 cerr << "fromBudget = " << fromBudget << endl; 00377 throw; 00378 } 00379 00380 return balance; 00381 } 00382 00385 CurrencyPool importSpend(const CurrencyPool & spend); 00386 00387 void recuperateTo(Account & parentAccount) 00388 { 00389 CurrencyPool amount = balance.nonNegative(); 00390 00391 recycledOut += amount; 00392 balance -= amount; 00393 00394 parentAccount.recycledIn += amount; 00395 parentAccount.balance += amount; 00396 00397 checkInvariants("recuperateTo"); 00398 } 00399 }; 00400 00401 00402 /*****************************************************************************/ 00403 /* SHADOW ACCOUNT */ 00404 /*****************************************************************************/ 00405 00411 struct ShadowAccount { 00412 ShadowAccount() 00413 : attachedBids(0), detachedBids(0) 00414 {} 00415 00416 // credit 00417 CurrencyPool netBudget; 00418 CurrencyPool commitmentsRetired; 00419 00420 // debit 00421 CurrencyPool commitmentsMade; 00422 CurrencyPool spent; 00423 00424 CurrencyPool balance; 00425 00426 LineItems lineItems; 00427 00428 struct Commitment { 00429 Commitment(Amount amount, Date timestamp) 00430 : amount(amount), timestamp(timestamp) 00431 { 00432 } 00433 00434 Amount amount; 00435 Date timestamp; 00436 }; 00437 00438 std::unordered_map<std::string, Commitment> commitments; 00439 00440 void checkInvariants() const 00441 { 00442 try { 00443 //ExcAssert(netBudget.isNonNegative()); 00444 ExcAssert(commitmentsRetired.isNonNegative()); 00445 ExcAssert(commitmentsMade.isNonNegative()); 00446 ExcAssert(spent.isNonNegative()); 00447 00448 CurrencyPool credit = netBudget + commitmentsRetired; 00449 CurrencyPool debit = commitmentsMade + spent + balance; 00450 00451 ExcAssertEqual(credit, debit); 00452 } catch (...) { 00453 using namespace std; 00454 cerr << "invariants failed:" << endl; 00455 cerr << *this << endl; 00456 throw; 00457 } 00458 } 00459 00460 Json::Value toJson() const 00461 { 00462 checkInvariants(); 00463 00464 Json::Value result(Json::objectValue); 00465 result["md"]["objectType"] = "ShadowAccount"; 00466 result["md"]["version"] = 1; 00467 result["netBudget"] = netBudget.toJson(); 00468 result["commitmentsRetired"] = commitmentsRetired.toJson(); 00469 result["commitmentsMade"] = commitmentsMade.toJson(); 00470 result["spent"] = spent.toJson(); 00471 result["lineItems"] = lineItems.toJson(); 00472 result["balance"] = balance.toJson(); 00473 00474 ShadowAccount reparsed = fromJson(result); 00475 reparsed.checkInvariants(); 00476 ExcAssertEqual(netBudget, reparsed.netBudget); 00477 ExcAssertEqual(spent, reparsed.spent); 00478 ExcAssertEqual(commitmentsRetired, reparsed.commitmentsRetired); 00479 ExcAssertEqual(commitmentsMade, reparsed.commitmentsMade); 00480 ExcAssertEqual(lineItems, reparsed.lineItems); 00481 00482 return result; 00483 } 00484 00485 static const ShadowAccount fromJson(const Json::Value & val) 00486 { 00487 ShadowAccount result; 00488 ExcAssertEqual(val["md"]["objectType"].asString(), "ShadowAccount"); 00489 ExcAssertEqual(val["md"]["version"].asInt(), 1); 00490 00491 result.netBudget = CurrencyPool::fromJson(val["netBudget"]); 00492 result.commitmentsRetired = CurrencyPool::fromJson(val["commitmentsRetired"]); 00493 result.commitmentsMade = CurrencyPool::fromJson(val["commitmentsMade"]); 00494 result.spent = CurrencyPool::fromJson(val["spent"]); 00495 result.balance = CurrencyPool::fromJson(val["balance"]); 00496 result.lineItems = LineItems::fromJson(val["lineItems"]); 00497 00498 result.checkInvariants(); 00499 00500 return result; 00501 } 00502 00503 /*************************************************************************/ 00504 /* SPEND TRACKING */ 00505 /*************************************************************************/ 00506 00507 void forceWinBid(Amount amountPaid, 00508 const LineItems & lineItems) 00509 { 00510 commitDetachedBid(Amount(), amountPaid, lineItems); 00511 } 00512 00514 void commitDetachedBid(Amount amountAuthorized, 00515 Amount amountPaid, 00516 const LineItems & lineItems) 00517 { 00518 checkInvariants(); 00519 Amount amountUnspent = amountAuthorized - amountPaid; 00520 balance += amountUnspent; 00521 commitmentsRetired += amountAuthorized; 00522 spent += amountPaid; 00523 this->lineItems += lineItems; 00524 checkInvariants(); 00525 } 00526 00527 00528 /*************************************************************************/ 00529 /* SPEND AUTHORIZATION */ 00530 /*************************************************************************/ 00531 00532 bool authorizeBid(const std::string & item, 00533 Amount amount) 00534 { 00535 checkInvariants(); 00536 00537 if (!balance.hasAvailable(amount)) 00538 return false; // no budget balance 00539 00540 attachBid(item, amount); 00541 00542 balance -= amount; 00543 commitmentsMade += amount; 00544 00545 checkInvariants(); 00546 00547 return true; 00548 } 00549 00550 void commitBid(const std::string & item, 00551 Amount amountPaid, 00552 const LineItems & lineItems) 00553 { 00554 commitDetachedBid(detachBid(item), amountPaid, lineItems); 00555 } 00556 00557 void cancelBid(const std::string & item) 00558 { 00559 commitDetachedBid(detachBid(item), Amount(), LineItems()); 00560 } 00561 00562 Amount detachBid(const std::string & item) 00563 { 00564 checkInvariants(); 00565 00566 auto cit = commitments.find(item); 00567 if (cit == commitments.end()) 00568 throw ML::Exception("unknown commitment being committed"); 00569 00570 Amount amountAuthorized = cit->second.amount; 00571 commitments.erase(cit); 00572 00573 checkInvariants(); 00574 00575 detachedBids++; 00576 00577 return amountAuthorized; 00578 } 00579 00580 void attachBid(const std::string & item, 00581 Amount amount) 00582 { 00583 Date now = Date::now(); 00584 auto c = commitments.insert(make_pair(item, Commitment(amount, now))); 00585 if (!c.second) 00586 throw ML::Exception("attempt to re-open commitment"); 00587 attachedBids++; 00588 } 00589 00590 /*************************************************************************/ 00591 /* SYNCHRONIZATION */ 00592 /*************************************************************************/ 00593 00594 const Account syncToMaster(Account & masterAccount) const 00595 { 00596 checkInvariants(); 00597 00598 masterAccount.checkInvariants(); 00599 00600 CurrencyPool newCommitmentsMade 00601 = commitmentsMade - masterAccount.commitmentsMade; 00602 CurrencyPool newCommitmentsRetired 00603 = commitmentsRetired - masterAccount.commitmentsRetired; 00604 CurrencyPool newSpend 00605 = spent - masterAccount.spent; 00606 00607 ExcAssert(newCommitmentsMade.isNonNegative()); 00608 ExcAssert(newCommitmentsRetired.isNonNegative()); 00609 ExcAssert(newSpend.isNonNegative()); 00610 00611 masterAccount.commitmentsRetired = commitmentsRetired; 00612 masterAccount.commitmentsMade = commitmentsMade; 00613 masterAccount.spent = spent; 00614 00615 masterAccount.balance 00616 += (newCommitmentsRetired - newCommitmentsMade - newSpend); 00617 00618 masterAccount.lineItems = lineItems; 00619 00620 masterAccount.checkInvariants("syncToMaster"); 00621 checkInvariants(); 00622 00623 return masterAccount; 00624 } 00625 00626 void syncFromMaster(const Account & masterAccount) 00627 { 00628 checkInvariants(); 00629 masterAccount.checkInvariants(); 00630 00631 // net budget: balance assuming spent, commitments are zero 00632 netBudget = masterAccount.getNetBudget(); 00633 balance = netBudget + commitmentsRetired 00634 - commitmentsMade - spent; 00635 00636 checkInvariants(); 00637 } 00638 00648 void initializeAndMergeState(const Account & masterAccount) 00649 { 00650 // We have to tally up the fields from the master and the current 00651 // status. 00652 00653 checkInvariants(); 00654 masterAccount.checkInvariants(); 00655 00656 // net budget: balance assuming spent, commitments are zero 00657 netBudget = (masterAccount.budgetIncreases 00658 - masterAccount.budgetDecreases 00659 + masterAccount.recycledIn 00660 - masterAccount.recycledOut 00661 + masterAccount.adjustmentsIn 00662 - masterAccount.adjustmentsOut 00663 + masterAccount.allocatedIn 00664 - masterAccount.allocatedOut); 00665 commitmentsMade += masterAccount.commitmentsMade; 00666 commitmentsRetired += masterAccount.commitmentsRetired; 00667 spent += masterAccount.spent; 00668 lineItems += masterAccount.lineItems; 00669 00670 balance = netBudget + commitmentsRetired - commitmentsMade - spent; 00671 00672 checkInvariants(); 00673 } 00674 00675 /* LOGGING */ 00676 uint32_t attachedBids; 00677 uint32_t detachedBids; 00678 uint32_t lastExpiredCommitments; 00679 00680 void logBidEvents(const Datacratic::EventRecorder & eventRecorder, 00681 const std::string & accountKey); 00682 }; 00683 00684 00685 /*****************************************************************************/ 00686 /* ACCOUNT SUMMARY */ 00687 /*****************************************************************************/ 00688 00691 struct AccountSummary { 00692 CurrencyPool budget; 00693 CurrencyPool inFlight; 00694 CurrencyPool spent; 00695 CurrencyPool available; 00696 00697 Account account; 00698 00699 void addChild(const std::string & name, 00700 const AccountSummary & child, 00701 bool addInSubaccounts) 00702 { 00703 if (addInSubaccounts) 00704 subAccounts[name] = child; 00705 inFlight += child.inFlight; 00706 spent += child.spent; 00707 } 00708 00709 void dump(std::ostream & stream, 00710 int indent = 0, 00711 const std::string & name = "toplevel") const 00712 { 00713 stream << std::string(indent, ' ') 00714 << name 00715 << " b:" << budget 00716 << " s:" << spent 00717 << " i:" << inFlight 00718 << std::endl; 00719 for (const auto & sa: subAccounts) { 00720 sa.second.dump(stream, indent + 2, sa.first); 00721 } 00722 } 00723 00724 Json::Value toJson(bool simplified = false) const 00725 { 00726 Json::Value result; 00727 00728 result["md"]["objectType"] 00729 = simplified ? "AccountSimpleSummary" : "AccountSummary"; 00730 result["md"]["version"] = 1; 00731 result["budget"] = budget.toJson(); 00732 result["spent"] = spent.toJson(); 00733 result["available"] = available.toJson(); 00734 result["inFlight"] = inFlight.toJson(); 00735 if (!simplified) { 00736 result["account"] = account.toJson(); 00737 for (const auto & sa: subAccounts) { 00738 result["subAccounts"][sa.first] = sa.second.toJson(); 00739 } 00740 } 00741 00742 return result; 00743 } 00744 00745 static AccountSummary fromJson(const Json::Value & val) 00746 { 00747 AccountSummary result; 00748 00749 ExcAssertEqual(val["md"]["objectType"].asString(), "AccountSummary"); 00750 ExcAssertEqual(val["md"]["version"].asInt(), 1); 00751 00752 result.budget = CurrencyPool::fromJson(val["budget"]); 00753 result.inFlight = CurrencyPool::fromJson(val["inFlight"]); 00754 result.spent = CurrencyPool::fromJson(val["spent"]); 00755 result.available = CurrencyPool::fromJson(val["available"]); 00756 00757 result.account = Account::fromJson(val["account"]); 00758 auto & sa = val["subAccounts"]; 00759 for (auto it = sa.begin(), end = sa.end(); it != end; ++it) { 00760 result.subAccounts[it.memberName()] 00761 = AccountSummary::fromJson(*it); 00762 } 00763 00764 return result; 00765 } 00766 00767 std::map<std::string, AccountSummary> subAccounts; 00768 }; 00769 00770 inline std::ostream & 00771 operator << (std::ostream & stream, const AccountSummary & summary) 00772 { 00773 summary.dump(stream); 00774 return stream; 00775 } 00776 00777 /*****************************************************************************/ 00778 /* ACCOUNTS */ 00779 /*****************************************************************************/ 00780 00781 struct Accounts { 00782 Accounts() 00783 : sessionStart(Datacratic::Date::now()) 00784 { 00785 } 00786 00787 Datacratic::Date sessionStart; 00788 00789 struct AccountInfo: public Account { 00790 std::set<AccountKey> children; 00791 00792 /* spend tracking across sessions */ 00793 CurrencyPool initialSpent; 00794 }; 00795 00796 const Account createAccount(const AccountKey & account, 00797 AccountType type) 00798 { 00799 Guard guard(lock); 00800 if (account.empty()) 00801 throw ML::Exception("can't create account with empty key"); 00802 return ensureAccount(account, type); 00803 } 00804 00805 void restoreAccount(const AccountKey & accountKey, 00806 const Json::Value & jsonValue, 00807 bool overwrite = false) { 00808 Guard guard(lock); 00809 00810 // if (accounts.count(accountKey) != 0 and !overwrite) { 00811 // throw ML::Exception("an account already exists with that name"); 00812 // } 00813 00814 Account validAccount = validAccount.fromJson(jsonValue); 00815 AccountInfo & newAccount = ensureAccount(accountKey, validAccount.type); 00816 newAccount.type = AT_SPEND; 00817 newAccount.type = validAccount.type; 00818 newAccount.budgetIncreases = validAccount.budgetIncreases; 00819 newAccount.budgetDecreases = validAccount.budgetDecreases; 00820 newAccount.spent = validAccount.spent; 00821 newAccount.recycledIn = validAccount.recycledIn; 00822 newAccount.recycledOut = validAccount.recycledOut; 00823 newAccount.allocatedIn = validAccount.allocatedIn; 00824 newAccount.allocatedOut = validAccount.allocatedOut; 00825 newAccount.commitmentsMade = validAccount.commitmentsMade; 00826 newAccount.commitmentsRetired = validAccount.commitmentsRetired; 00827 newAccount.adjustmentsIn = validAccount.adjustmentsIn; 00828 newAccount.adjustmentsOut = validAccount.adjustmentsOut; 00829 newAccount.balance = validAccount.balance; 00830 newAccount.lineItems = validAccount.lineItems; 00831 newAccount.adjustmentLineItems = validAccount.adjustmentLineItems; 00832 } 00833 00834 const Account createBudgetAccount(const AccountKey & account) 00835 { 00836 Guard guard(lock); 00837 if (account.empty()) 00838 throw ML::Exception("can't create account with empty key"); 00839 return ensureAccount(account, AT_BUDGET); 00840 } 00841 00842 const Account createSpendAccount(const AccountKey & account) 00843 { 00844 Guard guard(lock); 00845 if (account.size() < 2) 00846 throw ML::Exception("commitment account must have parent"); 00847 return ensureAccount(account, AT_SPEND); 00848 } 00849 00850 const AccountInfo getAccount(const AccountKey & account) const 00851 { 00852 Guard guard(lock); 00853 return getAccountImpl(account); 00854 } 00855 00856 void checkInvariants() const 00857 { 00858 Guard guard(lock); 00859 for (auto & a: accounts) { 00860 a.second.checkInvariants(); 00861 } 00862 } 00863 00864 Json::Value toJson() const 00865 { 00866 Json::Value result(Json::objectValue); 00867 00868 Guard guard(lock); 00869 for (auto & a: accounts) { 00870 result[a.first.toString()] = a.second.toJson(); 00871 } 00872 00873 return result; 00874 } 00875 00876 static Accounts fromJson(const Json::Value & json); 00877 00878 /*************************************************************************/ 00879 /* BUDGET OPERATIONS */ 00880 /*************************************************************************/ 00881 00882 /* These operations are assocated with putting money into the system. */ 00883 00884 const Account setBudget(const AccountKey & topLevelAccount, 00885 const CurrencyPool & newBudget) 00886 { 00887 using namespace std; 00888 //cerr << "setBudget with newBudget " << newBudget << endl; 00889 00890 Guard guard(lock); 00891 if (topLevelAccount.size() != 1) 00892 throw ML::Exception("can't setBudget except at top level"); 00893 auto & a = ensureAccount(topLevelAccount, AT_BUDGET); 00894 a.setBudget(newBudget); 00895 return a; 00896 } 00897 00904 const Account setBalance(const AccountKey & account, 00905 CurrencyPool amount, 00906 AccountType typeToCreate) 00907 { 00908 Guard guard(lock); 00909 00910 if (typeToCreate != AT_NONE && !accounts.count(account)) { 00911 auto & a = ensureAccount(account, typeToCreate); 00912 a.setBalance(getParentAccount(account), amount); 00913 return a; 00914 } 00915 else { 00916 auto & a = getAccountImpl(account); 00917 00918 #if 0 00919 using namespace std; 00920 if (a.type == AT_BUDGET) 00921 cerr << Date::now() 00922 << " setBalance " << account << " " << " from " << a.balance 00923 << " to " << amount << endl; 00924 #endif 00925 00926 a.setBalance(getParentAccount(account), amount); 00927 return a; 00928 } 00929 } 00930 00931 const CurrencyPool getBalance(const AccountKey & account) const 00932 { 00933 Guard guard(lock); 00934 auto it = accounts.find(account); 00935 if (it == accounts.end()) 00936 return CurrencyPool(); 00937 return it->second.balance; 00938 } 00939 00940 00941 /*************************************************************************/ 00942 /* TRANSFER OPERATIONS */ 00943 /*************************************************************************/ 00944 00945 /* These operations are two-sided and involve transferring between a 00946 parent account and a child account. 00947 */ 00948 00949 void recuperate(const AccountKey & account) 00950 { 00951 Guard guard(lock); 00952 getAccountImpl(account).recuperateTo(getParentAccount(account)); 00953 } 00954 00955 AccountSummary getAccountSummary(const AccountKey & account, 00956 int maxDepth = -1) const 00957 { 00958 Guard guard(lock); 00959 return getAccountSummaryImpl(account, 0, maxDepth); 00960 } 00961 00962 Json::Value 00963 getAccountSummariesJson(bool simplified = false, int maxDepth = -1) 00964 const 00965 { 00966 Guard guard(lock); 00967 00968 Json::Value summaries; 00969 00970 for (const auto & it: accounts) { 00971 const AccountKey & key = it.first; 00972 AccountSummary summary = getAccountSummaryImpl(key, 0, maxDepth); 00973 summaries[key.toString()] = summary.toJson(simplified); 00974 } 00975 00976 return summaries; 00977 } 00978 00979 const Account importSpend(const AccountKey & account, 00980 const CurrencyPool & amount) 00981 { 00982 Guard guard(lock); 00983 auto & a = getAccountImpl(account); 00984 a.importSpend(amount); 00985 return a; 00986 } 00987 00988 /*************************************************************************/ 00989 /* HIGH LEVEL OPERATIONS */ 00990 /*************************************************************************/ 00991 00992 /* These are higher-level opertions that build on top of the others in 00993 order to make a given condition true. 00994 */ 00995 00996 /*************************************************************************/ 00997 /* SYNCHRONIZATION OPERATIONS */ 00998 /*************************************************************************/ 00999 01000 const Account syncFromShadow(const AccountKey & account, 01001 const ShadowAccount & shadow) 01002 { 01003 Guard guard(lock); 01004 01005 // In the case that an account was added and the banker crashed 01006 // before it could be written to persistent storage, we need to 01007 // create the empty account here. 01008 if (!accounts.count(account)) 01009 return shadow.syncToMaster(ensureAccount(account, AT_SPEND)); 01010 01011 return shadow.syncToMaster(getAccountImpl(account)); 01012 } 01013 01014 /* "Out of sync" here means that the in-memory version of the relevant 01015 accounts is obsolete compared to the version stored in the Redis 01016 backend */ 01017 void markAccountOutOfSync(const AccountKey & account) 01018 { 01019 Guard guard(lock); 01020 01021 outOfSyncAccounts.insert(account); 01022 } 01023 01024 bool isAccountOutOfSync(const AccountKey & account) const 01025 { 01026 Guard guard(lock); 01027 01028 return (outOfSyncAccounts.count(account) > 0); 01029 } 01030 01031 01033 /* "Inconsistent" here means that there is a mismatch between the members 01034 * used in money transfers for a given Account and the corresponding 01035 * members in its subaccounts: allocatedOut and budgetIncreases, 01036 * recycledIn and recycedOut, ... 01037 */ 01038 void ensureInterAccountConsistency(); 01039 bool isAccountInconsistent(const AccountKey & account) const 01040 { 01041 Guard guard(lock); 01042 01043 return (inconsistentAccounts.count(account) > 0); 01044 } 01045 01046 /* Returns whether the budgetIncreases of subaccounts are consistent with 01047 the allocatedOut of the top-account, recursively. 01048 maxRecusion: -1 = infinity 01049 */ 01050 bool checkBudgetConsistency(const AccountKey & accountKey, 01051 int maxRecursion = -1) const; 01052 01053 /* Returns the amounts in recycledIn and recycledOut that were transferred 01054 * strictly from and to the parent account. */ 01055 void getRecycledUp(const AccountKey & accountKey, 01056 CurrencyPool & recycledInUp, 01057 CurrencyPool & recycledOutUp) const; 01058 01059 private: 01060 friend class ShadowAccounts; 01061 01062 typedef ML::Spinlock Lock; 01063 typedef std::unique_lock<Lock> Guard; 01064 mutable Lock lock; 01065 01066 typedef std::map<AccountKey, AccountInfo> AccountMap; 01067 AccountMap accounts; 01068 01069 typedef std::unordered_set<AccountKey> AccountSet; 01070 AccountSet outOfSyncAccounts; 01071 AccountSet inconsistentAccounts; 01072 01073 public: 01074 std::vector<AccountKey> 01075 getAccountKeys(const AccountKey & prefix = AccountKey(), 01076 int maxDepth = -1) const 01077 { 01078 Guard guard(lock); 01079 01080 std::vector<AccountKey> result; 01081 01082 for (auto it = accounts.lower_bound(prefix), end = accounts.end(); 01083 it != accounts.end() && it->first.hasPrefix(prefix); ++it) { 01084 if (maxDepth == -1 || it->first.size() <= maxDepth) 01085 result.push_back(it->first); 01086 } 01087 return result; 01088 } 01089 01090 void 01091 forEachAccount(const std::function<void (const AccountKey &, 01092 const Account &)> 01093 & onAccount) const 01094 { 01095 Guard guard(lock); 01096 01097 for (auto & a: accounts) { 01098 onAccount(a.first, a.second); 01099 } 01100 } 01101 01102 size_t size() const 01103 { 01104 Guard guard(lock); 01105 return accounts.size(); 01106 } 01107 01108 bool empty() const 01109 { 01110 Guard guard(lock); 01111 return accounts.empty(); 01112 } 01113 01115 Accounts getAccounts(const AccountKey & root, int maxDepth = 0) 01116 { 01117 Accounts result; 01118 Guard guard(lock); 01119 01120 std::function<void (const AccountKey &, int, int)> doAccount 01121 = [&] (const AccountKey & key, int depth, int maxDepth) 01122 { 01123 auto it = accounts.find(key); 01124 if (it == accounts.end()) 01125 return; 01126 result.ensureAccount(it->first, it->second.type) = it->second; 01127 01128 if (depth >= maxDepth) 01129 return; 01130 01131 for (auto & k: it->second.children) 01132 doAccount(k, depth + 1, maxDepth); 01133 }; 01134 01135 doAccount(root, 0, maxDepth); 01136 01137 return result; 01138 } 01139 01140 private: 01141 01142 AccountInfo & ensureAccount(const AccountKey & accountKey, 01143 AccountType type) 01144 { 01145 ExcAssertGreaterEqual(accountKey.size(), 1); 01146 01147 auto it = accounts.find(accountKey); 01148 if (it != accounts.end()) { 01149 ExcAssertEqual(it->second.type, type); 01150 return it->second; 01151 } 01152 else { 01153 if (accountKey.size() == 1) { 01154 ExcAssertEqual(type, AT_BUDGET); 01155 } 01156 else { 01157 AccountInfo & parent 01158 = ensureAccount(accountKey.parent(), AT_BUDGET); 01159 parent.children.insert(accountKey); 01160 } 01161 01162 auto & result = accounts[accountKey]; 01163 result.type = type; 01164 return result; 01165 } 01166 } 01167 01168 AccountInfo & getAccountImpl(const AccountKey & account) 01169 { 01170 auto it = accounts.find(account); 01171 if (it == accounts.end()) 01172 throw ML::Exception("couldn't get account"); 01173 return it->second; 01174 } 01175 01176 const AccountInfo & getAccountImpl(const AccountKey & account) const 01177 { 01178 auto it = accounts.find(account); 01179 if (it == accounts.end()) 01180 throw ML::Exception("couldn't get account"); 01181 return it->second; 01182 } 01183 01184 Account & getParentAccount(const AccountKey & accountKey) 01185 { 01186 if (accountKey.size() < 2) 01187 throw ML::Exception("account has no parent"); 01188 01189 AccountKey parentKey = accountKey; 01190 parentKey.pop_back(); 01191 01192 Account & result = getAccountImpl(parentKey); 01193 ExcAssertEqual(result.type, AT_BUDGET); 01194 return result; 01195 } 01196 01197 void forEachChildAccount(const AccountKey & account, 01198 std::function<void (const AccountKey & key)> cb) const 01199 { 01200 auto & info = getAccountImpl(account); 01201 for (const AccountKey & ch: info.children) 01202 cb(ch); 01203 } 01204 01205 AccountSummary getAccountSummaryImpl(const AccountKey & account, 01206 int depth, int maxDepth) const 01207 { 01208 AccountSummary result; 01209 01210 const Account & a = getAccountImpl(account); 01211 01212 result.account = a; 01213 result.spent = a.spent; 01214 result.budget = a.getBudget(); 01215 result.inFlight = a.commitmentsMade - a.commitmentsRetired; 01216 01217 auto doChildAccount = [&] (const AccountKey & key) { 01218 auto childSummary = getAccountSummaryImpl(key, depth + 1, 01219 maxDepth); 01220 result.addChild(key.back(), childSummary, 01221 maxDepth == -1 || depth < maxDepth); 01222 }; 01223 forEachChildAccount(account, doChildAccount); 01224 01225 result.available = result.budget - result.spent - result.inFlight; 01226 01227 return result; 01228 } 01229 01230 bool checkBudgetConsistencyImpl(const AccountKey & accountKey, 01231 int maxRecursion, int currentLevel) const; 01232 }; 01233 01234 01235 /*****************************************************************************/ 01236 /* SHADOW ACCOUNTS */ 01237 /*****************************************************************************/ 01238 01239 struct ShadowAccounts { 01244 std::function<void (AccountKey)> onNewAccount; 01245 01246 const ShadowAccount activateAccount(const AccountKey & account) 01247 { 01248 Guard guard(lock); 01249 return getAccountImpl(account); 01250 } 01251 01252 const ShadowAccount syncFromMaster(const AccountKey & account, 01253 const Account & master) 01254 { 01255 Guard guard(lock); 01256 auto & a = getAccountImpl(account); 01257 ExcAssert(!a.uninitialized); 01258 a.syncFromMaster(master); 01259 return a; 01260 } 01261 01265 const ShadowAccount 01266 initializeAndMergeState(const AccountKey & account, 01267 const Account & master) 01268 { 01269 Guard guard(lock); 01270 auto & a = getAccountImpl(account); 01271 ExcAssert(a.uninitialized); 01272 a.initializeAndMergeState(master); 01273 a.uninitialized = false; 01274 return a; 01275 } 01276 01277 void checkInvariants() const 01278 { 01279 Guard guard(lock); 01280 for (auto & a: accounts) { 01281 a.second.checkInvariants(); 01282 } 01283 } 01284 01285 const ShadowAccount getAccount(const AccountKey & accountKey) const 01286 { 01287 Guard guard(lock); 01288 return getAccountImpl(accountKey); 01289 } 01290 01291 bool accountExists(const AccountKey & accountKey) const 01292 { 01293 Guard guard(lock); 01294 return accounts.count(accountKey); 01295 } 01296 01297 bool createAccountAtomic(const AccountKey & accountKey) 01298 { 01299 Guard guard(lock); 01300 01301 AccountEntry & account = getAccountImpl(accountKey, false /* call onCreate */); 01302 bool result = account.first; 01303 01304 // record that this account creation is requested for the first time 01305 account.first = false; 01306 return result; 01307 01308 } 01309 01310 /*************************************************************************/ 01311 /* SYNCHRONIZATION */ 01312 /*************************************************************************/ 01313 01314 void syncTo(Accounts & master) const 01315 { 01316 Guard guard1(lock); 01317 Guard guard2(master.lock); 01318 01319 for (auto & a: accounts) 01320 a.second.syncToMaster(master.getAccountImpl(a.first)); 01321 } 01322 01323 void syncFrom(const Accounts & master) 01324 { 01325 Guard guard1(lock); 01326 Guard guard2(master.lock); 01327 01328 for (auto & a: accounts) { 01329 a.second.syncFromMaster(master.getAccountImpl(a.first)); 01330 if (master.outOfSyncAccounts.count(a.first) > 0) { 01331 outOfSyncAccounts.insert(a.first); 01332 } 01333 } 01334 } 01335 01336 void sync(Accounts & master) 01337 { 01338 Guard guard1(lock); 01339 Guard guard2(master.lock); 01340 01341 for (auto & a: accounts) { 01342 a.second.syncToMaster(master.getAccountImpl(a.first)); 01343 a.second.syncFromMaster(master.getAccountImpl(a.first)); 01344 } 01345 } 01346 01347 bool isInitialized(const AccountKey & accountKey) const 01348 { 01349 Guard guard(lock); 01350 return !getAccountImpl(accountKey).uninitialized; 01351 } 01352 01353 /*************************************************************************/ 01354 /* BID OPERATIONS */ 01355 /*************************************************************************/ 01356 01357 bool authorizeBid(const AccountKey & accountKey, 01358 const std::string & item, 01359 Amount amount) 01360 { 01361 Guard guard(lock); 01362 return (outOfSyncAccounts.count(accountKey) == 0 01363 && getAccountImpl(accountKey).authorizeBid(item, amount)); 01364 } 01365 01366 void commitBid(const AccountKey & accountKey, 01367 const std::string & item, 01368 Amount amountPaid, 01369 const LineItems & lineItems) 01370 { 01371 Guard guard(lock); 01372 return getAccountImpl(accountKey).commitBid(item, amountPaid, lineItems); 01373 } 01374 01375 void cancelBid(const AccountKey & accountKey, 01376 const std::string & item) 01377 { 01378 Guard guard(lock); 01379 return getAccountImpl(accountKey).cancelBid(item); 01380 } 01381 01382 void forceWinBid(const AccountKey & accountKey, 01383 Amount amountPaid, 01384 const LineItems & lineItems) 01385 { 01386 Guard guard(lock); 01387 return getAccountImpl(accountKey).forceWinBid(amountPaid, lineItems); 01388 } 01389 01391 void commitDetachedBid(const AccountKey & accountKey, 01392 Amount amountAuthorized, 01393 Amount amountPaid, 01394 const LineItems & lineItems) 01395 { 01396 Guard guard(lock); 01397 return getAccountImpl(accountKey) 01398 .commitDetachedBid(amountAuthorized, amountPaid, lineItems); 01399 } 01400 01401 Amount detachBid(const AccountKey & accountKey, 01402 const std::string & item) 01403 { 01404 Guard guard(lock); 01405 return getAccountImpl(accountKey).detachBid(item); 01406 } 01407 01408 void attachBid(const AccountKey & accountKey, 01409 const std::string & item, 01410 Amount amountAuthorized) 01411 { 01412 Guard guard(lock); 01413 getAccountImpl(accountKey).attachBid(item, amountAuthorized); 01414 } 01415 01416 void logBidEvents(const Datacratic::EventRecorder & eventRecorder); 01417 01418 private: 01419 01420 struct AccountEntry : public ShadowAccount { 01421 AccountEntry(bool uninitialized = true, bool first = true) 01422 : uninitialized(uninitialized), first(first) 01423 { 01424 } 01425 01436 bool uninitialized; 01437 bool first; 01438 }; 01439 01440 AccountEntry & getAccountImpl(const AccountKey & account, 01441 bool callOnNewAccount = true) 01442 { 01443 auto it = accounts.find(account); 01444 if (it == accounts.end()) { 01445 if (callOnNewAccount && onNewAccount) 01446 onNewAccount(account); 01447 it = accounts.insert(std::make_pair(account, AccountEntry())) 01448 .first; 01449 } 01450 return it->second; 01451 } 01452 01453 const AccountEntry & getAccountImpl(const AccountKey & account) const 01454 { 01455 auto it = accounts.find(account); 01456 if (it == accounts.end()) 01457 throw ML::Exception("getting unknown account " + account.toString()); 01458 return it->second; 01459 } 01460 01461 typedef ML::Spinlock Lock; 01462 typedef std::unique_lock<Lock> Guard; 01463 mutable Lock lock; 01464 01465 typedef std::map<AccountKey, AccountEntry> AccountMap; 01466 AccountMap accounts; 01467 01468 typedef std::unordered_set<AccountKey> AccountSet; 01469 AccountSet outOfSyncAccounts; 01470 01471 public: 01472 std::vector<AccountKey> 01473 getAccountKeys(const AccountKey & prefix = AccountKey()) const 01474 { 01475 Guard guard(lock); 01476 01477 std::vector<AccountKey> result; 01478 01479 for (auto it = accounts.lower_bound(prefix), end = accounts.end(); 01480 it != accounts.end() && it->first.hasPrefix(prefix); ++it) { 01481 result.push_back(it->first); 01482 } 01483 return result; 01484 } 01485 01486 void 01487 forEachAccount(const std::function<void (const AccountKey &, 01488 const ShadowAccount &)> & 01489 onAccount) const 01490 { 01491 Guard guard(lock); 01492 01493 for (auto & a: accounts) { 01494 onAccount(a.first, a.second); 01495 } 01496 } 01497 01498 void 01499 forEachInitializedAccount(const std::function<void (const AccountKey &, 01500 const ShadowAccount &)> & onAccount) 01501 { 01502 Guard guard(lock); 01503 01504 for (auto & a: accounts) { 01505 if (a.second.uninitialized) 01506 continue; 01507 onAccount(a.first, a.second); 01508 } 01509 } 01510 01511 size_t size() const 01512 { 01513 Guard guard(lock); 01514 return accounts.size(); 01515 } 01516 01517 bool empty() const 01518 { 01519 Guard guard(lock); 01520 return accounts.empty(); 01521 } 01522 }; 01523 01524 } // namespace RTBKIT 01525