Roxen.git / server / base_server / cache.pike

version» Context lines:

Roxen.git/server/base_server/cache.pike:1:   // This file is part of Roxen WebServer.   // Copyright © 1996 - 2009, Roxen IS. - // $Id: cache.pike,v 1.150 2012/03/05 13:55:10 grubba Exp $ + // $Id$      // FIXME: Add argcache, imagecache & protcache      #include <roxen.h>   #include <config.h>      #ifdef MORE_CACHE_DEBUG   # define MORE_CACHE_WERR(X...) report_debug("CACHE: "+X)   # undef CACHE_DEBUG   # define CACHE_DEBUG
Roxen.git/server/base_server/cache.pike:61:       // Rebalance immediately after setting the total size limit. This is    // important mostly at server startup since modules otherwise might    // do cache-intensive processing before the cache manager size has    // been raised from its minimum value, resulting in sub-optimal    // performance or even halting cache-filler operations    // (e.g. Sitebuilder's workarea prefetcher.)    update_cache_size_balance();   }    + //! The SNMP lookup root for the cache. + SNMP.SimpleMIB mib = SNMP.SimpleMIB(SNMP.RIS_OID_WEBSERVER + ({ 3 }), +  ({}),({ UNDEFINED })); +    //! Base class for cache entries. - class CacheEntry (mixed key, mixed data) + class CacheEntry (mixed key, mixed data, string cache_name)   {    // FIXME: Consider unifying this with CacheKey. But in that case we    // need to ensure "interpreter lock" atomicity below.       int size;    //! The size of this cache entry, as measured by @[Pike.count_memory].    -  +  //! Updates the size by calling @[Pike.count_memory], and returns +  //! the difference between the new and the old measurement. +  int update_size() +  { +  int old_size = size; + #ifdef DEBUG_COUNT_MEM +  mapping opts = (["lookahead": DEBUG_COUNT_MEM - 1, +  "collect_stats": 1, +  "collect_direct_externals": 1, +  "block_strings": -1 ]); +  float t = gauge { + #else +  mapping opts = (["block_strings": -1]); + #endif +  +  if (function(int|mapping:int) cm_cb = +  objectp (data) && data->cache_count_memory) +  this::size = cm_cb (opts) + Pike.count_memory (-1, this, key); +  else +  this::size = Pike.count_memory (opts, this, key, data); +  + #ifdef DEBUG_COUNT_MEM +  }; +  werror ("%O: la %d size %d time %g int %d cyc %d ext %d vis %d revis %d " +  "rnd %d wqa %d\n", +  new_entry, opts->lookahead, opts->size, t, opts->internal, +  opts->cyclic, opts->external, opts->visits, opts->revisits, +  opts->rounds, opts->work_queue_alloc); +  + #if 0 +  if (opts->external) { +  opts->collect_direct_externals = 1; +  // Raise the lookahead to 1 to recurse the closest externals. +  if (opts->lookahead < 1) opts->lookahead = 1; +  +  if (function(int|mapping:int) cm_cb = +  objectp (data) && data->cache_count_memory) +  res = cm_cb (opts) + Pike.count_memory (-1, entry, key); +  else +  res = Pike.count_memory (opts, entry, key, data); +  +  array exts = opts->collect_direct_externals; +  werror ("Externals found using lookahead %d: %O\n", +  opts->lookahead, exts); + #if 0 +  foreach (exts, mixed ext) +  if (objectp (ext) && ext->locate_my_ext_refs) { +  werror ("Refs to %O:\n", ext); +  _locate_references (ext); +  } + #endif +  } + #endif +  + #endif // DEBUG_COUNT_MEM + #undef opts +    #ifdef DEBUG_CACHE_SIZES -  +  new_entry->cmp_size = cmp_sizeof_cache_entry (cache_name, new_entry); + #endif +  +  return size - old_size; +  } +  + #ifdef DEBUG_CACHE_SIZES    int cmp_size;    // Size without counting strings. Used to compare the size between    // cache_set and cache_clean. Strings are excluded since they might    // get or lose unrelated refs in the time between which would make    // the comparison unreliable. This might make us miss significant    // strings though, but it's hard to get around it.   #endif       int timeout;    //! Unix time when the entry times out, or zero if there's no
Roxen.git/server/base_server/cache.pike:109:    return sprintf ("%t", key);    }       protected string _sprintf (int flag)    {    return flag == 'O' && sprintf ("CacheEntry(%s, %db)", format_key(), size);    }   }      class CacheStats - //! Holds statistics for each named cache. + //! Holds statistics for each group of named caches.   {    int count;    //! The number of entries in the cache.       int size;    //! The sum of @[CacheEntry.size] for all cache entries in the cache.       int hits, misses;    //! Plain counts of cache hits and misses since the creation of the    //! cache.
Roxen.git/server/base_server/cache.pike:141: Inside #if defined(CACHE_BYTE_HR_STATS)
   //! will not include when no new entry was created after a cache    //! miss.   #endif       protected string _sprintf (int flag)    {    return flag == 'O' && sprintf ("CacheStats(%d, %dk)", count, size / 1024);    }   }    + class CacheManagerPrefs(int(0..1) extend_entries // Set if a +  // cache_name may get +  // existing entries +  // extended even +  // after cache hit. +  ) {} +    class CacheManager   //! A cache manager handles one or more caches, applying the same   //! eviction policy and the same size limit on all of them. I.e. it's   //! practically one cache, and the named caches inside only act as   //! separate name spaces.   { -  //! @decl constant string name; -  //! +     //! A unique name to identify the manager. It is also used as    //! display name. -  +  constant name = "-";       //! @decl constant string doc;    //!    //! A description of the manager and its eviction policy in html.       constant has_cost = 0;    //! Nonzero if this cache manager implements a cost metric.       int total_size_limit = global::total_size_limit;    //! Maximum allowed size including the cache manager overhead.
Roxen.git/server/base_server/cache.pike:173:    //! The sum of @[CacheStats.size] for all named caches.       int size_limit = global::total_size_limit;    //! Maximum allowed size for cache entries - @[size] should never    //! greater than this. This is a cached value calculated from    //! @[total_size_limit] on regular intervals.       int entry_add_count;    //! Number of entries added since this cache manager was created.    +  int byte_add_count; +  //! Number of bytes added since this cache manager was created. +     mapping(string:mapping(mixed:CacheEntry)) lookup = ([]);    //! Lookup mapping on the form @expr{(["cache_name": ([key: data])])@}.    //!    //! For functions in this class, a cache submapping does not exist    //! only due to race, so the cache should just be ignored.       mapping(string:CacheStats) stats = ([]);    //! Statistics for the named caches managed by this object.    //!    //! For functions in this class, a @[CacheStats] object does not    //! exist only due to race, so the cache should just be ignored.    -  +  mapping(string:CacheManagerPrefs) prefs = ([]); +  //! Preferences for the named caches managed by this object. +  //! +  +  int cached_overhead_add_count; +  //! Snapshot of @[entry_add_count] at the point the manager overhead +  //! was computed. +  +  int cached_overhead; +  //! Cached manager overhead. Recomputed in manager_size_overhead(). +     //! @decl program CacheEntry;    //!    //! The manager-specific class to use to create @[CacheEntry] objects.       void clear_cache_context() {}    //! Called to clear any thread-local state that the manager keeps to    //! track execution times etc. This is called before a thread starts    //! with a new request or other kind of job.       void got_miss (string cache_name, mixed key, mapping cache_context);
Roxen.git/server/base_server/cache.pike:257:    // cache miss.    recent_cost_misses += entry->cost;       if (CacheStats cs = stats[cache_name]) {    cs->cost_misses += entry->cost;   #ifdef CACHE_BYTE_HR_STATS    cs->byte_misses += entry->size;   #endif       if (mapping(mixed:CacheEntry) lm = lookup[cache_name]) { +  CacheEntry old_entry;    // vvv Relying on the interpreter lock from here. -  CacheEntry old_entry = lm[entry->key]; +  while (old_entry = lm[entry->key]) { +  recent_added_bytes -= old_entry->size; +  remove_entry (cache_name, old_entry); +  }    lm[entry->key] = entry;    // ^^^ Relying on the interpreter lock to here.    -  if (old_entry) { -  account_remove_entry (cache_name, old_entry); -  recent_added_bytes -= entry->size; -  remove_entry (cache_name, old_entry); -  } -  +     cs->count++;    cs->size += entry->size;    size += entry->size;    recent_added_bytes += entry->size; -  +  byte_add_count += entry->size;       if (!(++entry_add_count & 0x3fff)) // = 16383    update_size_limit();    }       return 1;    }       return 0;    }
Roxen.git/server/base_server/cache.pike:315:    //! Called to evict entries until @expr{@[size] <= @[max_size]@}.       void after_gc() {}    //! Called from the periodic GC, after stale and invalid entries    //! have been removed from the cache.       int manager_size_overhead()    //! Returns the size consumed by the manager itself, excluding the    //! cache entries.    { +  // Return the cached overhead as long as the number of added +  // entries since the last computation is less than 10% of the +  // number of entries in the cache. The cache is a workaround for +  // the horrific performance of Pike.count_memory (-1, m) on large +  // mappings (addressed in Pike 8.1). +  int num_entries; +  foreach (stats;; CacheStats stats) { +  if (stats) num_entries += stats->count; +  } +  +  if ((entry_add_count - cached_overhead_add_count) < +  (num_entries / 10)) +  return cached_overhead; +     int res = (Pike.count_memory (-1, this, lookup) +    Pike.count_memory (0, stats));    foreach (lookup;; mapping(mixed:CacheEntry) lm)    res += Pike.count_memory (-1, lm); -  +  +  cached_overhead_add_count = entry_add_count; +  cached_overhead = res;    return res;    }       float add_rate = 0.0;    //! The number of newly added bytes per second, calculated as a    //! decaying average over the last @[cm_stats_avg_period] seconds.    //! Note that the returned value could become negative if entries    //! are replaced with smaller ones.       float hits = 0.0, misses = 0.0;
Roxen.git/server/base_server/cache.pike:354:    {    // Skip updating if we did it recently (avoid division by zero below.)    if (now == last_update)    return;       float last_period = (float) (now - last_update);    float tot_period = (float) (now - start_time);    int startup = tot_period < cm_stats_avg_period;    if (!startup) tot_period = (float) cm_stats_avg_period;    -  float our_weight = min (last_period / tot_period, 1.0); +  float our_weight = min (tot_period != 0.0 && last_period / tot_period, 1.0);    float old_weight = 1.0 - our_weight;       if (startup) {    hits += (float) recent_hits;    misses += (float) recent_misses;    cost_hits += (float) recent_cost_hits;    cost_misses += (float) recent_cost_misses;    }       else {
Roxen.git/server/base_server/cache.pike:409:    }       string format_cost (int|float cost) {return "-";}    //! Function to format a cost measurement for display in the status    //! page.       protected string _sprintf (int flag)    {    return flag == 'O' &&    sprintf ("CacheManager(%s: %dk/%dk)", -  this->name || "-", size / 1024, size_limit / 1024); +  name, size / 1024, size_limit / 1024);    } -  +  +  protected void create() +  { +  mib->merge(CacheManagerMIB(this));    } -  + }      #if 0   class CM_Random   {    inherit CacheManager;       constant name = "Random";    constant doc = #"\   This is a very simple cache manager that just evicts entries from the   cache at random. The only upside with it is that the cache management
Roxen.git/server/base_server/cache.pike:441: Inside #if 0
   }       void got_hit (string cache_name, CacheEntry entry, mapping cache_context)    {    account_hit (cache_name, entry);    }       int add_entry (string cache_name, CacheEntry entry,    int old_entry, mapping cache_context)    { +  entry->cache_name = cache_name;    int res = low_add_entry (cache_name, entry);    if (size > size_limit) evict (size_limit);    return res;    }       int remove_entry (string cache_name, CacheEntry entry)    {    return low_remove_entry (cache_name, entry);    }   
Roxen.git/server/base_server/cache.pike:494:   //! is always chosen for eviction. When a new entry p is added, and   //! each time it is hit afterwards, the priority is set to v(p) + L,   //! where v(p) is p's value according to some algorithm-specific   //! definition, and L is the lowest priority value in the cache. This   //! means that the priority values constantly increases, so that old   //! entries without hits eventually gets evicted regardless of their   //! initial v(p).   {    inherit CacheManager;    +  //! Allow cache to grow 10% above @[size_limit] before evicting +  //! entries synchronously in @[add_entry]. +  constant max_overshoot_factor = 1.1; +  +  //! Mutex protecting @[priority_queue]. +  Thread.Mutex priority_mux = Thread.Mutex(); +  +  //! A heap of all [CacheEntry]s in priority order sorted by @[CacheEntry.`<]. +  ADT.Heap priority_queue = ADT.Heap(); +  +  //! Queue to hold entries that need a size update (asynchronous +  //! count_memory). +  Thread.Queue update_size_queue = Thread.Queue(); +  +  mapping(CacheEntry:int(1..1)) pending_pval_updates = ([]); +  +  //! Wrapper so that we can get back to @[CacheEntry] from +  //! @[ADT.Heap.Element] easily. +  protected class HeapElement +  { +  inherit ADT.Heap.Element; +  +  //! Return the @[CacheEntry] that contains this @[HeapElement]. +  object(CacheEntry) cache_entry() { return [object(CacheEntry)](mixed)this; } +  } +     class CacheEntry    //!    { -  +  private local inherit HeapElement; +     inherit global::CacheEntry;       int|float value;    //! The value of the entry, i.e. v(p) in the class description.    -  int|float pval; -  //! The priority value for the entry, defining its position in -  //! @[priority_list]. Must not change for an entry that is -  //! currently a member of @[priority_list]. +  //! @decl int|float pval; +  //! The priority value for the entry, effecting its position in +  //! @[priority_queue]. +  //! +  //! If the element is a member of @[priority_queue] it will +  //! automatically adjust its position when this value is changed.    -  string cache_name; -  //! Need the cache name to find the entry, since @[priority_list] -  //! is global. -  -  protected int `< (CacheEntry other) +  int|float `pval()    { -  return pval < other->pval; +  return HeapElement::value;    } -  +  void `pval=(int|float val) +  { +  Element::value = val; +  if (HeapElement::pos != -1) { +  // NB: We may get called in a context where the mutex +  // already has been taken. +  Thread.MutexKey key = priority_mux->lock(2); +  priority_queue->adjust(HeapElement::this); +  } +  }    -  +  //! Return the @[HeapElement] corresponding to this @[CacheEntry]. +  HeapElement element() { return HeapElement::this; } +  +  string cache_name; +  //! Need the cache name to find the entry, since @[priority_queue] +  //! is global. +     protected string _sprintf (int flag)    {    return flag == 'O' &&    sprintf ("CM_GreedyDual.CacheEntry(%O: %s, %db, %O)",    pval, format_key(), size, value);    }    }    -  multiset(CacheEntry) priority_list = (<>); -  //! A list of all entries in priority order, by using the multiset -  //! builtin sorting through @[CacheEntry.`<]. -  +     protected int max_used_pval;    // Used to detect when the entry pval's get too big to warrant a    // reset.    //    // For integers, this is the maximum used pval and we reset at the    // next gc when it gets over Int.NATIVE_MAX/2 (to have some spare    // room for the gc delay).    //    // For floats, we reset whenever L is so big that less than 8    // significant bits remains when v(p) is added to it. In that case    // max_used_pval only works as a flag, and we set it to    // Int.NATIVE_MAX when that state is reached.    -  +  // call_out handle used by schedule_update_weights. +  protected mixed update_weights_handle; +  +  protected void schedule_update_weights() +  { +  if (!update_weights_handle) { +  // Weird indexing: the roxen constant is not registered when +  // this file is compiled... +  // +  // Warning: background_run doesn't return a handle if delay is set to 0. +  update_weights_handle = +  all_constants()->roxen->background_run (0.001, update_weights); +  } +  } +  +  protected void update_weights() +  { +  update_weights_handle = 0; +  +  // Try to limit run time to 50 ms at a time in order to avoid +  // impacting requests too much. If we're interrupted due to +  // timeout we'll reschedule ourselves as a background job. In a +  // heavily loaded server this might delay size/pval updates for a +  // while, but the main focus should be handling requests rather +  // than fiddling with size estimations and heap rebalancing. We'll +  // assume updates won't be deferred for so long that eviction +  // selection will be severely impacted. +  constant max_run_time = 50000; +  int reschedule; +  +  // Protect against race when rebalancing on setting entry->pval. +  Thread.MutexKey key = priority_mux->lock(); +  int start = gethrtime(); +  +  foreach (pending_pval_updates; CacheEntry entry;) { +  m_delete (pending_pval_updates, entry); +  +  string cache_name = entry->cache_name; +  // Check if entry has been evicted already. +  if (mapping(string:CacheEntry) lm = lookup[cache_name]) { +  if (lm[entry->key] != entry) { +  continue; +  } +  } +  +  // NB: The priority queue is automatically adjusted on +  // change of pval. +  entry->pval = calc_pval (entry); +  +  if (sizeof (pending_pval_updates) < 10000 && +  gethrtime() - start > (max_run_time / 2)) { +  reschedule = 1; +  break; +  } +  } +  +  // Avoid starvation of update_size_queue processing by making sure +  // it runs for at least (max_run_time / 2) usecs. +  +  // The whole run may overshoot max_run_time somewhat if the above +  // loop already overshot (max_run_time / 2), but we'll accept +  // that. +  start = gethrtime(); +  +  while (CacheEntry entry = update_size_queue->try_read()) { +  string cache_name = entry->cache_name; +  // Check if entry has been evicted already. +  if (mapping(string:CacheEntry) lm = lookup[cache_name]) { +  if (lm[entry->key] == entry) { +  int size_diff = entry->update_size(); +  size += size_diff; +  +  if (CacheStats cs = stats[cache_name]) { +  cs->size += size_diff; +  recent_added_bytes += size_diff; +  byte_add_count += size_diff; +  } +  } +  +  // NB: The priority queue is automatically adjusted on +  // change of pval. +  entry->pval = calc_pval (entry); +  } +  +  if (sizeof (update_size_queue) < 10000 && +  gethrtime() - start > (max_run_time / 2)) { +  reschedule = 1; +  break; +  } +  } +  +  key = 0; +  +  if (size > size_limit) { +  evict (size_limit); +  } +  +  if (reschedule) { +  schedule_update_weights(); +  } +  } +    #ifdef CACHE_DEBUG -  protected void debug_check_priority_list() +  protected void debug_check_priority_queue()    // Assumes no concurrent access - run inside _disable_threads.    { -  werror ("Checking priority_list with %d entries.\n", -  sizeof (priority_list)); -  CacheEntry prev; -  foreach (priority_list; CacheEntry entry;) { -  if (!prev) -  prev = entry; -  else if (prev >= entry) -  error ("Found cache entries in wrong order: %O vs %O in %O\n", -  prev, entry, priority_list); -  if (intp (entry->pval) && entry->pval > max_used_pval) -  error ("Found %O with pval higher than max %O.\n", -  entry, max_used_pval); +  werror ("Checking priority_queue with %d entries.\n", +  sizeof(priority_queue)); +  if (priority_queue->verify_heap) { +  Thread.MutexKey key = priority_mux->lock(); +  priority_queue->verify_heap();    }    }   #endif       int|float calc_value (string cache_name, CacheEntry entry,    int old_entry, mapping cache_context);    //! Called to calculate the value for @[entry], which gets assigned    //! to the @expr{value@} variable. Arguments are the same as to    //! @[add_entry].       void got_miss (string cache_name, mixed key, mapping cache_context)    {    account_miss (cache_name);    }       local protected int|float calc_pval (CacheEntry entry)    {    int|float pval; -  if (CacheEntry lowest = get_iterator (priority_list)->index()) { -  int|float l = lowest->pval, v = entry->value; +  if (HeapElement lowest = priority_queue->low_peek()) { +  int|float l = lowest->value, v = entry->value;    pval = l + v;       if (floatp (v)) {    if (v != 0.0 && v < l * (Float.EPSILON * 0x10)) {   #ifdef DEBUG    if (max_used_pval != Int.NATIVE_MAX)    werror ("%O: Ran out of significant digits for cache entry %O - "    "got min priority %O and entry value %O.\n",    this, entry, l, v);   #endif
Roxen.git/server/base_server/cache.pike:603:    }    else    // Assume entry->value isn't greater than Int.NATIVE_MAX/2 right away.    pval = entry->value;    return pval;    }       void got_hit (string cache_name, CacheEntry entry, mapping cache_context)    {    account_hit (cache_name, entry); -  int|float pval = calc_pval (entry); -  -  // Note: Won't have the interpreter lock during the operation below. The -  // tricky situation occur if the same entry is processed by another -  // got_hit, where we might get it inserted in more than one place and one -  // of them will be inconsistent with the pval value. evict() has a -  // consistency check that should detect and correct this eventually. The -  // worst thing that happens is that the eviction order is more or less -  // wrong until then. -  priority_list[entry] = 0; -  entry->pval = pval; -  priority_list[entry] = 1; +  // Even though heap rebalancing is relatively cheap (at least +  // compared to the old multiset strategy), we'll defer updates to +  // a background job (because of how frequent cache hits are). This +  // also helps consolidation. +  pending_pval_updates[entry] = 1; +  schedule_update_weights();    }       int add_entry (string cache_name, CacheEntry entry,    int old_entry, mapping cache_context)    { -  +  int need_size_update;    entry->cache_name = cache_name; -  +  // count_memory may account for significant amounts of CPU time on +  // frequent cache misses. To avoid impacting requests too much +  // we'll assign a mean value here and defer actual memory counting +  // to a background job. During load spikes that should help +  // amortize the cost of count_memory over a longer period of time. +  if (CacheStats cs = stats[cache_name]) { +  if (cs->count) { +  entry->size = cs->size / cs->count; +  need_size_update = 1; +  } else { +  // No entry present from before -- update synchronously. +  entry->update_size(); +  } +  } +  +  entry->cache_name = cache_name;    int|float v = entry->value =    calc_value (cache_name, entry, old_entry, cache_context);       if (!low_add_entry (cache_name, entry)) return 0;    -  +  Thread.MutexKey key = priority_mux->lock();    entry->pval = calc_pval (entry); -  priority_list[entry] = 1; +  priority_queue->push(entry->element()); +  key = 0;    -  if (size > size_limit) evict (size_limit); +  if (need_size_update) { +  update_size_queue->write (entry); +  schedule_update_weights(); +  } +  +  // Evictions will normally take place in the background job as +  // well, but we'll make sure we don't overshoot the size limit by +  // too much. +  int hard_size_limit = (int)(size_limit * max_overshoot_factor); +  if (size > hard_size_limit) +  evict (hard_size_limit); +     return 1;    }       int remove_entry (string cache_name, CacheEntry entry)    { -  priority_list[entry] = 0; +  Thread.MutexKey key = priority_mux->lock(); +  priority_queue->remove(entry->element()); +  key = 0;    return low_remove_entry (cache_name, entry);    }       void evict (int max_size)    { -  object threads_disabled; +  Thread.MutexKey key = priority_mux->lock(); +  while ((size > max_size) && sizeof(priority_queue)) { +  HeapElement element = priority_queue->low_pop(); +  if (!element) break;    -  while (size > max_size) { -  CacheEntry entry = get_iterator (priority_list)->index(); -  if (!entry) break; +  CacheEntry entry = element->cache_entry();    -  if (!priority_list[entry]) { -  // The order in the list has become inconsistent with the pval values. -  // It might happen due to the race in got_hit(), or it might just be -  // interference from a concurrent evict() call. -  if (!threads_disabled) -  // Take a hefty lock and retry, to rule out the second case. -  threads_disabled = _disable_threads(); -  else { -  // Got the lock so it can't be a race, i.e. the priority_list order -  // is funky. Have to rebuild it without interventions. - #ifdef CACHE_DEBUG -  debug_check_priority_list(); - #endif -  report_warning ("Warning: Recovering from race inconsistency " -  "in %O->priority_list (on %O).\n", this, entry); -  priority_list = (<>); -  foreach (lookup; string cache_name; mapping(mixed:CacheEntry) lm) -  foreach (lm;; CacheEntry entry) -  priority_list[entry] = 1; -  } -  continue; -  } -  -  priority_list[entry] = 0; -  +     MORE_CACHE_WERR ("evict: Size %db > %db - evicting %O / %O.\n",    size, max_size, entry->cache_name, entry);       low_remove_entry (entry->cache_name, entry);    } -  -  threads_disabled = 0; +     }       int manager_size_overhead()    { -  return Pike.count_memory (-1, priority_list) + ::manager_size_overhead(); +  return Pike.count_memory (-1, priority_queue) + ::manager_size_overhead();    }       void after_gc()    {    if (max_used_pval > Int.NATIVE_MAX / 2) {    int|float pval_base; -  if (CacheEntry lowest = get_iterator (priority_list)->index()) -  pval_base = lowest->pval; +  if (HeapElement lowest = priority_queue->low_peek()) +  pval_base = lowest->value;    else    return;       // To cope with concurrent updates, we replace the lookup    // mapping and start adding back the entries from the old one.    // Need _disable_threads to make the resets of the CacheStats    // fields atomic. -  +  Thread.MutexKey key = priority_mux->lock();    object threads_disabled = _disable_threads();    mapping(string:mapping(mixed:CacheEntry)) old_lookup = lookup;    lookup = ([]);    foreach (old_lookup; string cache_name;) {    lookup[cache_name] = ([]);    if (CacheStats cs = stats[cache_name]) {    cs->count = 0;    cs->size = 0;    }    } -  priority_list = (<>); +  priority_queue = ADT.Heap();    size = 0;    max_used_pval = 0;    threads_disabled = 0;       int|float max_pval;       foreach (old_lookup; string cache_name; mapping(mixed:CacheEntry) old_lm)    if (CacheStats cs = stats[cache_name])    if (mapping(mixed:CacheEntry) new_lm = lookup[cache_name])    foreach (old_lm; mixed key; CacheEntry entry) { -  +  entry->element()->pos = -1;    int|float pval = entry->pval -= pval_base;    if (!new_lm[key]) {    // Relying on the interpreter lock here.    new_lm[key] = entry;    -  priority_list[entry] = 1; +  priority_queue->push(entry->element());    if (pval > max_pval) max_pval = pval;       cs->count++;    cs->size += entry->size;    size += entry->size;    }    }    -  +  key = 0; +    #ifdef CACHE_DEBUG -  debug_check_priority_list(); +  debug_check_priority_queue();   #endif       if (intp (max_pval))    max_used_pval = max_pval;       CACHE_WERR ("%O: Rebased priority values - "    "old base was %O, new range is 0..%O.\n",    this, pval_base, max_pval);       if (Configuration admin_config = roxenp()->get_admin_configuration())
Roxen.git/server/base_server/cache.pike:847: Inside #if defined(DEBUG)
   else {   #ifdef DEBUG    werror ("Warning: Got call from %O without cache context mapping.\n%s\n",    Thread.this_thread(), describe_backtrace (backtrace()));   #endif    }    }       void got_miss (string cache_name, mixed key, mapping cache_context)    { -  //werror ("Miss.\n%s\n", describe_backtrace (backtrace())); -  account_miss (cache_name); +  ::got_miss (cache_name, key, cache_context);    save_start_hrtime (cache_name, key, cache_context);    }       void got_hit (string cache_name, CacheEntry entry, mapping cache_context)    { -  account_hit (cache_name, entry); -  // It shouldn't be necessary to record the start time for cache -  // hits, but do it anyway for now since there are caches that on -  // cache hits extend the entries with more data. +  ::got_hit(cache_name, entry, cache_context); +  if (CacheManagerPrefs prefs = prefs[cache_name]) { +  if (prefs->extend_entries) { +  // Save start time for caches that may want to extend existing +  // entries.    save_start_hrtime (cache_name, entry->key, cache_context);    } -  +  } +  }       protected int entry_create_hrtime (string cache_name, mixed key,    mapping cache_context)    {    if (mapping all_ctx = cache_context || cache_contexts->get())    if (mapping(mixed:int) ctx = all_ctx[cache_name]) {    int start = m_delete (ctx, key);    if (!zero_type (start)) {    int duration = (gettime_func() - all_ctx[0]) - start;   #if 0
Roxen.git/server/base_server/cache.pike:1085:   ]);      //! All available cache managers.   array(CacheManager) cache_managers =    Array.uniq (({cache_manager_prefs->default,    cache_manager_prefs->no_cpu_timings,    cache_manager_prefs->no_thread_timings,    cache_manager_prefs->no_timings,    }));    + protected array(int) string_to_oid(string s) + { +  return ({ sizeof(s) }) + (array(int))s; + } +  + class CacheStatsMIB + { +  inherit SNMP.SimpleMIB; +  +  CacheStats stats; +  +  int get_count() { return stats->count; } +  int get_size() { return stats->size; } +  int get_hits() { return stats->hits; } +  int get_misses() { return stats->misses; } +  int get_cost_hits() { return (int)stats->cost_hits; } +  int get_cost_misses() { return (int)stats->cost_misses; } + #ifdef CACHE_HYTE_HR_STATS +  int get_byte_hits() { return stats->byte_hits; } +  int get_byte_misses() { return stats->byte_misses; } + #endif +  protected void create(CacheManager manager, string name, CacheStats stats) +  { +  this::stats = stats; +  array(int) oid = mib->path + string_to_oid(manager->name) + ({ 3 }) + +  string_to_oid(name); +  string label = "cache-"+name+"-"; +  ::create(oid, ({}), +  ({ +  UNDEFINED, +  SNMP.String(name, label+"name"), +  SNMP.Gauge(get_count, label+"numEntries"), +  SNMP.Gauge(get_size, label+"numBytes"), +  ({ +  SNMP.Counter(get_hits, label+"numHits"), +  SNMP.Integer(get_cost_hits, label+"costHits"), + #ifdef CACHE_BYTE_HR_STATS +  SNMP.Counter(get_byte_hits, label+"byteHits"), + #else +  UNDEFINED, /* Reserved */ + #endif +  }), +  ({ +  SNMP.Counter(get_misses, label+"numMisses"), +  SNMP.Integer(get_cost_misses, label+"costMisses"), + #ifdef CACHE_BYTE_HR_STATS +  SNMP.Counter(get_byte_misses, label+"byteMisses"), + #else +  UNDEFINED, /* Reserved */ + #endif +  }), +  })); +  } + } +  + class CacheManagerMIB + { +  inherit SNMP.SimpleMIB; +  +  CacheManager manager; +  int get_entries() { return Array.sum(values(manager->stats)->count); } +  int get_size() { return manager->size; } +  int get_entry_add_count() { return manager->entry_add_count; } +  int max_byte_add_count; +  int get_byte_add_count() { +  // SNMP.Counter should never decrease +  return max_byte_add_count = max(max_byte_add_count, manager->byte_add_count); +  } +  +  protected void create(CacheManager manager) +  { +  this::manager = manager; +  array(int) oid = mib->path + string_to_oid(manager->name); +  string label = "cacheManager-"+manager->name+"-"; +  ::create(oid, ({}), +  ({ +  UNDEFINED, +  SNMP.String(manager->name, label+"name"), +  ({ +  SNMP.Integer(get_entries, label+"numEntries"), +  SNMP.Integer(get_size, label+"numBytes"), +  SNMP.Counter(get_entry_add_count, label+"addedEntries"), +  SNMP.Counter(get_byte_add_count, label+"addedBytes"), +  }), +  UNDEFINED, // Reserved for CacheStatsMIB. +  })); +  } + } +    protected Thread.Mutex cache_mgmt_mutex = Thread.Mutex();   // Locks operations that manipulate named caches, i.e. changes in the   // caches, CacheManager.stats and CacheManager.lookup mappings.      protected int cache_start_time = time() - 1;   protected int last_cache_size_balance = time() - 1;   // Subtract 1 from the initial values to avoid division by zero in   // update_decaying_stats if update_cache_size_balance gets called   // really soon after startup.   
Roxen.git/server/base_server/cache.pike:1163:    // by whichever cache fills it first. If that's under    // rebalance_min_size it means we get some "overshoot" room here    // too, like above.    reserved = mgr_used[mgr];   #ifdef CACHE_DEBUG    overshoot_size += rebalance_min_size - reserved;   #endif    }    reserved_size += mgr_reserved_size[mgr] = reserved;    -  float lookups = mgr->has_cost ? -  mgr->cost_hits + mgr->cost_misses : mgr->hits + mgr->misses; +  float hits; +  float misses; +  if (mgr->has_cost) { +  hits = mgr->cost_hits; +  misses = mgr->cost_misses; +  } else { +  hits = mgr->hits; +  misses = mgr->misses; +  } +  +  float lookups = hits + misses;    float hit_rate_per_byte = lookups != 0.0 && mgr_used[mgr] ? -  mgr->hits / lookups / mgr_used[mgr] : 0.0; +  hits / lookups / mgr_used[mgr] : 0.0;       // add_rate is a measurement on how many new bytes a cache could put    // into use, and hit_rate_per_byte weighs in a projection on how    // successful it would be caching those bytes.    float weight = max (mgr->add_rate * hit_rate_per_byte, 0.0);    mgr_weights[mgr] = weight;    total_weight += weight;       CACHE_WERR ("Rebalance weight %s: Reserved %db, "    "add rate %g, hr %g, hr/b %g, weight %g.\n",
Roxen.git/server/base_server/cache.pike:1298:   // Maps the named caches to the cache managers that handle them.      mapping(string:CacheManager) cache_list()   //! Returns a list of all currently registered caches and their   //! managers.   {    return caches + ([]);   }      CacheManager cache_register (string cache_name, -  void|string|CacheManager manager) +  void|string|CacheManager manager, +  void|CacheManagerPrefs prefs)   //! Registers a new cache. Returns its @[CacheManager] instance.   //!   //! @[manager] can be a specific @[CacheManager] instance to use, a   //! string that specifies a type of manager (see   //! @[cache_manager_prefs]), or zero to select the default manager.   //! -  + //! If @[prefs] is given it should point to an instance of + //! @[CacheManagerPrefs] that defines various cache behavior. + //!   //! If the cache already exists, its current manager is simply - //! returned, and @[manager] has no effect. + //! returned, and @[manager] has no effect. It is however possible to + //! update @[prefs] for existing caches.   //!   //! Registering a cache is not mandatory before it is used - one will   //! be created automatically with the default manager otherwise.   //! Still, it's a good idea so that the cache list in the admin   //! interface gets populated timely.      {    Thread.MutexKey lock =    cache_mgmt_mutex->lock (2); // Called from cache_change_manager too.    -  if (CacheManager mgr = caches[cache_name]) +  if (CacheManager mgr = caches[cache_name]) { +  if (prefs) +  mgr->prefs[cache_name] = prefs;    return mgr; -  +  }       if (!manager) manager = cache_manager_prefs->default;    else if (stringp (manager)) {    string cache_type = manager;    manager = cache_manager_prefs[cache_type];    if (!manager) error ("Unknown cache manager type %O requested.\n",    cache_type);    }    -  +  string cache_name_prefix = (cache_name/":")[0]; +  CacheStats stats = manager->stats[cache_name_prefix]; +  if (!stats) { +  stats = CacheStats(); +  mib->merge(CacheStatsMIB(manager, cache_name_prefix, stats)); +  manager->stats[cache_name_prefix] = stats; +  }    caches[cache_name] = manager; -  manager->stats[cache_name] = CacheStats(); +  manager->stats[cache_name] = stats;    manager->lookup[cache_name] = ([]); -  +  if (prefs) +  manager->prefs[cache_name] = prefs;    return manager;   }      void cache_unregister (string cache_name)   //! Unregisters the specified cache. This empties the cache and also   //! removes it from the cache overview in the admin interface.   {    Thread.MutexKey lock = cache_mgmt_mutex->lock();    -  // vvv Relying on the interpreter lock from here. +     if (CacheManager mgr = m_delete (caches, cache_name)) {    mapping(mixed:CacheEntry) lm = m_delete (mgr->lookup, cache_name); -  CacheStats cs = m_delete (mgr->stats, cache_name); -  // ^^^ Relying on the interpreter lock to here. -  mgr->size -= cs->size; -  +     destruct (lock); -  +  +  // NB: The CacheStats object is still active here in order to let +  // the removal code update it.    foreach (lm;; CacheEntry entry)    mgr->remove_entry (cache_name, entry); -  +  +  lock = cache_mgmt_mutex->lock(); +  if (!caches[cache_name]) { +  // The cache is still gone, so remove its associated CacheStats. +  string cache_name_prefix = (cache_name/":")[0]; +  if (cache_name_prefix != cache_name) { +  m_delete(mgr->stats, cache_name);    } -  +  if (!caches[cache_name_prefix]) { +  string prefix = cache_name_prefix + ":"; +  foreach(caches; string name;) { +  if (has_prefix(name, prefix)) { +  // There's another cache that uses the CacheStats object. +  return;    } -  +  } +  // None of the caches uses the CacheStats object. +  m_delete(mgr->stats, cache_name_prefix); +  } +  } +  } + }      CacheManager cache_get_manager (string cache_name)   //! Returns the cache manager for the given cache, or zero if the   //! cache isn't registered.   {    return caches[cache_name];   }      void cache_change_manager (string cache_name, CacheManager manager)   //! Changes the manager for a cache. All the cache entries are moved
Roxen.git/server/base_server/cache.pike:1376:    Thread.MutexKey lock = cache_mgmt_mutex->lock();       // vvv Relying on the interpreter lock from here.    CacheManager old_mgr = m_delete (caches, cache_name);    if (old_mgr == manager)    caches[cache_name] = manager;    // ^^^ Relying on the interpreter lock to here.       else {    mapping(mixed:CacheEntry) old_lm = m_delete (old_mgr->lookup, cache_name); -  CacheStats old_cs = m_delete (old_mgr->stats, cache_name); +     // ^^^ Relying on the interpreter lock to here. -  old_mgr->size -= old_cs->size; +     cache_register (cache_name, manager);       // Move over the entries.    destruct (lock);    int entry_size_diff = (Pike.count_memory (0, manager->CacheEntry (0, 0)) -    Pike.count_memory (0, old_mgr->CacheEntry (0, 0)));    foreach (old_lm; mixed key; CacheEntry old_ent) {    old_mgr->remove_entry (cache_name, old_ent);    CacheEntry new_ent = manager->CacheEntry (key, old_ent->data);    new_ent->size = old_ent->size + entry_size_diff;
Roxen.git/server/base_server/cache.pike:1419:    }    else    foreach (lm;; CacheEntry entry) {    MORE_CACHE_WERR ("cache_expire: Removing %O\n", entry);    mgr->remove_entry (cn, entry);    }    }    }   }    + void cache_expire_by_prefix(string cache_name_prefix) + { +  map(filter(indices(caches), has_prefix, cache_name_prefix), cache_expire); + } +    void flush_memory_cache (void|string cache_name) {cache_expire (cache_name);}      void cache_clear_deltas()   {    cache_managers->clear_cache_context();   }      mixed cache_lookup (string cache_name, mixed key, void|mapping cache_context) - //! Looks up an entry in a cache. Returns @[UNDEFINED] if not found. + //! Looks up an entry in a cache. Returns @[UNDEFINED] if not found, or + //! if the stored value was zero and the cache garb evicted the entry.   //!   //! @[cache_context] is an optional mapping used to pass info between   //! @[cache_lookup] and @[cache_set], which some cache managers need   //! to determine the cost of the created entry (the work done between   //! a failed @[cache_lookup] and the following @[cache_set] with the   //! same key is assumed to be the creation of the cache entry).   //!   //! If @[cache_context] is not specified, a thread local mapping is   //! used. @[cache_context] is necessary when @[cache_lookup] and   //! @[cache_set] are called from different threads, or in different
Roxen.git/server/base_server/cache.pike:1510:   //! @param cache_name   //! The name of the cache. The cache has preferably been created with   //! @[cache_register], but otherwise it is created on-demand using the   //! default cache manager.   //!   //! @param key   //! The key for the cache entry. Normally a string, but can be   //! anything that works as an index in a mapping.   //!   //! @param data - //! The payload data. This cannot be a zero, since the cache garb will - //! consider that a destructed object and evict it from the cache. + //! The payload data. Note that if it is zero, the cache garb will + //! consider it a destructed object and evict it from the cache so + //! future lookups may return UNDEFINED instead.   //!   //! @param timeout   //! If nonzero, sets the maximum time in seconds that the entry is   //! valid.   //!   //! @param cache_context   //! The cache context mapping given to the earlier @[cache_lookup]   //! which failed to find the entry that this call adds to the cache.   //! See @[cache_lookup] for more details.   //!
Roxen.git/server/base_server/cache.pike:1535:   //!   //! @returns   //! Returns @[data].   //!   //! @note   //! Cache managers commonly uses the time from the closest preceding   //! @[cache_lookup] call to calculate a weight for the entry. That   //! means the caller should avoid repeated calls to @[cache_set] for   //! the same entry.   { -  ASSERT_IF_DEBUG (data); -  +     CacheManager mgr = caches[cache_name] || cache_register (cache_name);    CacheEntry new_entry = mgr->CacheEntry (key, data);       // We always create a new entry, even if the given key already    // exists in the cache with the same data. That's to ensure we get    // an up-to-date cost for the entry. (It's also a bit tricky to    // atomically check for an existing entry here before creating a new    // one.)    - #ifdef DEBUG_COUNT_MEM -  mapping opts = (["lookahead": DEBUG_COUNT_MEM - 1, -  "collect_stats": 1, -  "collect_direct_externals": 1, -  ]); -  float t = gauge { - #else - #define opts 0 - #endif -  -  if (function(int|mapping:int) cm_cb = -  objectp (data) && data->cache_count_memory) -  new_entry->size = cm_cb (opts) + Pike.count_memory (-1, new_entry, key); -  else -  new_entry->size = Pike.count_memory (opts, new_entry, key, data); -  - #ifdef DEBUG_COUNT_MEM -  }; -  werror ("%O: la %d size %d time %g int %d cyc %d ext %d vis %d revis %d " -  "rnd %d wqa %d\n", -  new_entry, opts->lookahead, opts->size, t, opts->internal, -  opts->cyclic, opts->external, opts->visits, opts->revisits, -  opts->rounds, opts->work_queue_alloc); -  - #if 0 -  if (opts->external) { -  opts->collect_direct_externals = 1; -  // Raise the lookahead to 1 to recurse the closest externals. -  if (opts->lookahead < 1) opts->lookahead = 1; -  -  if (function(int|mapping:int) cm_cb = -  objectp (data) && data->cache_count_memory) -  res = cm_cb (opts) + Pike.count_memory (-1, entry, key); -  else -  res = Pike.count_memory (opts, entry, key, data); -  -  array exts = opts->collect_direct_externals; -  werror ("Externals found using lookahead %d: %O\n", -  opts->lookahead, exts); - #if 0 -  foreach (exts, mixed ext) -  if (objectp (ext) && ext->locate_my_ext_refs) { -  werror ("Refs to %O:\n", ext); -  _locate_references (ext); -  } - #endif -  } - #endif -  - #endif // DEBUG_COUNT_MEM - #undef opts -  - #ifdef DEBUG_CACHE_SIZES -  new_entry->cmp_size = cmp_sizeof_cache_entry (cache_name, new_entry); - #endif -  +     if (timeout)    new_entry->timeout = time (1) + timeout;       mgr->add_entry (cache_name, new_entry,    intp (cache_context) && cache_context,    mappingp (cache_context) && cache_context);       MORE_CACHE_WERR ("cache_set (%O, %s, %s, %O): %O\n",    cache_name, RXML.utils.format_short (key),    sprintf (objectp (data) ? "%O" : "%t", data), timeout,
Roxen.git/server/base_server/cache.pike:1624:    return data;   }      void cache_remove (string cache_name, mixed key)   //! Removes an entry from the cache.   //!   //! @note   //! If @[key] was zero, this function used to remove the whole cache.   //! Use @[cache_expire] for that instead.   { -  MORE_CACHE_WERR ("cache_remove (%O, %O)\n", cache_name, key); +  MORE_CACHE_WERR ("cache_remove (%O, %s)\n", +  cache_name, RXML.utils.format_short (key));    if (CacheManager mgr = caches[cache_name])    if (mapping(mixed:CacheEntry) lm = mgr->lookup[cache_name])    if (CacheEntry entry = lm[key])    mgr->remove_entry (cache_name, entry);   }      mapping(mixed:CacheEntry) cache_entries (string cache_name)   //! Returns the lookup mapping for the given named cache. Don't be   //! destructive on the returned mapping or anything inside it.   {
Roxen.git/server/base_server/cache.pike:1657:    return indices (cache_entries (cache_name));   }      mapping(CacheManager:mapping(string:CacheStats)) cache_stats()   //! Returns the complete cache statistics. For each cache manager, a   //! mapping with the named caches it handles is returned, with their   //! respective @[CacheStat] objects. Don't be destructive on any part   //! of the returned value.   {    mapping(CacheManager:mapping(string:CacheStats)) res = ([]); -  foreach (cache_managers, CacheManager mgr) -  res[mgr] = mgr->stats; +  foreach (cache_managers, CacheManager mgr) { +  res[mgr] = ([]); +  foreach(mgr->stats; string cache_name; CacheStats cs) { +  if (has_value(cache_name, ':')) continue; +  res[mgr][cache_name] = cs; +  } +  }    return res;   }      // GC statistics. These are decaying sums/averages over the last   // gc_stats_period seconds.   constant gc_stats_period = 60 * 60;   float sum_gc_runs = 0.0, sum_gc_time = 0.0;   float sum_destruct_garbage_size = 0.0;   float sum_timeout_garbage_size = 0.0;   float avg_destruct_garbage_ratio = 0.0;
Roxen.git/server/base_server/cache.pike:2038:    "persistence INT UNSIGNED NOT NULL DEFAULT 0, "    "data BLOB NOT NULL)");    return 1;   }      // Sets up the session database tables.   private void setup_tables() {    setup_session_table ("local");    master()->resolv("DBManager.is_module_table")    ( 0, "local", "session_cache", "Used by the session manager" ); +  master()->resolv("DBManager.inhibit_backups")( "local", "session_cache" );   }      //! Initializes the session handler.   void init_session_cache() {    db = (([function(string:function(string:object(Sql.Sql)))]master()->resolv)    ("DBManager.cached_get"));    setup_tables();   }      void init_call_outs()