pierre@0: . pierre@0: */ pierre@0: pierre@0: /** pierre@0: * TODO: Debug Raw and IFrame display methods, neither currently seem to work pierre@0: * with this cache type. pierre@0: */ pierre@0: pierre@0: /** pierre@0: * Called by adserve.inc, display an ad from memcache. pierre@0: */ pierre@0: function ad_cache_memcache() { pierre@0: _debug_echo('Memcache: entering ad_cache_memcache().'); pierre@0: pierre@0: // TODO: Move the meat of this function into adserve.php, simplifying what pierre@0: // cache plugins have to do and removing duplicated logic. pierre@0: $init_cache = array(); pierre@0: $init_func = ad_cache_memcache_hook($init_cache, 'include_file_init', 'include_func_init'); pierre@0: pierre@0: $hostid = adserve_variable('hostid') ? adserve_variable('hostid') : 'none'; pierre@0: if ($hostid == 'none' || ad_memcache_get("ad-hostid-$hostid")) { pierre@0: if (function_exists($init_func)) { pierre@0: $init = $init_func($init_cache, $hostid); pierre@0: } pierre@0: if (!empty($init)) { pierre@0: if (adserve_variable('debug')) { pierre@0: echo "Memcache: initialized externally:
\n";
pierre@0:         print_r($init);
pierre@0:         echo '
'; pierre@0: } pierre@0: $type = $init['type']; pierre@0: $id = $init['id']; pierre@0: $group = $init['group']; pierre@0: $aids = explode(',', $id); pierre@0: adserve_variable('quantity', $init['quantity']); pierre@0: } pierre@0: else { pierre@0: if ($id = adserve_variable('nids')) { pierre@0: $type = 'node'; pierre@0: } pierre@0: else if ($id = adserve_variable('tids')) { pierre@0: $type = 'taxonomy'; pierre@0: } pierre@0: else { pierre@0: $type = 'default'; pierre@0: $id = 0; pierre@0: } pierre@0: $aids = ad_cache_memcache_get_ids($type, $id); pierre@0: $group = $id; pierre@0: } pierre@0: adserve_variable('group', $group); pierre@0: pierre@0: if (adserve_variable('debug')) { pierre@0: echo 'Memcache: selecting from the following ad id(s): '; pierre@0: if (empty($aids)) { pierre@0: echo 'none.
'; pierre@0: } pierre@0: else { pierre@0: echo implode(', ', $aids) .'.
'; pierre@0: } pierre@0: } pierre@0: pierre@0: $ids = adserve_variable("$type-ids"); pierre@0: if ($ids == NULL) { pierre@0: $ids = array(); pierre@0: } pierre@0: pierre@0: $output = ''; pierre@0: $selected = adserve_select_ad($aids, adserve_variable('quantity'), $ids); pierre@0: adserve_variable("$type-ids", array_merge($selected, $ids)); pierre@0: foreach ($selected as $aid) { pierre@0: if ($aid = (int)$aid) { pierre@0: $ad = ad_cache_memcache_get_ad($aid); pierre@0: pierre@0: if (!empty($output)) { pierre@0: $display_count++; pierre@0: $output .= "
"; pierre@0: } pierre@0: pierre@0: $output .= $ad->display; pierre@0: } pierre@0: else { pierre@0: $ad = array(); pierre@0: } pierre@0: pierre@0: _debug_echo("Displaying AID: $aid."); pierre@0: $action = $aid ? 'view' : 'count'; pierre@0: ad_cache_memcache_increment($action, $aid, $group, $hostid, $ad); pierre@0: } pierre@0: if (empty($output)) { pierre@0: adserve_variable('error', TRUE); pierre@0: $output = 'No active ads were found in the '. (empty($nids) ? 'tids' : 'nids') ." '$id'."; pierre@0: _debug_echo("Memcache: {$output}"); pierre@0: } pierre@0: } pierre@0: else { pierre@0: _debug_echo("Memcache: invalid hostid: '$hostid'."); pierre@0: $output = 'You do not have permission to display ads.'; pierre@0: } pierre@0: pierre@0: return $output; pierre@0: } pierre@0: pierre@0: function ad_cache_memcache_hook(&$cache, $hook, $func) { pierre@0: pierre@0: if (empty($cache)) { pierre@0: _debug_echo('Memcache: retrieving hook info from cache.'); pierre@0: $cache = ad_memcache_get('ad-cache-hook'); pierre@0: } pierre@0: $include_func = NULL; pierre@0: if (is_array($cache) && !empty($cache)) { pierre@0: $include_file = adserve_variable('root_dir') .'/'. $cache[$hook]; pierre@0: if (file_exists($include_file) && is_file($include_file)) { pierre@0: _debug_echo("Memcache: including external file: '$include_file'."); pierre@0: include_once($include_file); pierre@0: } pierre@0: else if (is_file($include_file)) { pierre@0: _debug_echo("Memcache: unable to find external file: '$include_file'."); pierre@0: } pierre@0: else { pierre@0: _debug_echo('Memcache: no include file defined in cache.'); pierre@0: } pierre@0: $include_func = $cache[$func]; pierre@0: if ($include_func) { pierre@0: _debug_echo("Memcache: returning requested func($func): '$include_func'."); pierre@0: } pierre@0: } pierre@0: return ($include_func); pierre@0: } pierre@0: pierre@0: function ad_cache_memcache_get_ids($op = 'default', $id = 0) { pierre@0: switch ($op) { pierre@0: pierre@0: case 'node': { pierre@0: $ids = explode(',', $id); pierre@0: break; pierre@0: } pierre@0: pierre@0: case 'taxonomy': { pierre@0: $ids = ad_memcache_get("ad-taxonomy-cache-$id"); pierre@0: if (!$ids || empty($ids)) { pierre@0: $taxonomy = ad_memcache_get('ad-taxonomy'); pierre@0: $cache = array(); pierre@0: $ids = explode(',', $id); pierre@0: foreach ($ids as $tid) { pierre@0: if (is_array($taxonomy[$tid])) { pierre@0: $cache += $taxonomy[$tid]; pierre@0: } pierre@0: } pierre@0: // Rebuild keys from 0, cache for quick re-use on next ad display. pierre@0: $ids = array_values($cache); pierre@0: ad_memcache_set("ad-taxonomy-cache-$id", $ids); pierre@0: } pierre@0: break; pierre@0: } pierre@0: pierre@0: default: { pierre@0: $taxonomy = ad_memcache_get('ad-taxonomy'); pierre@0: $ids = $taxonomy[0]; pierre@0: break; pierre@0: } pierre@0: pierre@0: } pierre@0: pierre@0: return $ids; pierre@0: } pierre@0: pierre@0: function ad_cache_memcache_get_ad($aid) { pierre@0: static $load = FALSE; pierre@0: pierre@0: $ad = ad_memcache_get("ad-aid-$aid"); pierre@0: pierre@0: if (!$load && !is_object($ad)) { pierre@0: $load = TRUE; pierre@0: adserve_bootstrap(); pierre@0: $ad_memcache_build = variable_get('ad_memcache_build', ''); pierre@0: if ((time() - $ad_memcache_build) >= 60) { pierre@0: ad_cache_memcache_build(); pierre@0: } pierre@0: } pierre@0: pierre@0: return $ad; pierre@0: } pierre@0: pierre@0: /** pierre@0: * Increment impressions counter in memcache. pierre@0: */ pierre@0: function ad_cache_memcache_increment($action, $aid, $group = '', $hostid = '', $ad = array()) { pierre@0: static $timestamp = NULL; pierre@0: pierre@0: _debug_echo("Memcache: increment action($action) aid($aid) group($group) hostid($hostid)."); pierre@0: pierre@0: if ($aid && !is_object($ad)) { pierre@0: _debug_echo("Invalid ad id: $aid."); pierre@0: return (0); pierre@0: } pierre@0: pierre@0: if (!isset($timestamp)) { pierre@0: $timestamp = date('YmdH'); pierre@0: } pierre@0: $counters = ad_memcache_get("ad-counters-$aid"); pierre@0: pierre@0: $update = TRUE; pierre@0: if (!is_array($counters) || !isset($counters["$action:$group:$hostid:$timestamp"])) { pierre@0: _debug_echo("Memcache: adding map: action($action) aid($aid) group($group) hostid($hostid) timestamp($timestamp)"); pierre@0: ad_memcache_increment_map($action, $aid, $group, $hostid, $timestamp); pierre@0: } pierre@0: pierre@0: $rc = ad_memcache_increment("ad-$action-$aid-$group-$hostid-$timestamp"); pierre@0: _debug_echo("Memcache: incrementing ad-$action-$aid-$group-$hostid-$timestamp ($rc)"); pierre@0: } pierre@0: pierre@0: /** pierre@0: * The maximum time any process can hold a given lock, in seconds. pierre@0: */ pierre@0: define('AD_MEMCACHE_LOCK_LIMIT', 2); pierre@0: pierre@0: /** pierre@0: * Store a value in memcache. pierre@0: */ pierre@0: function ad_memcache_set($key, $value, $timeout = 86400) { pierre@0: $memcache = ad_memcache_init(); pierre@0: pierre@0: return $memcache->set($key, $value, MEMCACHE_COMPRESSED, $timeout); pierre@0: } pierre@0: pierre@0: /** pierre@0: * Store a value in memcache. pierre@0: */ pierre@0: function ad_memcache_add($key, $value, $timeout = 86400) { pierre@0: $memcache = ad_memcache_init(); pierre@0: pierre@0: return $memcache->add($key, $value, MEMCACHE_COMPRESSED, $timeout); pierre@0: } pierre@0: pierre@0: /** pierre@0: * Get a value from memcache. pierre@0: */ pierre@0: function ad_memcache_get($key) { pierre@0: $memcache = ad_memcache_init(); pierre@0: pierre@0: return $memcache->get($key); pierre@0: } pierre@0: pierre@0: /** pierre@0: * Delete a value from memcache. pierre@0: */ pierre@0: function ad_memcache_delete($key) { pierre@0: $memcache = ad_memcache_init(); pierre@0: pierre@0: return $memcache->delete($key); pierre@0: } pierre@0: pierre@0: /** pierre@0: * Get a lock in memcache. pierre@0: */ pierre@0: function ad_memcache_lock($key, $wait = TRUE) { pierre@0: $loop = 0; pierre@0: $lock = FALSE; pierre@0: while ($lock == FALSE) { pierre@0: $lock = ad_memcache_add("LOCK-$key-LOCK", TRUE, AD_MEMCACHE_LOCK_LIMIT); pierre@0: if (!$lock && $wait) { pierre@0: if ($loop++ > 50) { pierre@0: // Hard limit of 5 seconds, after which we fail to grab a lock. pierre@0: return FALSE; pierre@0: } pierre@0: // Wait 1/10th of a second and try again. pierre@0: usleep(100000); pierre@0: } pierre@0: else if (!$lock && !$wait) { pierre@0: return FALSE; pierre@0: } pierre@0: } pierre@0: return TRUE; pierre@0: } pierre@0: pierre@0: /** pierre@0: * Release a lock in memcache. pierre@0: */ pierre@0: function ad_memcache_unlock($key) { pierre@0: ad_memcache_delete("LOCK-$key-LOCK"); pierre@0: } pierre@0: pierre@0: /** pierre@0: * Increment a numerical value in memcache. pierre@0: */ pierre@0: function ad_memcache_increment($key, $value = 1) { pierre@0: $memcache = ad_memcache_init(); pierre@0: pierre@0: $rc = $memcache->increment($key, $value); pierre@0: if ($rc === FALSE) { pierre@0: // We tried incrementing a counter that hasn't yet been initialized. pierre@0: $rc = $memcache->set($key, $value); pierre@0: if ($rc === FALSE) { pierre@0: // Another process already initialized the counter, increment it. pierre@0: $rc = $memcache->increment($key); pierre@0: } pierre@0: } pierre@0: return $rc; pierre@0: } pierre@0: pierre@0: /** pierre@0: * Decrement a numerical value in memcache. pierre@0: */ pierre@0: function ad_memcache_decrement($key, $value = 1) { pierre@0: $memcache = ad_memcache_init(); pierre@0: pierre@0: $rc = $memcache->decrement($key, $value); pierre@0: if ($rc === FALSE) { pierre@0: // We tried incrementing a counter that hasn't yet been initialized. pierre@0: $rc = $memcache->set($key, $value); pierre@0: if ($rc === FALSE) { pierre@0: // Another process already initialized the counter, increment it. pierre@0: $rc = $memcache->decrement($key); pierre@0: } pierre@0: } pierre@0: return $rc; pierre@0: } pierre@0: pierre@0: /** pierre@0: * Update mapping which allows us to quickly find stats in memcache when pierre@0: * feeding them into the database. pierre@0: */ pierre@0: function ad_memcache_increment_map($action, $aid, $group, $hostid, $timestamp) { pierre@0: $key = "ad-counters-$aid"; pierre@0: if (ad_memcache_lock($key)) { pierre@0: $counters = ad_memcache_get($key); pierre@0: if (!is_array($counters) || !isset($counters["$action:$group:$hostid:$timestamp"])) { pierre@0: $counters["$action:$group:$hostid:$timestamp"] = "$action:$group:$hostid:$timestamp"; pierre@0: ad_memcache_set($key, $counters); pierre@0: } pierre@0: ad_memcache_unlock($key); pierre@0: } pierre@0: } pierre@0: pierre@0: /** pierre@0: * Decrement a numerical value in memcache. pierre@0: * TODO: Use the same configuration style as Drupal's memcache module, pierre@0: * supporting multiple memcache servers, etc. pierre@0: */ pierre@0: function ad_memcache_init() { pierre@0: static $memcache = NULL; pierre@0: pierre@0: if (!$memcache) { pierre@0: $memcache = new Memcache; pierre@0: $memcache->addServer('localhost', 11211); pierre@0: } pierre@0: return $memcache; pierre@0: } pierre@0: pierre@0: /** pierre@0: * Allow external ad selection logic. pierre@0: */ pierre@0: function ad_cache_memcache_adserve_select($ads, $invalid) { pierre@0: $cache = array(); pierre@0: if ($select_func = ad_cache_memcache_hook($cache, 'include_file_select', 'include_func_select')) { pierre@0: _debug_echo("Memcache: adserve_select: invoking '$select_func()'"); pierre@0: if (function_exists($select_func)) { pierre@0: if (is_array($cache) && !empty($cache)) { pierre@0: return $select_func($ads, $invalid, $cache); pierre@0: } pierre@0: else { pierre@0: _debug_echo("Memcache: unexpected error: cache empty."); pierre@0: } pierre@0: } pierre@0: else { pierre@0: _debug_echo("Memcache: adserve_select: '$include_func_select()' not found"); pierre@0: } pierre@0: } pierre@0: else { pierre@0: _debug_echo("Memcache: adserve_select: no select function defined"); pierre@0: } pierre@0: } pierre@0: pierre@0: /** pierre@0: * Allow external exit text. pierre@0: */ pierre@0: function ad_cache_memcache_adserve_exit_text() { pierre@0: $cache = array(); pierre@0: if ($exit_text_func = ad_cache_memcache_hook($cache, 'include_file_exit_text', 'include_func_exit_text')) { pierre@0: _debug_echo("Memcache: adserve_exit_text: invoking '$exit_text_func()'"); pierre@0: if (function_exists($exit_text_func)) { pierre@0: return $exit_text_func(); pierre@0: } pierre@0: else { pierre@0: _debug_echo("Memcache: adserve_exit_text: '$exit_text_func()' not found"); pierre@0: } pierre@0: } pierre@0: else { pierre@0: _debug_echo("Memcache: adserve_exit_text: no exit_text function defined"); pierre@0: } pierre@0: } pierre@0: