Skip to content

Commit

Permalink
Core: introduce new eventHistoryTTL and minBidCacheTTL settings t…
Browse files Browse the repository at this point in the history
…o control memory usage (#10308)

* Core: introduce `eventHistoryTTL` to periodically purge events from event log

* Core: introduce `preserveBidCache` to drop stale auctions

* `minBidCacheTTL` instead of `preserveBidCache`
  • Loading branch information
dgirardi authored Sep 7, 2023
1 parent c91f337 commit 2042919
Show file tree
Hide file tree
Showing 12 changed files with 705 additions and 130 deletions.
7 changes: 5 additions & 2 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ import {bidderSettings} from './bidderSettings.js';
import * as events from './events.js';
import adapterManager from './adapterManager.js';
import CONSTANTS from './constants.json';
import {GreedyPromise} from './utils/promise.js';
import {defer, GreedyPromise} from './utils/promise.js';
import {useMetrics} from './utils/perfMetrics.js';
import {adjustCpm} from './utils/cpm.js';
import {getGlobal} from './prebidGlobal.js';
Expand Down Expand Up @@ -143,6 +143,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
const _auctionId = auctionId || generateUUID();
const _timeout = cbTimeout;
const _timelyBidders = new Set();
const done = defer();
let _bidsRejected = [];
let _callback = callback;
let _bidderRequests = [];
Expand Down Expand Up @@ -193,7 +194,6 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
if (cleartimer) {
clearTimeout(_timer);
}

if (_auctionEnd === undefined) {
let timedOutBidders = [];
if (timedOut) {
Expand All @@ -209,6 +209,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
metrics.checkpoint('auctionEnd');
metrics.timeBetween('requestBids', 'auctionEnd', 'requestBids.total');
metrics.timeBetween('callBids', 'auctionEnd', 'requestBids.callBids');
done.resolve();

events.emit(CONSTANTS.EVENTS.AUCTION_END, getProperties());
bidsBackCallback(_adUnits, function () {
Expand Down Expand Up @@ -392,6 +393,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
setBidTargeting,
getWinningBids: () => _winningBids,
getAuctionStart: () => _auctionStart,
getAuctionEnd: () => _auctionEnd,
getTimeout: () => _timeout,
getAuctionId: () => _auctionId,
getAuctionStatus: () => _auctionStatus,
Expand All @@ -403,6 +405,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
getNonBids: () => _nonBids,
getFPD: () => ortb2Fragments,
getMetrics: () => metrics,
end: done.promise
};
}

Expand Down
121 changes: 75 additions & 46 deletions src/auctionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
* @property {function(): void} clearAllAuctions - clear all auctions for testing
*/

import { uniques, flatten, logWarn } from './utils.js';
import { uniques, logWarn } from './utils.js';
import { newAuction, getStandardBidderSettings, AUCTION_COMPLETED } from './auction.js';
import {find} from './polyfill.js';
import {AuctionIndex} from './auctionIndex.js';
import CONSTANTS from './constants.json';
import {useMetrics} from './utils/perfMetrics.js';
import {ttlCollection} from './utils/ttlCollection.js';
import {getTTL, onTTLBufferChange} from './bidTTL.js';
import {config} from './config.js';

const CACHE_TTL_SETTING = 'minBidCacheTTL';

/**
* Creates new instance of auctionManager. There will only be one instance of auctionManager but
Expand All @@ -33,15 +37,42 @@ import {useMetrics} from './utils/perfMetrics.js';
* @returns {AuctionManager} auctionManagerInstance
*/
export function newAuctionManager() {
const _auctions = [];
let minCacheTTL = null;

const _auctions = ttlCollection({
startTime: (au) => au.end.then(() => au.getAuctionEnd()),
ttl: (au) => minCacheTTL == null ? null : au.end.then(() => {
return Math.max(minCacheTTL, ...au.getBidsReceived().map(getTTL)) * 1000
}),
});

onTTLBufferChange(() => {
if (minCacheTTL != null) _auctions.refresh();
})

config.getConfig(CACHE_TTL_SETTING, (cfg) => {
const prev = minCacheTTL;
minCacheTTL = cfg?.[CACHE_TTL_SETTING];
minCacheTTL = typeof minCacheTTL === 'number' ? minCacheTTL : null;
if (prev !== minCacheTTL) {
_auctions.refresh();
}
})

const auctionManager = {};

function getAuction(auctionId) {
for (const auction of _auctions) {
if (auction.getAuctionId() === auctionId) return auction;
}
}

auctionManager.addWinningBid = function(bid) {
const metrics = useMetrics(bid.metrics);
metrics.checkpoint('bidWon');
metrics.timeBetween('auctionEnd', 'bidWon', 'render.pending');
metrics.timeBetween('requestBids', 'bidWon', 'render.e2e');
const auction = find(_auctions, auction => auction.getAuctionId() === bid.auctionId);
const auction = getAuction(bid.auctionId);
if (auction) {
bid.status = CONSTANTS.BID_STATUS.RENDERED;
auction.addWinningBid(bid);
Expand All @@ -50,56 +81,53 @@ export function newAuctionManager() {
}
};

auctionManager.getAllWinningBids = function() {
return _auctions.map(auction => auction.getWinningBids())
.reduce(flatten, []);
};

auctionManager.getBidsRequested = function() {
return _auctions.map(auction => auction.getBidRequests())
.reduce(flatten, []);
};

auctionManager.getNoBids = function() {
return _auctions.map(auction => auction.getNoBids())
.reduce(flatten, []);
};

auctionManager.getBidsReceived = function() {
return _auctions.map((auction) => {
if (auction.getAuctionStatus() === AUCTION_COMPLETED) {
return auction.getBidsReceived();
Object.entries({
getAllWinningBids: {
name: 'getWinningBids',
},
getBidsRequested: {
name: 'getBidRequests'
},
getNoBids: {},
getAdUnits: {},
getBidsReceived: {
pre(auction) {
return auction.getAuctionStatus() === AUCTION_COMPLETED;
}
}).reduce(flatten, [])
.filter(bid => bid);
};
},
getAdUnitCodes: {
post: uniques,
}
}).forEach(([mgrMethod, {name = mgrMethod, pre, post}]) => {
const mapper = pre == null
? (auction) => auction[name]()
: (auction) => pre(auction) ? auction[name]() : [];
const filter = post == null
? (items) => items
: (items) => items.filter(post)
auctionManager[mgrMethod] = () => {
return filter(_auctions.toArray().flatMap(mapper));
}
})

function allBidsReceived() {
return _auctions.toArray().flatMap(au => au.getBidsReceived())
}

auctionManager.getAllBidsForAdUnitCode = function(adUnitCode) {
return _auctions.map((auction) => {
return auction.getBidsReceived();
}).reduce(flatten, [])
return allBidsReceived()
.filter(bid => bid && bid.adUnitCode === adUnitCode)
};

auctionManager.getAdUnits = function() {
return _auctions.map(auction => auction.getAdUnits())
.reduce(flatten, []);
};

auctionManager.getAdUnitCodes = function() {
return _auctions.map(auction => auction.getAdUnitCodes())
.reduce(flatten, [])
.filter(uniques);
};

auctionManager.createAuction = function(opts) {
const auction = newAuction(opts);
_addAuction(auction);
return auction;
};

auctionManager.findBidByAdId = function(adId) {
return find(_auctions.map(auction => auction.getBidsReceived()).reduce(flatten, []), bid => bid.adId === adId);
return allBidsReceived()
.find(bid => bid.adId === adId);
};

auctionManager.getStandardBidderAdServerTargeting = function() {
Expand All @@ -111,24 +139,25 @@ export function newAuctionManager() {
if (bid) bid.status = status;

if (bid && status === CONSTANTS.BID_STATUS.BID_TARGETING_SET) {
const auction = find(_auctions, auction => auction.getAuctionId() === bid.auctionId);
const auction = getAuction(bid.auctionId);
if (auction) auction.setBidTargeting(bid);
}
}

auctionManager.getLastAuctionId = function() {
return _auctions.length && _auctions[_auctions.length - 1].getAuctionId()
const auctions = _auctions.toArray();
return auctions.length && auctions[auctions.length - 1].getAuctionId()
};

auctionManager.clearAllAuctions = function() {
_auctions.length = 0;
_auctions.clear();
}

function _addAuction(auction) {
_auctions.push(auction);
_auctions.add(auction);
}

auctionManager.index = new AuctionIndex(() => _auctions);
auctionManager.index = new AuctionIndex(() => _auctions.toArray());

return auctionManager;
}
Expand Down
25 changes: 25 additions & 0 deletions src/bidTTL.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {config} from './config.js';
import {logError} from './utils.js';
let TTL_BUFFER = 1;

const listeners = [];

config.getConfig('ttlBuffer', (cfg) => {
if (typeof cfg.ttlBuffer === 'number') {
const prev = TTL_BUFFER;
TTL_BUFFER = cfg.ttlBuffer;
if (prev !== TTL_BUFFER) {
listeners.forEach(l => l(TTL_BUFFER))
}
} else {
logError('Invalid value for ttlBuffer', cfg.ttlBuffer);
}
})

export function getTTL(bid) {
return bid.ttl - (bid.hasOwnProperty('ttlBuffer') ? bid.ttlBuffer : TTL_BUFFER);
}

export function onTTLBufferChange(listener) {
listeners.push(listener);
}
71 changes: 40 additions & 31 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,38 @@
*/
import * as utils from './utils.js'
import CONSTANTS from './constants.json';
import {ttlCollection} from './utils/ttlCollection.js';
import {config} from './config.js';
const TTL_CONFIG = 'eventHistoryTTL';

var slice = Array.prototype.slice;
var push = Array.prototype.push;
let eventTTL = null;

// define entire events
// var allEvents = ['bidRequested','bidResponse','bidWon','bidTimeout'];
var allEvents = utils._map(CONSTANTS.EVENTS, function (v) {
return v;
// keep a record of all events fired
const eventsFired = ttlCollection({
monotonic: true,
ttl: () => eventTTL,
})

config.getConfig(TTL_CONFIG, (val) => {
const previous = eventTTL;
val = val?.[TTL_CONFIG];
eventTTL = typeof val === 'number' ? val * 1000 : null;
if (previous !== eventTTL) {
eventsFired.refresh();
}
});

var idPaths = CONSTANTS.EVENT_ID_PATHS;
let slice = Array.prototype.slice;
let push = Array.prototype.push;

// define entire events
let allEvents = Object.values(CONSTANTS.EVENTS);

const idPaths = CONSTANTS.EVENT_ID_PATHS;

// keep a record of all events fired
var eventsFired = [];
const _public = (function () {
var _handlers = {};
var _public = {};
let _handlers = {};
let _public = {};

/**
*
Expand All @@ -30,18 +45,18 @@ const _public = (function () {
function _dispatch(eventString, args) {
utils.logMessage('Emitting event for: ' + eventString);

var eventPayload = args[0] || {};
var idPath = idPaths[eventString];
var key = eventPayload[idPath];
var event = _handlers[eventString] || { que: [] };
var eventKeys = utils._map(event, function (v, k) {
let eventPayload = args[0] || {};
let idPath = idPaths[eventString];
let key = eventPayload[idPath];
let event = _handlers[eventString] || { que: [] };
let eventKeys = utils._map(event, function (v, k) {
return k;
});

var callbacks = [];
let callbacks = [];

// record the event:
eventsFired.push({
eventsFired.add({
eventType: eventString,
args: eventPayload,
id: key,
Expand Down Expand Up @@ -79,7 +94,7 @@ const _public = (function () {
_public.on = function (eventString, handler, id) {
// check whether available event or not
if (_checkAvailableEvent(eventString)) {
var event = _handlers[eventString] || { que: [] };
let event = _handlers[eventString] || { que: [] };

if (id) {
event[id] = event[id] || { que: [] };
Expand All @@ -95,12 +110,12 @@ const _public = (function () {
};

_public.emit = function (event) {
var args = slice.call(arguments, 1);
let args = slice.call(arguments, 1);
_dispatch(event, args);
};

_public.off = function (eventString, handler, id) {
var event = _handlers[eventString];
let event = _handlers[eventString];

if (utils.isEmpty(event) || (utils.isEmpty(event.que) && utils.isEmpty(event[id]))) {
return;
Expand All @@ -112,14 +127,14 @@ const _public = (function () {

if (id) {
utils._each(event[id].que, function (_handler) {
var que = event[id].que;
let que = event[id].que;
if (_handler === handler) {
que.splice(que.indexOf(_handler), 1);
}
});
} else {
utils._each(event.que, function (_handler) {
var que = event.que;
let que = event.que;
if (_handler === handler) {
que.splice(que.indexOf(_handler), 1);
}
Expand All @@ -142,13 +157,7 @@ const _public = (function () {
* @return {Array} array of events fired
*/
_public.getEvents = function () {
var arrayCopy = [];
utils._each(eventsFired, function (value) {
var newProp = Object.assign({}, value);
arrayCopy.push(newProp);
});

return arrayCopy;
return eventsFired.toArray().map(val => Object.assign({}, val))
};

return _public;
Expand All @@ -159,5 +168,5 @@ utils._setEventEmitter(_public.emit.bind(_public));
export const {on, off, get, getEvents, emit, addEvents} = _public;

export function clearEvents() {
eventsFired.length = 0;
eventsFired.clear();
}
Loading

0 comments on commit 2042919

Please sign in to comment.