Blame SOURCES/0062-Ticket-49330-Improve-ndn-cache-performance-1.3.6.patch

6f51e1
From 2975f68e139169ee2d2259cfbbb2a15b54dc3724 Mon Sep 17 00:00:00 2001
6f51e1
From: William Brown <firstyear@redhat.com>
6f51e1
Date: Wed, 26 Jul 2017 11:01:49 +1000
6f51e1
Subject: [PATCH] Ticket 49330 - Improve ndn cache performance 1.3.6
6f51e1
6f51e1
Backport from 1.3.7 master.
6f51e1
6f51e1
Bug Description:  Normalised DN's are a costly process to update
6f51e1
and maintain. As a result, a normalised DN cache was created. Yet
6f51e1
it was never able to perform well. In some datasets with large sets
6f51e1
of dn attr types, the NDN cache actively hurt performance.
6f51e1
6f51e1
The issue stemmed from 3 major issues in the design of the NDN
6f51e1
cache.
6f51e1
6f51e1
First, it is a global cache which means it exists behind
6f51e1
a rwlock. This causes delay as threads wait behind the lock
6f51e1
to access or update the cache (especially on a miss).
6f51e1
6f51e1
Second, the cache was limited to 4073 buckets. Despite the fact
6f51e1
that a prime number on a hash causes a skew in distribution,
6f51e1
this was in an NSPR hash - which does not grow dynamically,
6f51e1
rather devolving a bucket to a linked list. AS a result, once you
6f51e1
passed ~3000 your lookup performance would degrade rapidly to O(1)
6f51e1
6f51e1
Finally, the cache's lru policy did not evict least used - it
6f51e1
evicted the 10,000 least used. So if you tuned your cache
6f51e1
to match the NSPR map, every inclusion that would trigger a
6f51e1
delete of old values would effectively empty your cache. ON bigger
6f51e1
set sizes, this has to walk the map (at O(1)) to clean 10,000
6f51e1
elements.
6f51e1
6f51e1
Premature optimisation strikes again ....
6f51e1
6f51e1
Fix Description:  Throw it out. Rewrite. We now use a hash
6f51e1
algo that has proper distribution across a set. The hash
6f51e1
sizes slots to a power of two. Finally, each thread has
6f51e1
a private cache rather than shared which completely eliminates
6f51e1
a lock contention and even NUMA performance issues.
6f51e1
6f51e1
Interestingly this fix should have improvements for DB
6f51e1
imports, memberof and refint performance and more.
6f51e1
6f51e1
Some testing has shown in simple search workloads a 10%
6f51e1
improvement in throughput, and on complex searches a 47x
6f51e1
improvement.
6f51e1
6f51e1
https://pagure.io/389-ds-base/issue/49330
6f51e1
6f51e1
Author: wibrown
6f51e1
6f51e1
Review by: lkrispen, tbordaz
6f51e1
---
6f51e1
 ldap/servers/slapd/back-ldbm/monitor.c |  11 +-
6f51e1
 ldap/servers/slapd/dn.c                | 809 +++++++++++++++++++++------------
6f51e1
 ldap/servers/slapd/slapi-private.h     |   2 +-
6f51e1
 3 files changed, 527 insertions(+), 295 deletions(-)
6f51e1
6f51e1
diff --git a/ldap/servers/slapd/back-ldbm/monitor.c b/ldap/servers/slapd/back-ldbm/monitor.c
6f51e1
index c58b069..aa7d709 100644
6f51e1
--- a/ldap/servers/slapd/back-ldbm/monitor.c
6f51e1
+++ b/ldap/servers/slapd/back-ldbm/monitor.c
6f51e1
@@ -43,6 +43,9 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
6f51e1
     PRUint64 hits, tries;
6f51e1
     long nentries, maxentries, count;
6f51e1
     size_t size, maxsize;
6f51e1
+    size_t thread_size;
6f51e1
+    size_t evicts;
6f51e1
+    size_t slots;
6f51e1
 /* NPCTE fix for bugid 544365, esc 0. <p.r> <04-Jul-2001> */
6f51e1
     struct stat astat;
6f51e1
 /* end of NPCTE fix for bugid 544365 */
6f51e1
@@ -118,7 +121,7 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
6f51e1
     }
6f51e1
     /* normalized dn cache stats */
6f51e1
     if(ndn_cache_started()){
6f51e1
-        ndn_cache_get_stats(&hits, &tries, &size, &maxsize, &count);
6f51e1
+        ndn_cache_get_stats(&hits, &tries, &size, &maxsize, &thread_size, &evicts, &slots, &count);
6f51e1
         sprintf(buf, "%" PRIu64, tries);
6f51e1
         MSET("normalizedDnCacheTries");
6f51e1
         sprintf(buf, "%" PRIu64, hits);
6f51e1
@@ -127,6 +130,8 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
6f51e1
         MSET("normalizedDnCacheMisses");
6f51e1
         sprintf(buf, "%lu", (unsigned long)(100.0*(double)hits / (double)(tries > 0 ? tries : 1)));
6f51e1
         MSET("normalizedDnCacheHitRatio");
6f51e1
+        sprintf(buf, "%"PRIu64, evicts);
6f51e1
+        MSET("NormalizedDnCacheEvictions");
6f51e1
         sprintf(buf, "%lu", (long unsigned int)size);
6f51e1
         MSET("currentNormalizedDnCacheSize");
6f51e1
         if(maxsize == 0){
6f51e1
@@ -135,6 +140,10 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
6f51e1
         	sprintf(buf, "%lu", (long unsigned int)maxsize);
6f51e1
         }
6f51e1
         MSET("maxNormalizedDnCacheSize");
6f51e1
+        sprintf(buf, "%"PRIu64, thread_size);
6f51e1
+        MSET("NormalizedDnCacheThreadSize");
6f51e1
+        sprintf(buf, "%"PRIu64, slots);
6f51e1
+        MSET("NormalizedDnCacheThreadSlots");
6f51e1
         sprintf(buf, "%ld", count);
6f51e1
         MSET("currentNormalizedDnCacheCount");
6f51e1
     }
6f51e1
diff --git a/ldap/servers/slapd/dn.c b/ldap/servers/slapd/dn.c
6f51e1
index fa3909f..9cb3e7b 100644
6f51e1
--- a/ldap/servers/slapd/dn.c
6f51e1
+++ b/ldap/servers/slapd/dn.c
6f51e1
@@ -22,6 +22,24 @@
6f51e1
 #include "slap.h"
6f51e1
 #include <plhash.h>
6f51e1
 
6f51e1
+#include <inttypes.h>
6f51e1
+#include <stddef.h> /* for size_t */
6f51e1
+
6f51e1
+#if defined(HAVE_SYS_ENDIAN_H)
6f51e1
+#include <sys endian.h="">
6f51e1
+#elif defined(HAVE_ENDIAN_H)
6f51e1
+#include <endian.h>
6f51e1
+#else
6f51e1
+#error platform header for endian detection not found.
6f51e1
+#endif
6f51e1
+
6f51e1
+/* See: http://sourceforge.net/p/predef/wiki/Endianness/ */
6f51e1
+#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN
6f51e1
+#define _le64toh(x) ((uint64_t)(x))
6f51e1
+#else
6f51e1
+#define _le64toh(x) le64toh(x)
6f51e1
+#endif
6f51e1
+
6f51e1
 #undef SDN_DEBUG
6f51e1
 
6f51e1
 static void add_rdn_av( char *avstart, char *avend, int *rdn_av_countp,
6f51e1
@@ -33,52 +51,89 @@ static void rdn_av_swap( struct berval *av1, struct berval *av2, int escape );
6f51e1
 static int does_cn_uses_dn_syntax_in_dns(char *type, char *dn);
6f51e1
 
6f51e1
 /* normalized dn cache related definitions*/
6f51e1
-struct
6f51e1
-ndn_cache_lru
6f51e1
-{
6f51e1
-    struct ndn_cache_lru *prev;
6f51e1
-    struct ndn_cache_lru *next;
6f51e1
-    char *key;
6f51e1
-};
6f51e1
-
6f51e1
-struct
6f51e1
-ndn_cache_ctx
6f51e1
-{
6f51e1
-    struct ndn_cache_lru *head;
6f51e1
-    struct ndn_cache_lru *tail;
6f51e1
+struct ndn_cache_stats {
6f51e1
     Slapi_Counter *cache_hits;
6f51e1
     Slapi_Counter *cache_tries;
6f51e1
-    Slapi_Counter *cache_misses;
6f51e1
-    size_t cache_size;
6f51e1
-    size_t cache_max_size;
6f51e1
-    long cache_count;
6f51e1
+    Slapi_Counter *cache_count;
6f51e1
+    Slapi_Counter *cache_size;
6f51e1
+    Slapi_Counter *cache_evicts;
6f51e1
+    size_t max_size;
6f51e1
+    size_t thread_max_size;
6f51e1
+    size_t slots;
6f51e1
 };
6f51e1
 
6f51e1
-struct
6f51e1
-ndn_hash_val
6f51e1
-{
6f51e1
+struct ndn_cache_value {
6f51e1
+    size_t size;
6f51e1
+    size_t slot;
6f51e1
+    char *dn;
6f51e1
     char *ndn;
6f51e1
-    size_t len;
6f51e1
-    int size;
6f51e1
-    struct ndn_cache_lru *lru_node; /* used to speed up lru shuffling */
6f51e1
+    struct ndn_cache_value *next;
6f51e1
+    struct ndn_cache_value *prev;
6f51e1
+    struct ndn_cache_value *child;
6f51e1
+};
6f51e1
+
6f51e1
+/*
6f51e1
+ * This uses a similar alloc trick to IDList to keep
6f51e1
+ * The amount of derefs small.
6f51e1
+ */
6f51e1
+struct ndn_cache {
6f51e1
+    /*
6f51e1
+     * We keep per thread stats and flush them occasionally
6f51e1
+     */
6f51e1
+    size_t max_size;
6f51e1
+    /* Need to track this because we need to provide diffs to counter */
6f51e1
+    size_t last_count;
6f51e1
+    size_t count;
6f51e1
+    /* Number of ops */
6f51e1
+    size_t tries;
6f51e1
+    /* hit vs miss. in theroy miss == tries - hits.*/
6f51e1
+    size_t hits;
6f51e1
+    /* How many values we kicked out */
6f51e1
+    size_t evicts;
6f51e1
+    /* Need to track this because we need to provide diffs to counter */
6f51e1
+    size_t last_size;
6f51e1
+    size_t size;
6f51e1
+
6f51e1
+    size_t slots;
6f51e1
+    /*
6f51e1
+     * This is used by siphash to prevent hash bugket attacks
6f51e1
+     */
6f51e1
+    char key[16];
6f51e1
+
6f51e1
+    struct ndn_cache_value *head;
6f51e1
+    struct ndn_cache_value *tail;
6f51e1
+    struct ndn_cache_value *table[1];
6f51e1
 };
6f51e1
 
6f51e1
-#define NDN_FLUSH_COUNT 10000 /* number of DN's to remove when cache fills up */
6f51e1
-#define NDN_MIN_COUNT 1000 /* the minimum number of DN's to keep in the cache */
6f51e1
-#define NDN_CACHE_BUCKETS 2053 /* prime number */
6f51e1
+/*
6f51e1
+ * This means we need 1 MB minimum per thread
6f51e1
+ * 
6f51e1
+ */
6f51e1
+#define NDN_CACHE_MINIMUM_CAPACITY 1048576
6f51e1
+/*
6f51e1
+ * This helps us define the number of hashtable slots
6f51e1
+ * to create. We assume an average DN is 64 chars long
6f51e1
+ * This way we end up we a ht entry of:
6f51e1
+ * 8 bytes: from the table pointing to us.
6f51e1
+ * 8 bytes: next ptr
6f51e1
+ * 8 bytes: prev ptr
6f51e1
+ * 8 bytes + 64: dn
6f51e1
+ * 8 bytes + 64: ndn itself.
6f51e1
+ * This gives us 168 bytes. In theory this means
6f51e1
+ * 6241 entries, but we have to clamp this to a power of
6f51e1
+ * two, so we have 8192 slots. In reality, dns may be
6f51e1
+ * shorter *and* the dn may be the same as the ndn
6f51e1
+ * so we *may* store more ndns that this. Again, a good reason
6f51e1
+ * to round the ht size up!
6f51e1
+ */
6f51e1
+#define NDN_ENTRY_AVG_SIZE 168
6f51e1
+/*
6f51e1
+ * After how many operations do we sync our per-thread stats.
6f51e1
+ */
6f51e1
+#define NDN_STAT_COMMIT_FREQUENCY 256
6f51e1
 
6f51e1
-static PLHashNumber ndn_hash_string(const void *key);
6f51e1
 static int ndn_cache_lookup(char *dn, size_t dn_len, char **result, char **udn, int *rc);
6f51e1
-static void ndn_cache_update_lru(struct ndn_cache_lru **node);
6f51e1
 static void ndn_cache_add(char *dn, size_t dn_len, char *ndn, size_t ndn_len);
6f51e1
-static void ndn_cache_delete(char *dn);
6f51e1
-static void ndn_cache_flush(void);
6f51e1
-static void ndn_cache_free(void);
6f51e1
-static int ndn_started = 0;
6f51e1
-static PRLock *lru_lock = NULL;
6f51e1
-static Slapi_RWLock *ndn_cache_lock = NULL;
6f51e1
-static struct ndn_cache_ctx *ndn_cache = NULL;
6f51e1
-static PLHashTable *ndn_cache_hashtable = NULL;
6f51e1
 
6f51e1
 #define ISBLANK(c)	((c) == ' ')
6f51e1
 #define ISBLANKSTR(s)	(((*(s)) == '2') && (*((s)+1) == '0'))
6f51e1
@@ -2768,166 +2823,408 @@ slapi_sdn_get_size(const Slapi_DN *sdn)
6f51e1
  *
6f51e1
  */
6f51e1
 
6f51e1
+/* <mit license="">
6f51e1
+ Copyright (c) 2013  Marek Majkowski <marek@popcount.org>
6f51e1
+
6f51e1
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6f51e1
+ of this software and associated documentation files (the "Software"), to deal
6f51e1
+ in the Software without restriction, including without limitation the rights
6f51e1
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
6f51e1
+ copies of the Software, and to permit persons to whom the Software is
6f51e1
+ furnished to do so, subject to the following conditions:
6f51e1
+
6f51e1
+ The above copyright notice and this permission notice shall be included in
6f51e1
+ all copies or substantial portions of the Software.
6f51e1
+
6f51e1
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
6f51e1
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6f51e1
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
6f51e1
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
6f51e1
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
6f51e1
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
6f51e1
+ THE SOFTWARE.
6f51e1
+ </mit>
6f51e1
+
6f51e1
+ Original location:
6f51e1
+    https://github.com/majek/csiphash/
6f51e1
+
6f51e1
+ Solution inspired by code from:
6f51e1
+    Samuel Neves (supercop/crypto_auth/siphash24/little)
6f51e1
+    djb (supercop/crypto_auth/siphash24/little2)
6f51e1
+    Jean-Philippe Aumasson (https://131002.net/siphash/siphash24.c)
6f51e1
+*/
6f51e1
+
6f51e1
+#define ROTATE(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
6f51e1
+
6f51e1
+#define HALF_ROUND(a, b, c, d, s, t) \
6f51e1
+    a += b;                          \
6f51e1
+    c += d;                          \
6f51e1
+    b = ROTATE(b, s) ^ a;            \
6f51e1
+    d = ROTATE(d, t) ^ c;            \
6f51e1
+    a = ROTATE(a, 32);
6f51e1
+
6f51e1
+#define ROUND(v0, v1, v2, v3)           \
6f51e1
+    HALF_ROUND(v0, v1, v2, v3, 13, 16); \
6f51e1
+    HALF_ROUND(v2, v1, v0, v3, 17, 21)
6f51e1
+
6f51e1
+#define cROUND(v0, v1, v2, v3) \
6f51e1
+    ROUND(v0, v1, v2, v3)
6f51e1
+
6f51e1
+#define dROUND(v0, v1, v2, v3) \
6f51e1
+    ROUND(v0, v1, v2, v3);     \
6f51e1
+    ROUND(v0, v1, v2, v3);     \
6f51e1
+    ROUND(v0, v1, v2, v3)
6f51e1
+
6f51e1
+
6f51e1
+static uint64_t
6f51e1
+sds_siphash13(const void *src, size_t src_sz, const char key[16])
6f51e1
+{
6f51e1
+    const uint64_t *_key = (uint64_t *)key;
6f51e1
+    uint64_t k0 = _le64toh(_key[0]);
6f51e1
+    uint64_t k1 = _le64toh(_key[1]);
6f51e1
+    uint64_t b = (uint64_t)src_sz << 56;
6f51e1
+    const uint64_t *in = (uint64_t *)src;
6f51e1
+
6f51e1
+    uint64_t v0 = k0 ^ 0x736f6d6570736575ULL;
6f51e1
+    uint64_t v1 = k1 ^ 0x646f72616e646f6dULL;
6f51e1
+    uint64_t v2 = k0 ^ 0x6c7967656e657261ULL;
6f51e1
+    uint64_t v3 = k1 ^ 0x7465646279746573ULL;
6f51e1
+
6f51e1
+    while (src_sz >= 8) {
6f51e1
+        uint64_t mi = _le64toh(*in);
6f51e1
+        in += 1;
6f51e1
+        src_sz -= 8;
6f51e1
+        v3 ^= mi;
6f51e1
+        // cround
6f51e1
+        cROUND(v0, v1, v2, v3);
6f51e1
+        v0 ^= mi;
6f51e1
+    }
6f51e1
+
6f51e1
+    uint64_t t = 0;
6f51e1
+    uint8_t *pt = (uint8_t *)&t;
6f51e1
+    uint8_t *m = (uint8_t *)in;
6f51e1
+
6f51e1
+    switch (src_sz) {
6f51e1
+    case 7:
6f51e1
+        pt[6] = m[6]; /* FALLTHRU */
6f51e1
+    case 6:
6f51e1
+        pt[5] = m[5]; /* FALLTHRU */
6f51e1
+    case 5:
6f51e1
+        pt[4] = m[4]; /* FALLTHRU */
6f51e1
+    case 4:
6f51e1
+        *((uint32_t *)&pt[0]) = *((uint32_t *)&m[0]);
6f51e1
+        break;
6f51e1
+    case 3:
6f51e1
+        pt[2] = m[2]; /* FALLTHRU */
6f51e1
+    case 2:
6f51e1
+        pt[1] = m[1]; /* FALLTHRU */
6f51e1
+    case 1:
6f51e1
+        pt[0] = m[0]; /* FALLTHRU */
6f51e1
+    }
6f51e1
+    b |= _le64toh(t);
6f51e1
+
6f51e1
+    v3 ^= b;
6f51e1
+    // cround
6f51e1
+    cROUND(v0, v1, v2, v3);
6f51e1
+    v0 ^= b;
6f51e1
+    v2 ^= 0xff;
6f51e1
+    // dround
6f51e1
+    dROUND(v0, v1, v2, v3);
6f51e1
+    return (v0 ^ v1) ^ (v2 ^ v3);
6f51e1
+}
6f51e1
+
6f51e1
+static pthread_key_t ndn_cache_key;
6f51e1
+static pthread_once_t ndn_cache_key_once = PTHREAD_ONCE_INIT;
6f51e1
+static struct ndn_cache_stats t_cache_stats = {0};
6f51e1
 /*
6f51e1
- *  Hashing function using Bernstein's method
6f51e1
+ * WARNING: For some reason we try to use the NDN cache *before*
6f51e1
+ * we have a chance to configure it. As a result, we need to rely
6f51e1
+ * on a trick in the way we start, that we start in one thread
6f51e1
+ * so we can manipulate ints as though they were atomics, then
6f51e1
+ * we start in *one* thread, so it's set, then when threads
6f51e1
+ * fork the get barriers, so we can go from there. However we *CANNOT*
6f51e1
+ * change this at runtime without expensive atomics per op, so lets
6f51e1
+ * not bother until we improve libglobs to be COW.
6f51e1
  */
6f51e1
-static PLHashNumber
6f51e1
-ndn_hash_string(const void *key)
6f51e1
-{
6f51e1
-    PLHashNumber hash = 5381;
6f51e1
-    unsigned char *x = (unsigned char *)key;
6f51e1
-    int c;
6f51e1
+static int32_t ndn_enabled = 0;
6f51e1
+
6f51e1
+static struct ndn_cache *
6f51e1
+ndn_thread_cache_create(size_t thread_max_size, size_t slots) {
6f51e1
+    size_t t_cache_size = sizeof(struct ndn_cache) + (slots * sizeof(struct ndn_cache_value *));
6f51e1
+    struct ndn_cache *t_cache = (struct ndn_cache *)slapi_ch_calloc(1, t_cache_size);
6f51e1
+
6f51e1
+    t_cache->max_size = thread_max_size;
6f51e1
+    t_cache->slots = slots;
6f51e1
 
6f51e1
-    while ((c = *x++)){
6f51e1
-        hash = ((hash << 5) + hash) ^ c;
6f51e1
+    return t_cache;
6f51e1
+}
6f51e1
+
6f51e1
+static void
6f51e1
+ndn_thread_cache_commit_status(struct ndn_cache *t_cache) {
6f51e1
+    /*
6f51e1
+     * Every so often we commit these atomically. We do this infrequently
6f51e1
+     * to avoid the costly atomics.
6f51e1
+     */
6f51e1
+    if (t_cache->tries % NDN_STAT_COMMIT_FREQUENCY == 0) {
6f51e1
+        /* We can just add tries and hits. */
6f51e1
+        slapi_counter_add(t_cache_stats.cache_evicts, t_cache->evicts);
6f51e1
+        slapi_counter_add(t_cache_stats.cache_tries, t_cache->tries);
6f51e1
+        slapi_counter_add(t_cache_stats.cache_hits, t_cache->hits);
6f51e1
+        t_cache->hits = 0;
6f51e1
+        t_cache->tries = 0;
6f51e1
+        t_cache->evicts = 0;
6f51e1
+        /* Count and size need diff */
6f51e1
+        int64_t diff = (t_cache->size - t_cache->last_size);
6f51e1
+        if (diff > 0) {
6f51e1
+            // We have more ....
6f51e1
+            slapi_counter_add(t_cache_stats.cache_size, (uint64_t)diff);
6f51e1
+        } else if (diff < 0) {
6f51e1
+            slapi_counter_subtract(t_cache_stats.cache_size, (uint64_t)llabs(diff));
6f51e1
+        }
6f51e1
+        t_cache->last_size = t_cache->size;
6f51e1
+
6f51e1
+        diff = (t_cache->count - t_cache->last_count);
6f51e1
+        if (diff > 0) {
6f51e1
+            // We have more ....
6f51e1
+            slapi_counter_add(t_cache_stats.cache_count, (uint64_t)diff);
6f51e1
+        } else if (diff < 0) {
6f51e1
+            slapi_counter_subtract(t_cache_stats.cache_count, (uint64_t)llabs(diff));
6f51e1
+        }
6f51e1
+        t_cache->last_count = t_cache->count;
6f51e1
+
6f51e1
+    }
6f51e1
+}
6f51e1
+
6f51e1
+static void
6f51e1
+ndn_thread_cache_value_destroy(struct ndn_cache *t_cache, struct ndn_cache_value *v) {
6f51e1
+    /* Update stats */
6f51e1
+    t_cache->size = t_cache->size - v->size;
6f51e1
+    t_cache->count--;
6f51e1
+    t_cache->evicts++;
6f51e1
+
6f51e1
+    if (v == t_cache->head) {
6f51e1
+        t_cache->head = v->prev;
6f51e1
+    }
6f51e1
+    if (v == t_cache->tail) {
6f51e1
+        t_cache->tail = v->next;
6f51e1
+    }
6f51e1
+
6f51e1
+    /* Cut the node out. */
6f51e1
+    if (v->next != NULL) {
6f51e1
+        v->next->prev = v->prev;
6f51e1
+    }
6f51e1
+    if (v->prev != NULL) {
6f51e1
+        v->prev->next = v->next;
6f51e1
+    }
6f51e1
+    /* Set the pointer in the table to NULL */
6f51e1
+    /* Now see if we were in a list */
6f51e1
+    struct ndn_cache_value *slot_node = t_cache->table[v->slot];
6f51e1
+    if (slot_node == v) {
6f51e1
+        t_cache->table[v->slot] = v->child;
6f51e1
+    } else {
6f51e1
+        struct ndn_cache_value *former_slot_node = NULL;
6f51e1
+        do {
6f51e1
+            former_slot_node = slot_node;
6f51e1
+            slot_node = slot_node->child;
6f51e1
+        } while(slot_node != v);
6f51e1
+        /* Okay, now slot_node is us, and former is our parent */
6f51e1
+        former_slot_node->child = v->child;
6f51e1
+    }
6f51e1
+
6f51e1
+    slapi_ch_free((void **)&(v->dn));
6f51e1
+    slapi_ch_free((void **)&(v->ndn));
6f51e1
+    slapi_ch_free((void **)&v);
6f51e1
+}
6f51e1
+
6f51e1
+static void
6f51e1
+ndn_thread_cache_destroy(void *v_cache) {
6f51e1
+    struct ndn_cache *t_cache = (struct ndn_cache *)v_cache;
6f51e1
+    /*
6f51e1
+     * FREE ALL THE NODES!!!
6f51e1
+     */
6f51e1
+    struct ndn_cache_value *node = t_cache->tail;
6f51e1
+    struct ndn_cache_value *next_node = NULL;
6f51e1
+    while (node) {
6f51e1
+        next_node = node->next;
6f51e1
+        ndn_thread_cache_value_destroy(t_cache, node);
6f51e1
+        node = next_node;
6f51e1
+    }
6f51e1
+    slapi_ch_free((void **)&t_cache);
6f51e1
+}
6f51e1
+
6f51e1
+static void
6f51e1
+ndn_cache_key_init() {
6f51e1
+    if (pthread_key_create(&ndn_cache_key, ndn_thread_cache_destroy) != 0) {
6f51e1
+        /* Log a scary warning? */
6f51e1
+        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_init", "Failed to create pthread key, aborting.\n");
6f51e1
     }
6f51e1
-    return hash;
6f51e1
 }
6f51e1
 
6f51e1
 void
6f51e1
 ndn_cache_init()
6f51e1
 {
6f51e1
-    if(!config_get_ndn_cache_enabled() || ndn_started){
6f51e1
+    ndn_enabled = config_get_ndn_cache_enabled();
6f51e1
+    if (ndn_enabled == 0) {
6f51e1
+        /*
6f51e1
+         * Don't configure the keys or anything, need a restart
6f51e1
+         * to enable. We'll just never use ndn cache in this
6f51e1
+         * run.
6f51e1
+         */
6f51e1
         return;
6f51e1
     }
6f51e1
-    ndn_cache_hashtable = PL_NewHashTable( NDN_CACHE_BUCKETS, ndn_hash_string, PL_CompareStrings, PL_CompareValues, 0, 0);
6f51e1
-    ndn_cache = (struct ndn_cache_ctx *)slapi_ch_malloc(sizeof(struct ndn_cache_ctx));
6f51e1
-    ndn_cache->cache_max_size = config_get_ndn_cache_size();
6f51e1
-    ndn_cache->cache_hits = slapi_counter_new();
6f51e1
-    ndn_cache->cache_tries = slapi_counter_new();
6f51e1
-    ndn_cache->cache_misses = slapi_counter_new();
6f51e1
-    ndn_cache->cache_count = 0;
6f51e1
-    ndn_cache->cache_size = sizeof(struct ndn_cache_ctx) + sizeof(PLHashTable) + sizeof(PLHashTable);
6f51e1
-    ndn_cache->head = NULL;
6f51e1
-    ndn_cache->tail = NULL;
6f51e1
-    ndn_started = 1;
6f51e1
-    if ( NULL == ( lru_lock = PR_NewLock()) ||  NULL == ( ndn_cache_lock = slapi_new_rwlock())) {
6f51e1
-        ndn_cache_destroy();
6f51e1
-        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_init", "Failed to create locks.  Disabling cache.\n" );
6f51e1
+
6f51e1
+    /* Create the pthread key */
6f51e1
+    (void)pthread_once(&ndn_cache_key_once, ndn_cache_key_init);
6f51e1
+
6f51e1
+    /* Create the global stats. */
6f51e1
+    t_cache_stats.max_size = config_get_ndn_cache_size();
6f51e1
+    t_cache_stats.cache_evicts = slapi_counter_new();
6f51e1
+    t_cache_stats.cache_tries = slapi_counter_new();
6f51e1
+    t_cache_stats.cache_hits = slapi_counter_new();
6f51e1
+    t_cache_stats.cache_count = slapi_counter_new();
6f51e1
+    t_cache_stats.cache_size = slapi_counter_new();
6f51e1
+    /* Get thread numbers and calc the per thread size */
6f51e1
+    int32_t maxthreads = (int32_t)config_get_threadnumber();
6f51e1
+    size_t tentative_size = t_cache_stats.max_size / maxthreads;
6f51e1
+    if (tentative_size < NDN_CACHE_MINIMUM_CAPACITY) {
6f51e1
+        tentative_size = NDN_CACHE_MINIMUM_CAPACITY;
6f51e1
+        t_cache_stats.max_size = NDN_CACHE_MINIMUM_CAPACITY * maxthreads;
6f51e1
+    }
6f51e1
+    t_cache_stats.thread_max_size = tentative_size;
6f51e1
+
6f51e1
+    /*
6f51e1
+     * Slots *must* be a power of two, even if the number of entries
6f51e1
+     * we store will be *less* than this.
6f51e1
+     */
6f51e1
+    size_t possible_elements = tentative_size / NDN_ENTRY_AVG_SIZE;
6f51e1
+    /*
6f51e1
+     * So this is like 1048576 / 168, so we get 6241. Now we need to
6f51e1
+     * shift this to get the number of bits.
6f51e1
+     */
6f51e1
+    size_t shifts = 0;
6f51e1
+    while (possible_elements > 0) {
6f51e1
+        shifts++;
6f51e1
+        possible_elements = possible_elements >> 1;
6f51e1
     }
6f51e1
+    /*
6f51e1
+     * So now we can use this to make the slot count.
6f51e1
+     */
6f51e1
+    t_cache_stats.slots = 1 << shifts;
6f51e1
+    /* Done? */
6f51e1
+    return;
6f51e1
 }
6f51e1
 
6f51e1
 void
6f51e1
 ndn_cache_destroy()
6f51e1
 {
6f51e1
-    if(!ndn_started){
6f51e1
+    if (ndn_enabled == 0) {
6f51e1
         return;
6f51e1
     }
6f51e1
-    if(lru_lock){
6f51e1
-        PR_DestroyLock(lru_lock);
6f51e1
-        lru_lock = NULL;
6f51e1
-    }
6f51e1
-    if(ndn_cache_lock){
6f51e1
-        slapi_destroy_rwlock(ndn_cache_lock);
6f51e1
-        ndn_cache_lock = NULL;
6f51e1
-    }
6f51e1
-    if(ndn_cache_hashtable){
6f51e1
-        ndn_cache_free();
6f51e1
-        PL_HashTableDestroy(ndn_cache_hashtable);
6f51e1
-        ndn_cache_hashtable = NULL;
6f51e1
-    }
6f51e1
-    config_set_ndn_cache_enabled(CONFIG_NDN_CACHE, "off", NULL, 1 );
6f51e1
-    slapi_counter_destroy(&ndn_cache->cache_hits);
6f51e1
-    slapi_counter_destroy(&ndn_cache->cache_tries);
6f51e1
-    slapi_counter_destroy(&ndn_cache->cache_misses);
6f51e1
-    slapi_ch_free((void **)&ndn_cache);
6f51e1
-
6f51e1
-    ndn_started = 0;
6f51e1
+    slapi_counter_destroy(&(t_cache_stats.cache_tries));
6f51e1
+    slapi_counter_destroy(&(t_cache_stats.cache_hits));
6f51e1
+    slapi_counter_destroy(&(t_cache_stats.cache_count));
6f51e1
+    slapi_counter_destroy(&(t_cache_stats.cache_size));
6f51e1
+    slapi_counter_destroy(&(t_cache_stats.cache_evicts));
6f51e1
 }
6f51e1
 
6f51e1
 int
6f51e1
 ndn_cache_started()
6f51e1
 {
6f51e1
-    return ndn_started;
6f51e1
+    return ndn_enabled;
6f51e1
 }
6f51e1
 
6f51e1
 /*
6f51e1
  *  Look up this dn in the ndn cache
6f51e1
  */
6f51e1
 static int
6f51e1
-ndn_cache_lookup(char *dn, size_t dn_len, char **result, char **udn, int *rc)
6f51e1
+ndn_cache_lookup(char *dn, size_t dn_len, char **ndn, char **udn, int *rc)
6f51e1
 {
6f51e1
-    struct ndn_hash_val *ndn_ht_val = NULL;
6f51e1
-    char *ndn, *key;
6f51e1
-    int rv = 0;
6f51e1
-
6f51e1
-    if(NULL == udn){
6f51e1
-        return rv;
6f51e1
+    if (ndn_enabled == 0 || NULL == udn) {
6f51e1
+        return 0;
6f51e1
     }
6f51e1
     *udn = NULL;
6f51e1
-    if(ndn_started == 0){
6f51e1
-        return rv;
6f51e1
-    }
6f51e1
-    if(dn_len == 0){
6f51e1
-        *result = dn;
6f51e1
+
6f51e1
+    if (dn_len == 0) {
6f51e1
+        *ndn = dn;
6f51e1
         *rc = 0;
6f51e1
         return 1;
6f51e1
     }
6f51e1
-    slapi_counter_increment(ndn_cache->cache_tries);
6f51e1
-    slapi_rwlock_rdlock(ndn_cache_lock);
6f51e1
-    ndn_ht_val = (struct ndn_hash_val *)PL_HashTableLookupConst(ndn_cache_hashtable, dn);
6f51e1
-    if(ndn_ht_val){
6f51e1
-        ndn_cache_update_lru(&ndn_ht_val->lru_node);
6f51e1
-        slapi_counter_increment(ndn_cache->cache_hits);
6f51e1
-        if ((ndn_ht_val->len != dn_len) || 
6f51e1
-            /* even if the lengths match, dn may not be normalized yet.
6f51e1
-             * (e.g., 'cn="o=ABC",o=XYZ' vs. 'cn=o\3DABC,o=XYZ') */
6f51e1
-            (memcmp(dn, ndn_ht_val->ndn, dn_len))){
6f51e1
-            *rc = 1; /* free result */
6f51e1
-            ndn = slapi_ch_malloc(ndn_ht_val->len + 1);
6f51e1
-            memcpy(ndn, ndn_ht_val->ndn, ndn_ht_val->len);
6f51e1
-            ndn[ndn_ht_val->len] = '\0';
6f51e1
-            *result = ndn;
6f51e1
-        } else {
6f51e1
-            /* the dn was already normalized, just return the dn as the result */
6f51e1
-            *result = dn;
6f51e1
-            *rc = 0;
6f51e1
-        }
6f51e1
-        rv = 1;
6f51e1
-    } else {
6f51e1
-        /* copy/preserve the udn, so we can use it as the key when we add dn's to the hashtable */
6f51e1
-        key = slapi_ch_malloc(dn_len + 1);
6f51e1
-        memcpy(key, dn, dn_len);
6f51e1
-        key[dn_len] = '\0';
6f51e1
-        *udn = key;
6f51e1
+
6f51e1
+    struct ndn_cache *t_cache = pthread_getspecific(ndn_cache_key);
6f51e1
+    if (t_cache == NULL) {
6f51e1
+        t_cache = ndn_thread_cache_create(t_cache_stats.thread_max_size, t_cache_stats.slots);
6f51e1
+        pthread_setspecific(ndn_cache_key, t_cache);
6f51e1
+        /* If we have no cache, we can't look up ... */
6f51e1
+        return 0;
6f51e1
     }
6f51e1
-    slapi_rwlock_unlock(ndn_cache_lock);
6f51e1
 
6f51e1
-    return rv;
6f51e1
-}
6f51e1
+    t_cache->tries++;
6f51e1
 
6f51e1
-/*
6f51e1
- *  Move this lru node to the top of the list
6f51e1
- */
6f51e1
-static void
6f51e1
-ndn_cache_update_lru(struct ndn_cache_lru **node)
6f51e1
-{
6f51e1
-    struct ndn_cache_lru *prev, *next, *curr_node = *node;
6f51e1
+    /*
6f51e1
+     * Hash our DN ...
6f51e1
+     */
6f51e1
+    uint64_t dn_hash = sds_siphash13(dn, dn_len, t_cache->key);
6f51e1
+    /* Where should it be? */
6f51e1
+    size_t expect_slot = dn_hash % t_cache->slots;
6f51e1
 
6f51e1
-    if(curr_node == NULL){
6f51e1
-        return;
6f51e1
-    }
6f51e1
-    PR_Lock(lru_lock);
6f51e1
-    if(curr_node->prev == NULL){
6f51e1
-        /* already the top node */
6f51e1
-        PR_Unlock(lru_lock);
6f51e1
-        return;
6f51e1
-    }
6f51e1
-    prev = curr_node->prev;
6f51e1
-    next = curr_node->next;
6f51e1
-    if(next){
6f51e1
-        next->prev = prev;
6f51e1
-        prev->next = next;
6f51e1
-    } else {
6f51e1
-        /* this was the tail, so reset the tail */
6f51e1
-        ndn_cache->tail = prev;
6f51e1
-        prev->next = NULL;
6f51e1
+    /*
6f51e1
+     * Is it there?
6f51e1
+     */
6f51e1
+    if (t_cache->table[expect_slot] != NULL) {
6f51e1
+        /*
6f51e1
+         * Check it really matches, could be collision.
6f51e1
+         */
6f51e1
+        struct ndn_cache_value *node = t_cache->table[expect_slot];
6f51e1
+        while (node != NULL) {
6f51e1
+            if (strcmp(dn, node->dn) == 0) {
6f51e1
+                /*
6f51e1
+                 * Update LRU
6f51e1
+                 * Are we already the tail? If so, we can just skip.
6f51e1
+                 * remember, this means in a set of 1, we will always be tail
6f51e1
+                 */
6f51e1
+                if (t_cache->tail != node) {
6f51e1
+                    /*
6f51e1
+                     * Okay, we are *not* the tail. We could be anywhere between
6f51e1
+                     * tail -> ... -> x -> head
6f51e1
+                     * or even, we are the head ourself.
6f51e1
+                     */
6f51e1
+                    if (t_cache->head == node) {
6f51e1
+                        /* We are the head, update head to our predecessor */
6f51e1
+                        t_cache->head = node->prev;
6f51e1
+                        /* Remember, the head has no next. */
6f51e1
+                        t_cache->head->next = NULL;
6f51e1
+                    } else {
6f51e1
+                        /* Right, we aren't the head, so we have a next node. */
6f51e1
+                        node->next->prev = node->prev;
6f51e1
+                    }
6f51e1
+                    /* Because we must be in the middle somewhere, we can assume next and prev exist. */
6f51e1
+                    node->prev->next = node->next;
6f51e1
+                    /*
6f51e1
+                     * Tail can't be NULL if we have a value in the cache, so we can
6f51e1
+                     * just deref this.
6f51e1
+                     */
6f51e1
+                    node->next = t_cache->tail;
6f51e1
+                    t_cache->tail->prev = node;
6f51e1
+                    t_cache->tail = node;
6f51e1
+                    node->prev = NULL;
6f51e1
+                }
6f51e1
+                /* Update that we have a hit.*/
6f51e1
+                t_cache->hits++;
6f51e1
+                /* Cope the NDN to the caller. */
6f51e1
+                *ndn = slapi_ch_strdup(node->ndn);
6f51e1
+                /* Indicate to the caller to free this. */
6f51e1
+                *rc = 1;
6f51e1
+                ndn_thread_cache_commit_status(t_cache);
6f51e1
+                return 1;
6f51e1
+            }
6f51e1
+            node = node->child;
6f51e1
+        }
6f51e1
     }
6f51e1
-    curr_node->prev = NULL;
6f51e1
-    curr_node->next = ndn_cache->head;
6f51e1
-    ndn_cache->head->prev = curr_node;
6f51e1
-    ndn_cache->head = curr_node;
6f51e1
-    PR_Unlock(lru_lock);
6f51e1
+    /* If we miss, we need to duplicate dn to udn here. */
6f51e1
+    *udn = slapi_ch_strdup(dn);
6f51e1
+    *rc = 0;
6f51e1
+    ndn_thread_cache_commit_status(t_cache);
6f51e1
+    return 0;
6f51e1
 }
6f51e1
 
6f51e1
 /*
6f51e1
@@ -2936,176 +3233,102 @@ ndn_cache_update_lru(struct ndn_cache_lru **node)
6f51e1
 static void
6f51e1
 ndn_cache_add(char *dn, size_t dn_len, char *ndn, size_t ndn_len)
6f51e1
 {
6f51e1
-    struct ndn_hash_val *ht_entry;
6f51e1
-    struct ndn_cache_lru *new_node = NULL;
6f51e1
-    PLHashEntry *he;
6f51e1
-    int size;
6f51e1
-
6f51e1
-    if(ndn_started == 0 || dn_len == 0){
6f51e1
+    if (ndn_enabled == 0) {
6f51e1
         return;
6f51e1
     }
6f51e1
-    if(strlen(ndn) > ndn_len){
6f51e1
+    if (dn_len == 0) {
6f51e1
+        return;
6f51e1
+    }
6f51e1
+    if (strlen(ndn) > ndn_len) {
6f51e1
         /* we need to null terminate the ndn */
6f51e1
         *(ndn + ndn_len) = '\0';
6f51e1
     }
6f51e1
     /*
6f51e1
      *  Calculate the approximate memory footprint of the hash entry, key, and lru entry.
6f51e1
      */
6f51e1
-    size = (dn_len * 2) + ndn_len + sizeof(PLHashEntry) + sizeof(struct ndn_hash_val) + sizeof(struct ndn_cache_lru);
6f51e1
+    struct ndn_cache_value *new_value = (struct ndn_cache_value *)slapi_ch_calloc(1, sizeof(struct ndn_cache_value));
6f51e1
+    new_value->size = sizeof(struct ndn_cache_value) + dn_len + ndn_len;
6f51e1
+    /* DN is alloc for us */
6f51e1
+    new_value->dn = dn;
6f51e1
+    /* But we need to copy ndn */
6f51e1
+    new_value->ndn = slapi_ch_strdup(ndn);
6f51e1
+
6f51e1
     /*
6f51e1
-     *  Create our LRU node
6f51e1
+     * Get our local cache out.
6f51e1
      */
6f51e1
-    new_node = (struct ndn_cache_lru *)slapi_ch_malloc(sizeof(struct ndn_cache_lru));
6f51e1
-    if(new_node == NULL){
6f51e1
-        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_add", "Failed to allocate new lru node.\n");
6f51e1
-        return;
6f51e1
+    struct ndn_cache *t_cache = pthread_getspecific(ndn_cache_key);
6f51e1
+    if (t_cache == NULL) {
6f51e1
+        t_cache = ndn_thread_cache_create(t_cache_stats.thread_max_size, t_cache_stats.slots);
6f51e1
+        pthread_setspecific(ndn_cache_key, t_cache);
6f51e1
     }
6f51e1
-    new_node->prev = NULL;
6f51e1
-    new_node->key = dn; /* dn has already been allocated */
6f51e1
     /*
6f51e1
-     *  Its possible this dn was added to the hash by another thread.
6f51e1
+     * Hash the DN
6f51e1
      */
6f51e1
-    slapi_rwlock_wrlock(ndn_cache_lock);
6f51e1
-    ht_entry = (struct ndn_hash_val *)PL_HashTableLookupConst(ndn_cache_hashtable, dn);
6f51e1
-    if(ht_entry){
6f51e1
-        /* already exists, free the node and return */
6f51e1
-        slapi_rwlock_unlock(ndn_cache_lock);
6f51e1
-        slapi_ch_free_string(&new_node->key);
6f51e1
-        slapi_ch_free((void **)&new_node);
6f51e1
-        return;
6f51e1
-    }
6f51e1
+    uint64_t dn_hash = sds_siphash13(new_value->dn, dn_len, t_cache->key);
6f51e1
     /*
6f51e1
-     *  Create the hash entry
6f51e1
+     * Get the insert slot: This works because the number spaces of dn_hash is
6f51e1
+     * a 64bit int, and slots is a power of two. As a result, we end up with
6f51e1
+     * even distribution of the values.
6f51e1
      */
6f51e1
-    ht_entry = (struct ndn_hash_val *)slapi_ch_malloc(sizeof(struct ndn_hash_val));
6f51e1
-    if(ht_entry == NULL){
6f51e1
-        slapi_rwlock_unlock(ndn_cache_lock);
6f51e1
-        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_add", "Failed to allocate new hash entry.\n");
6f51e1
-        slapi_ch_free_string(&new_node->key);
6f51e1
-        slapi_ch_free((void **)&new_node);
6f51e1
-        return;
6f51e1
-    }
6f51e1
-    ht_entry->ndn = slapi_ch_malloc(ndn_len + 1);
6f51e1
-    memcpy(ht_entry->ndn, ndn, ndn_len);
6f51e1
-    ht_entry->ndn[ndn_len] = '\0';
6f51e1
-    ht_entry->len = ndn_len;
6f51e1
-    ht_entry->size = size;
6f51e1
-    ht_entry->lru_node = new_node;
6f51e1
+    size_t insert_slot = dn_hash % t_cache->slots;
6f51e1
+    /* Track this for free */
6f51e1
+    new_value->slot = insert_slot;
6f51e1
+
6f51e1
     /*
6f51e1
-     *  Check if our cache is full
6f51e1
+     * Okay, check if we have space, else we need to trim nodes from
6f51e1
+     * the LRU
6f51e1
      */
6f51e1
-    PR_Lock(lru_lock); /* grab the lru lock now, as ndn_cache_flush needs it */
6f51e1
-    if(ndn_cache->cache_max_size != 0 && ((ndn_cache->cache_size + size) > ndn_cache->cache_max_size)){
6f51e1
-        ndn_cache_flush();
6f51e1
+    while (t_cache->head && (t_cache->size + new_value->size) > t_cache->max_size) {
6f51e1
+        struct ndn_cache_value *trim_node = t_cache->head;
6f51e1
+        ndn_thread_cache_value_destroy(t_cache, trim_node);
6f51e1
     }
6f51e1
+
6f51e1
     /*
6f51e1
-     * Set the ndn cache lru nodes
6f51e1
+     * Add it!
6f51e1
      */
6f51e1
-    if(ndn_cache->head == NULL && ndn_cache->tail == NULL){
6f51e1
-        /* this is the first node */
6f51e1
-        ndn_cache->head = new_node;
6f51e1
-        ndn_cache->tail = new_node;
6f51e1
-        new_node->next = NULL;
6f51e1
+    if (t_cache->table[insert_slot] == NULL) {
6f51e1
+        t_cache->table[insert_slot] = new_value;
6f51e1
     } else {
6f51e1
-        new_node->next = ndn_cache->head;
6f51e1
-        if(ndn_cache->head)
6f51e1
-            ndn_cache->head->prev = new_node;
6f51e1
+        /*
6f51e1
+         * Hash collision! We need to replace the bucket then ....
6f51e1
+         * insert at the head of the slot to make this simpler.
6f51e1
+         */
6f51e1
+        new_value->child = t_cache->table[insert_slot];
6f51e1
+        t_cache->table[insert_slot] = new_value;
6f51e1
     }
6f51e1
-    ndn_cache->head = new_node;
6f51e1
-    PR_Unlock(lru_lock);
6f51e1
+
6f51e1
     /*
6f51e1
-     *  Add the new object to the hashtable, and update our stats
6f51e1
+     * Finally, stick this onto the tail because it's the newest.
6f51e1
      */
6f51e1
-    he = PL_HashTableAdd(ndn_cache_hashtable, new_node->key, (void *)ht_entry);
6f51e1
-    if(he == NULL){
6f51e1
-        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_add", "Failed to add new entry to hash(%s)\n",dn);
6f51e1
-    } else {
6f51e1
-        ndn_cache->cache_count++;
6f51e1
-        ndn_cache->cache_size += size;
6f51e1
+    if (t_cache->head == NULL) {
6f51e1
+        t_cache->head = new_value;
6f51e1
     }
6f51e1
-    slapi_rwlock_unlock(ndn_cache_lock);
6f51e1
-}
6f51e1
-
6f51e1
-/*
6f51e1
- *  cache is full, remove the least used dn's.  lru_lock/ndn_cache write lock are already taken
6f51e1
- */
6f51e1
-static void
6f51e1
-ndn_cache_flush(void)
6f51e1
-{
6f51e1
-    struct ndn_cache_lru *node, *next, *flush_node;
6f51e1
-    int i;
6f51e1
-
6f51e1
-    node = ndn_cache->tail;
6f51e1
-    for(i = 0; node && i < NDN_FLUSH_COUNT && ndn_cache->cache_count > NDN_MIN_COUNT; i++){
6f51e1
-        flush_node = node;
6f51e1
-        /* update the lru */
6f51e1
-        next = node->prev;
6f51e1
-        next->next = NULL;
6f51e1
-        ndn_cache->tail = next;
6f51e1
-        node = next;
6f51e1
-        /* now update the hash */
6f51e1
-        ndn_cache->cache_count--;
6f51e1
-        ndn_cache_delete(flush_node->key);
6f51e1
-        slapi_ch_free_string(&flush_node->key);
6f51e1
-        slapi_ch_free((void **)&flush_node);
6f51e1
+    if (t_cache->tail != NULL) {
6f51e1
+        new_value->next = t_cache->tail;
6f51e1
+        t_cache->tail->prev = new_value;
6f51e1
     }
6f51e1
+    t_cache->tail = new_value;
6f51e1
 
6f51e1
-    slapi_log_err(SLAPI_LOG_CACHE, "ndn_cache_flush","Flushed cache.\n");
6f51e1
-}
6f51e1
-
6f51e1
-static void
6f51e1
-ndn_cache_free(void)
6f51e1
-{
6f51e1
-    struct ndn_cache_lru *node, *next, *flush_node;
6f51e1
-
6f51e1
-    if(!ndn_cache){
6f51e1
-        return;
6f51e1
-    }
6f51e1
-
6f51e1
-    node = ndn_cache->tail;
6f51e1
-    while(node && ndn_cache->cache_count){
6f51e1
-        flush_node = node;
6f51e1
-        /* update the lru */
6f51e1
-        next = node->prev;
6f51e1
-        if(next){
6f51e1
-            next->next = NULL;
6f51e1
-        }
6f51e1
-        ndn_cache->tail = next;
6f51e1
-        node = next;
6f51e1
-        /* now update the hash */
6f51e1
-        ndn_cache->cache_count--;
6f51e1
-        ndn_cache_delete(flush_node->key);
6f51e1
-        slapi_ch_free_string(&flush_node->key);
6f51e1
-        slapi_ch_free((void **)&flush_node);
6f51e1
-    }
6f51e1
-}
6f51e1
-
6f51e1
-/* this is already "write" locked from ndn_cache_add */
6f51e1
-static void
6f51e1
-ndn_cache_delete(char *dn)
6f51e1
-{
6f51e1
-    struct ndn_hash_val *ht_entry;
6f51e1
+    /*
6f51e1
+     * And update the stats.
6f51e1
+     */
6f51e1
+    t_cache->size = t_cache->size + new_value->size;
6f51e1
+    t_cache->count++;
6f51e1
 
6f51e1
-    ht_entry = (struct ndn_hash_val *)PL_HashTableLookupConst(ndn_cache_hashtable, dn);
6f51e1
-    if(ht_entry){
6f51e1
-        ndn_cache->cache_size -= ht_entry->size;
6f51e1
-        slapi_ch_free_string(&ht_entry->ndn);
6f51e1
-        slapi_ch_free((void **)&ht_entry);
6f51e1
-        PL_HashTableRemove(ndn_cache_hashtable, dn);
6f51e1
-    }
6f51e1
 }
6f51e1
 
6f51e1
 /* stats for monitor */
6f51e1
 void
6f51e1
-ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, long *count)
6f51e1
-{
6f51e1
-    slapi_rwlock_rdlock(ndn_cache_lock);
6f51e1
-    *hits = slapi_counter_get_value(ndn_cache->cache_hits);
6f51e1
-    *tries = slapi_counter_get_value(ndn_cache->cache_tries);
6f51e1
-    *size = ndn_cache->cache_size;
6f51e1
-    *max_size = ndn_cache->cache_max_size;
6f51e1
-    *count = ndn_cache->cache_count;
6f51e1
-    slapi_rwlock_unlock(ndn_cache_lock);
6f51e1
+ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, size_t *thread_size, size_t *evicts, size_t *slots, long *count)
6f51e1
+{
6f51e1
+    *max_size = t_cache_stats.max_size;
6f51e1
+    *thread_size = t_cache_stats.thread_max_size;
6f51e1
+    *slots = t_cache_stats.slots;
6f51e1
+    *evicts = slapi_counter_get_value(t_cache_stats.cache_evicts);
6f51e1
+    *hits = slapi_counter_get_value(t_cache_stats.cache_hits);
6f51e1
+    *tries = slapi_counter_get_value(t_cache_stats.cache_tries);
6f51e1
+    *size = slapi_counter_get_value(t_cache_stats.cache_size);
6f51e1
+    *count = slapi_counter_get_value(t_cache_stats.cache_count);
6f51e1
 }
6f51e1
 
6f51e1
 /* Common ancestor sdn is allocated.
6f51e1
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
6f51e1
index 3910dbe..68b59f3 100644
6f51e1
--- a/ldap/servers/slapd/slapi-private.h
6f51e1
+++ b/ldap/servers/slapd/slapi-private.h
6f51e1
@@ -380,7 +380,7 @@ char *slapi_dn_normalize_case_original( char *dn );
6f51e1
 void ndn_cache_init(void);
6f51e1
 void ndn_cache_destroy(void);
6f51e1
 int ndn_cache_started(void);
6f51e1
-void ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, long *count);
6f51e1
+void ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, size_t *thread_size, size_t *evicts, size_t *slots, long *count);
6f51e1
 #define NDN_DEFAULT_SIZE 20971520 /* 20mb - size of normalized dn cache */
6f51e1
 
6f51e1
 /* filter.c */
6f51e1
-- 
6f51e1
2.9.4
6f51e1