Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: introduce new eventHistoryTTL and minBidCacheTTL settings to control memory usage #10308

Merged
merged 3 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading