From 75c166570a23161d8ba4c11d80afcd2708fd1c76 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Thu, 13 Sep 2012 10:00:56 -0400
Subject: [PATCH] Ticket 408 - create a normalized dn cache

Description:  When we call slapi_dn_normalize_ext, first check if the dn is
              in the cache.  If it is, return the normalized dn(ndn), otherwise
              make a copy of the raw/unnormalized dn so we can use it as the hash
              key.  We need to copy it because the normalize process consumes/modifys
              the string.  Then at the end of the slapi_dn_normalize_ext, we add the
              ndn to the hash using the rawdn as the key.

              This feature is configurable, and the max size can also be set.  The
              default is 20mb for max size.  You can also monitor the cache searching
              on the ldbm monitor(cn=monitor,cn=userRoot,cn=ldbm database,cn=plugins,cn=config).

https://fedorahosted.org/389/ticket/408

Reviewed by: ?
---
 ldap/ldif/template-dse.ldif.in         |    3 +-
 ldap/servers/slapd/attrsyntax.c        |   23 --
 ldap/servers/slapd/back-ldbm/monitor.c |   26 +++-
 ldap/servers/slapd/dn.c                |  347 ++++++++++++++++++++++++++++++++
 ldap/servers/slapd/libglobs.c          |   71 +++++++-
 ldap/servers/slapd/main.c              |    3 +
 ldap/servers/slapd/proto-slap.h        |    7 +
 ldap/servers/slapd/schema.c            |   17 --
 ldap/servers/slapd/slap.h              |    6 +
 ldap/servers/slapd/slapi-private.h     |    6 +-
 10 files changed, 462 insertions(+), 47 deletions(-)

diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
index ddf2b35..c626726 100644
--- a/ldap/ldif/template-dse.ldif.in
+++ b/ldap/ldif/template-dse.ldif.in
@@ -53,9 +53,10 @@ nsslapd-auditlog-maxlogsize: 100
 nsslapd-auditlog-logrotationtime: 1
 nsslapd-auditlog-logrotationtimeunit: day
 nsslapd-rootdn: %rootdn%
+nsslapd-rootpw: %ds_passwd%
 nsslapd-maxdescriptors: 1024
 nsslapd-max-filter-nest-level: 40
-nsslapd-rootpw: %ds_passwd%
+nsslapd-ndn-cache-enabled: off
 
 dn: cn=features,cn=config
 objectclass: top
diff --git a/ldap/servers/slapd/attrsyntax.c b/ldap/servers/slapd/attrsyntax.c
index 62dfea1..36ed768 100644
--- a/ldap/servers/slapd/attrsyntax.c
+++ b/ldap/servers/slapd/attrsyntax.c
@@ -202,29 +202,6 @@ attr_syntax_new()
 }
 
 /*
- * hashNocaseString - used for case insensitive hash lookups
- */
-static PLHashNumber
-hashNocaseString(const void *key)
-{
-    PLHashNumber h = 0;
-    const unsigned char *s;
- 
-    for (s = key; *s; s++)
-        h = (h >> 28) ^ (h << 4) ^ (tolower(*s));
-    return h;
-}
-
-/*
- * hashNocaseCompare - used for case insensitive hash key comparisons
- */
-static PRIntn
-hashNocaseCompare(const void *v1, const void *v2)
-{
-	return (strcasecmp((char *)v1, (char *)v2) == 0);
-}
-
-/*
  * Given an OID, return the syntax info.  If there is more than one
  * attribute syntax with the same OID (i.e. aliases), the first one
  * will be returned.  This is usually the "canonical" one, but it may
diff --git a/ldap/servers/slapd/back-ldbm/monitor.c b/ldap/servers/slapd/back-ldbm/monitor.c
index 075a48f..ee4f01a 100644
--- a/ldap/servers/slapd/back-ldbm/monitor.c
+++ b/ldap/servers/slapd/back-ldbm/monitor.c
@@ -70,8 +70,8 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
     struct berval *vals[2];
     char buf[BUFSIZ];
     PRUint64 hits, tries;
-    long nentries,maxentries;
-    size_t size,maxsize;
+    long nentries, maxentries, count;
+    size_t size, maxsize;
 /* NPCTE fix for bugid 544365, esc 0. <P.R> <04-Jul-2001> */
     struct stat astat;
 /* end of NPCTE fix for bugid 544365 */
@@ -145,6 +145,28 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
         sprintf(buf, "%ld", maxentries);
         MSET("maxDnCacheCount");
     }
+    /* normalized dn cache stats */
+    if(config_get_ndn_cache_enabled()){
+        ndn_cache_get_stats(&hits, &tries, &size, &maxsize, &count);
+        sprintf(buf, "%" NSPRIu64, tries);
+        MSET("normalizedDnCacheTries");
+        sprintf(buf, "%" NSPRIu64, hits);
+        MSET("normalizedDnCacheHits");
+        sprintf(buf, "%" NSPRIu64, tries - hits);
+        MSET("normalizedDnCacheMisses");
+        sprintf(buf, "%lu", (unsigned long)(100.0*(double)hits / (double)(tries > 0 ? tries : 1)));
+        MSET("normalizedDnCacheHitRatio");
+        sprintf(buf, "%lu", size);
+        MSET("currentNormalizedDnCacheSize");
+        if(maxsize == 0){
+        	sprintf(buf, "%d", -1);
+        } else {
+        	sprintf(buf, "%lu", maxsize);
+        }
+        MSET("maxNormalizedDnCacheSize");
+        sprintf(buf, "%ld", count);
+        MSET("currentNormalizedDnCacheCount");
+    }
 
 #ifdef DEBUG
     {
diff --git a/ldap/servers/slapd/dn.c b/ldap/servers/slapd/dn.c
index 568871f..11e56a9 100644
--- a/ldap/servers/slapd/dn.c
+++ b/ldap/servers/slapd/dn.c
@@ -51,6 +51,7 @@
 #include <sys/socket.h>
 #endif
 #include "slap.h"
+#include <plhash.h>
 
 #undef SDN_DEBUG
 
@@ -61,6 +62,52 @@ static void sort_rdn_avs( struct berval *avs, int count, int escape );
 static int rdn_av_cmp( struct berval *av1, struct berval *av2 );
 static void rdn_av_swap( struct berval *av1, struct berval *av2, int escape );
 
+/* normalized dn cache related definitions*/
+struct
+ndn_cache_lru
+{
+    struct ndn_cache_lru *prev;
+    struct ndn_cache_lru *next;
+    char *key;
+};
+
+struct
+ndn_cache_ctx
+{
+    struct ndn_cache_lru *head;
+    struct ndn_cache_lru *tail;
+    Slapi_Counter *cache_hits;
+    Slapi_Counter *cache_tries;
+    Slapi_Counter *cache_misses;
+    size_t cache_size;
+    size_t cache_max_size;
+    long cache_count;
+};
+
+struct
+ndn_hash_val
+{
+    char *ndn;
+    size_t len;
+    int size;
+    struct ndn_cache_lru *lru_node; /* used to speed up lru shuffling */
+};
+
+#define NDN_FLUSH_COUNT 10000 /* number of DN's to remove when cache fills up */
+#define NDN_MIN_COUNT 1000 /* the minimum number of DN's to keep in the cache */
+#define NDN_CACHE_BUCKETS 2053 /* prime number */
+
+static PLHashNumber ndn_hash_string(const void *key);
+static int ndn_cache_lookup(char *dn, size_t dn_len, char **result, char **udn, int *rc);
+static void ndn_cache_update_lru(struct ndn_cache_lru **node);
+static void ndn_cache_add(char *dn, size_t dn_len, char *ndn, size_t ndn_len);
+static void ndn_cache_delete(char *dn);
+static void ndn_cache_flush();
+static int ndn_started = 0;
+static PRLock *lru_lock = NULL;
+static Slapi_RWLock *ndn_cache_lock = NULL;
+static struct ndn_cache_ctx *ndn_cache = NULL;
+static PLHashTable *ndn_cache_hashtable = NULL;
 
 #define ISBLANK(c)	((c) == ' ')
 #define ISBLANKSTR(s)	(((*(s)) == '2') && (*((s)+1) == '0'))
@@ -487,6 +534,7 @@ slapi_dn_normalize_ext(char *src, size_t src_len, char **dest, size_t *dest_len)
     char *ends = NULL;
     char *endd = NULL;
     char *lastesc = NULL;
+    char *udn;
     /* rdn avs for the main DN */
     char *typestart = NULL;
     int rdn_av_count = 0;
@@ -511,6 +559,14 @@ slapi_dn_normalize_ext(char *src, size_t src_len, char **dest, size_t *dest_len)
     if (0 == src_len) {
         src_len = strlen(src);
     }
+    /*
+     *  Check the normalized dn cache
+     */
+    if(ndn_cache_lookup(src, src_len, dest, &udn, &rc)){
+        *dest_len = strlen(*dest);
+        return rc;
+    }
+
     s = PL_strnchr(src, '\\', src_len);
     if (s) {
         *dest_len = src_len * 3;
@@ -1072,6 +1128,10 @@ bail:
         /* We terminate the str with NULL only when we allocate the str */
         *d = '\0';
     }
+    /* add this dn to the normalized dn cache */
+    if(*dest)
+        ndn_cache_add(udn, src_len, *dest, *dest_len);
+
     return rc;
 }
 
@@ -2567,3 +2627,290 @@ slapi_sdn_get_size(const Slapi_DN *sdn)
     sz += strlen(sdn->dn) + 1;
     return sz;
 }
+
+/*
+ *
+ *  Normalized DN Cache
+ *
+ */
+
+/*
+ *  Hashing function using Bernstein's method
+ */
+static PLHashNumber
+ndn_hash_string(const void *key)
+{
+    PLHashNumber hash = 5381;
+    unsigned char *x = (unsigned char *)key;
+    int c;
+
+    while ((c = *x++)){
+        hash = ((hash << 5) + hash) ^ c;
+    }
+    return hash;
+}
+
+void
+ndn_cache_init()
+{
+    if(!config_get_ndn_cache_enabled()){
+        return;
+    }
+    ndn_cache_hashtable = PL_NewHashTable( NDN_CACHE_BUCKETS, ndn_hash_string, PL_CompareStrings, PL_CompareValues, 0, 0);
+    ndn_cache = (struct ndn_cache_ctx *)slapi_ch_malloc(sizeof(struct ndn_cache_ctx));
+    ndn_cache->cache_max_size = config_get_ndn_cache_size();
+    ndn_cache->cache_hits = slapi_counter_new();
+    ndn_cache->cache_tries = slapi_counter_new();
+    ndn_cache->cache_misses = slapi_counter_new();
+    ndn_cache->cache_count = 0;
+    ndn_cache->cache_size = sizeof(struct ndn_cache_ctx) + sizeof(PLHashTable) + sizeof(PLHashTable);
+    ndn_cache->head = NULL;
+    ndn_cache->tail = NULL;
+
+    if ( NULL == ( lru_lock = PR_NewLock()) ||  NULL == ( ndn_cache_lock = slapi_new_rwlock())) {
+        char *errorbuf = NULL;
+        if(ndn_cache_hashtable){
+            PL_HashTableDestroy(ndn_cache_hashtable);
+        }
+        ndn_cache_hashtable = NULL;
+        config_set_ndn_cache_enabled(CONFIG_NDN_CACHE, "off", errorbuf, 1 );
+        slapi_counter_destroy(&ndn_cache->cache_hits);
+        slapi_counter_destroy(&ndn_cache->cache_tries);
+        slapi_counter_destroy(&ndn_cache->cache_misses);
+        slapi_ch_free((void **)&ndn_cache);
+        slapi_log_error( SLAPI_LOG_FATAL, "ndn_cache_init", "Failed to create locks.  Disabling cache.\n" );
+    } else {
+        ndn_started = 1;
+    }
+}
+
+/*
+ *  Look up this dn in the ndn cache
+ */
+static int
+ndn_cache_lookup(char *dn, size_t dn_len, char **result, char **udn, int *rc)
+{
+    struct ndn_hash_val *ndn_ht_val = NULL;
+    char *ndn, *key;
+    int rv = 0;
+
+    if(ndn_started == 0){
+        return rv;
+    }
+    if(dn_len == 0){
+        *result = dn;
+        *rc = 0;
+        return 1;
+    }
+    slapi_counter_increment(ndn_cache->cache_tries);
+    slapi_rwlock_rdlock(ndn_cache_lock);
+    ndn_ht_val = (struct ndn_hash_val *)PL_HashTableLookupConst(ndn_cache_hashtable, dn);
+    if(ndn_ht_val){
+    	ndn_cache_update_lru(&ndn_ht_val->lru_node);
+    	slapi_counter_increment(ndn_cache->cache_hits);
+    	if(ndn_ht_val->len == dn_len ){
+    	    /* the dn was already normalized, just return the dn as the result */
+    	    *result = dn;
+    	    *rc = 0;
+    	} else {
+    	    *rc = 1; /* free result */
+            ndn = slapi_ch_malloc(ndn_ht_val->len + 1);
+            memcpy(ndn, ndn_ht_val->ndn, ndn_ht_val->len);
+            ndn[ndn_ht_val->len] = '\0';
+            *result = ndn;
+        }
+        rv = 1;
+    } else {
+    	/* copy/preserve the udn, so we can use it as the key when we add dn's to the hashtable */
+    	key = slapi_ch_malloc(dn_len + 1);
+    	memcpy(key, dn, dn_len);
+    	key[dn_len] = '\0';
+    	*udn = key;
+    }
+    slapi_rwlock_unlock(ndn_cache_lock);
+
+    return rv;
+}
+
+/*
+ *  Move this lru node to the top of the list
+ */
+static void
+ndn_cache_update_lru(struct ndn_cache_lru **node)
+{
+    struct ndn_cache_lru *prev, *next, *curr_node = *node;
+
+    if(curr_node == NULL){
+        return;
+    }
+    PR_Lock(lru_lock);
+    if(curr_node->prev == NULL){
+        /* already the top node */
+        PR_Unlock(lru_lock);
+        return;
+    }
+    prev = curr_node->prev;
+    next = curr_node->next;
+    if(next){
+        next->prev = prev;
+        prev->next = next;
+    } else {
+        /* this was the tail, so reset the tail */
+        ndn_cache->tail = prev;
+        prev->next = NULL;
+    }
+    curr_node->prev = NULL;
+    curr_node->next = ndn_cache->head;
+    ndn_cache->head->prev = curr_node;
+    ndn_cache->head = curr_node;
+    PR_Unlock(lru_lock);
+}
+
+/*
+ *  Add a ndn to the cache.  Try and do as much as possible before taking the write lock.
+ */
+static void
+ndn_cache_add(char *dn, size_t dn_len, char *ndn, size_t ndn_len)
+{
+    struct ndn_hash_val *ht_entry;
+    struct ndn_cache_lru *new_node = NULL;
+    PLHashEntry *he;
+    int size;
+
+    if(ndn_started == 0 || dn_len == 0){
+        return;
+    }
+    if(strlen(ndn) > ndn_len){
+        /* we need to null terminate the ndn */
+        *(ndn + ndn_len) = '\0';
+    }
+    /*
+     *  Calculate the approximate memory footprint of the hash entry, key, and lru entry.
+     */
+    size = (dn_len * 2) + ndn_len + sizeof(PLHashEntry) + sizeof(struct ndn_hash_val) + sizeof(struct ndn_cache_lru);
+    /*
+     *  Create our LRU node
+     */
+    new_node = (struct ndn_cache_lru *)slapi_ch_malloc(sizeof(struct ndn_cache_lru));
+    if(new_node == NULL){
+        slapi_log_error( SLAPI_LOG_FATAL, "ndn_cache_add", "Failed to allocate new lru node.\n");
+        return;
+    }
+    new_node->prev = NULL;
+    new_node->key = dn; /* dn has already been allocated */
+    /*
+     *  Its possible this dn was added to the hash by another thread.
+     */
+    slapi_rwlock_wrlock(ndn_cache_lock);
+    ht_entry = (struct ndn_hash_val *)PL_HashTableLookupConst(ndn_cache_hashtable, dn);
+    if(ht_entry){
+        /* already exists, free the node and return */
+        slapi_rwlock_unlock(ndn_cache_lock);
+        slapi_ch_free_string(&new_node->key);
+        slapi_ch_free((void **)&new_node);
+        return;
+    }
+    /*
+     *  Create the hash entry
+     */
+    ht_entry = (struct ndn_hash_val *)slapi_ch_malloc(sizeof(struct ndn_hash_val));
+    if(ht_entry == NULL){
+        slapi_rwlock_unlock(ndn_cache_lock);
+        slapi_log_error( SLAPI_LOG_FATAL, "ndn_cache_add", "Failed to allocate new hash entry.\n");
+        slapi_ch_free_string(&new_node->key);
+        slapi_ch_free((void **)&new_node);
+        return;
+    }
+    ht_entry->ndn = slapi_ch_malloc(ndn_len + 1);
+    memcpy(ht_entry->ndn, ndn, ndn_len);
+    ht_entry->ndn[ndn_len] = '\0';
+    ht_entry->len = ndn_len;
+    ht_entry->size = size;
+    ht_entry->lru_node = new_node;
+    /*
+     *  Check if our cache is full
+     */
+    PR_Lock(lru_lock); /* grab the lru lock now, as ndn_cache_flush needs it */
+    if(ndn_cache->cache_max_size != 0 && ((ndn_cache->cache_size + size) > ndn_cache->cache_max_size)){
+        ndn_cache_flush();
+    }
+    /*
+     * Set the ndn cache lru nodes
+     */
+    if(ndn_cache->head == NULL && ndn_cache->tail == NULL){
+        /* this is the first node */
+        ndn_cache->head = new_node;
+        ndn_cache->tail = new_node;
+        new_node->next = NULL;
+    } else {
+        new_node->next = ndn_cache->head;
+        ndn_cache->head->prev = new_node;
+    }
+    ndn_cache->head = new_node;
+    PR_Unlock(lru_lock);
+    /*
+     *  Add the new object to the hashtable, and update our stats
+     */
+    he = PL_HashTableAdd(ndn_cache_hashtable, new_node->key, (void *)ht_entry);
+    if(he == NULL){
+        slapi_log_error( SLAPI_LOG_FATAL, "ndn_cache_add", "Failed to add new entry to hash(%s)\n",dn);
+    } else {
+        ndn_cache->cache_count++;
+        ndn_cache->cache_size += size;
+    }
+    slapi_rwlock_unlock(ndn_cache_lock);
+}
+
+/*
+ *  cache is full, remove the least used dn's.  lru_lock/ndn_cache write lock are already taken
+ */
+static void
+ndn_cache_flush()
+{
+    struct ndn_cache_lru *node, *next, *flush_node;
+    int i;
+
+    node = ndn_cache->tail;
+    for(i = 0; i < NDN_FLUSH_COUNT && ndn_cache->cache_count > NDN_MIN_COUNT; i++){
+        flush_node = node;
+        /* update the lru */
+        next = node->prev;
+        next->next = NULL;
+        ndn_cache->tail = next;
+        node = next;
+        /* now update the hash */
+        ndn_cache->cache_count--;
+        ndn_cache_delete(flush_node->key);
+        slapi_ch_free_string(&flush_node->key);
+        slapi_ch_free((void **)&flush_node);
+    }
+
+    slapi_log_error( SLAPI_LOG_CACHE, "ndn_cache_flush","Flushed cache.\n");
+}
+
+/* this is already "write" locked from ndn_cache_add */
+static void
+ndn_cache_delete(char *dn)
+{
+    struct ndn_hash_val *ht_val;
+
+    ht_val = (struct ndn_hash_val *)PL_HashTableLookupConst(ndn_cache_hashtable, dn);
+    if(ht_val){
+        ndn_cache->cache_size -= ht_val->size;
+        slapi_ch_free_string(&ht_val->ndn);
+        PL_HashTableRemove(ndn_cache_hashtable, dn);
+    }
+}
+/* stats for monitor */
+void
+ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, long *count)
+{
+    slapi_rwlock_rdlock(ndn_cache_lock);
+    *hits = slapi_counter_get_value(ndn_cache->cache_hits);
+    *tries = slapi_counter_get_value(ndn_cache->cache_tries);
+    *size = ndn_cache->cache_size;
+    *max_size = ndn_cache->cache_max_size;
+    *count = ndn_cache->cache_count;
+    slapi_rwlock_unlock(ndn_cache_lock);
+}
+
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index 3226ede..dc8452a 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -691,6 +691,14 @@ static struct config_get_and_set {
 		NULL, 0,
 		(void**)&global_slapdFrontendConfig.disk_preserve_logging,
 		CONFIG_ON_OFF, (ConfigGetFunc)config_get_disk_preserve_logging},
+	{CONFIG_NDN_CACHE, config_set_ndn_cache_enabled,
+		NULL, 0,
+		(void**)&global_slapdFrontendConfig.ndn_cache_enabled, CONFIG_INT,
+		(ConfigGetFunc)config_get_ndn_cache_enabled},
+	{CONFIG_NDN_CACHE_SIZE, config_set_ndn_cache_max_size,
+		NULL, 0,
+		(void**)&global_slapdFrontendConfig.ndn_cache_max_size,
+		CONFIG_INT, (ConfigGetFunc)config_get_ndn_cache_size},
 #ifdef MEMPOOL_EXPERIMENTAL
 	,{CONFIG_MEMPOOL_SWITCH_ATTRIBUTE, config_set_mempool_switch,
 		NULL, 0,
@@ -704,7 +712,7 @@ static struct config_get_and_set {
 /*
  * hashNocaseString - used for case insensitive hash lookups
  */
-static PLHashNumber
+PLHashNumber
 hashNocaseString(const void *key)
 {
     PLHashNumber h = 0;
@@ -718,7 +726,7 @@ hashNocaseString(const void *key)
 /*
  * hashNocaseCompare - used for case insensitive hash key comparisons
  */
-static PRIntn
+PRIntn
 hashNocaseCompare(const void *v1, const void *v2)
 {
 	return (strcasecmp((char *)v1, (char *)v2) == 0);
@@ -1092,6 +1100,8 @@ FrontendConfig_init () {
   cfg->disk_grace_period = 60; /* 1 hour */
   cfg->disk_preserve_logging = LDAP_OFF;
   cfg->disk_logging_critical = LDAP_OFF;
+  cfg->ndn_cache_enabled = LDAP_OFF;
+  cfg->ndn_cache_max_size = NDN_DEFAULT_SIZE;
 
 #ifdef MEMPOOL_EXPERIMENTAL
   cfg->mempool_switch = LDAP_ON;
@@ -1310,6 +1320,42 @@ config_set_disk_grace_period( const char *attrname, char *value, char *errorbuf,
     return retVal;
 }
 
+int
+config_set_ndn_cache_enabled(const char *attrname, char *value, char *errorbuf, int apply )
+{
+    slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+    int retVal;
+
+    retVal = config_set_onoff ( attrname, value, &(slapdFrontendConfig->ndn_cache_enabled), errorbuf, apply);
+
+    return retVal;
+}
+
+int
+config_set_ndn_cache_max_size(const char *attrname, char *value, char *errorbuf, int apply )
+{
+    slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+    long size;
+    int retVal = LDAP_SUCCESS;
+
+    size = atol(value);
+    if(size < 0){
+        size = 0; /* same as -1 */
+    }
+    if(size > 0 && size < 1024000){
+        PR_snprintf ( errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "ndn_cache_max_size too low(%d), changing to "
+            "%d bytes.\n",(int)size, NDN_DEFAULT_SIZE);
+        size = NDN_DEFAULT_SIZE;
+    }
+    if(apply){
+        CFG_LOCK_WRITE(slapdFrontendConfig);
+        slapdFrontendConfig->ndn_cache_max_size = size;
+        CFG_UNLOCK_WRITE(slapdFrontendConfig);
+    }
+
+    return retVal;
+}
+
 int 
 config_set_port( const char *attrname, char *port, char *errorbuf, int apply ) {
   long nPort;
@@ -5213,6 +5259,27 @@ config_get_max_filter_nest_level()
 	return retVal;
 }
 
+size_t
+config_get_ndn_cache_size(){
+    slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+    size_t retVal;
+
+    CFG_LOCK_READ(slapdFrontendConfig);
+    retVal = slapdFrontendConfig->ndn_cache_max_size;
+    CFG_UNLOCK_READ(slapdFrontendConfig);
+    return retVal;
+}
+
+int
+config_get_ndn_cache_enabled(){
+    slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+    int retVal;
+
+    CFG_LOCK_READ(slapdFrontendConfig);
+    retVal = slapdFrontendConfig->ndn_cache_enabled;
+    CFG_UNLOCK_READ(slapdFrontendConfig);
+    return retVal;
+}
 
 char *
 config_get_basedn() {
diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c
index 69314cc..03d7086 100644
--- a/ldap/servers/slapd/main.c
+++ b/ldap/servers/slapd/main.c
@@ -1042,6 +1042,9 @@ main( int argc, char **argv)
 		}
 	}
 
+	/* initialize the normalized DN cache */
+	ndn_cache_init();
+
 	/*
 	 * Detach ourselves from the terminal (unless running in debug mode).
 	 * We must detach before we start any threads since detach forks() on
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index cdd5eec..55c3a78 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -388,6 +388,8 @@ int config_set_disk_grace_period( const char *attrname, char *value, char *error
 int config_set_disk_preserve_logging( const char *attrname, char *value, char *errorbuf, int apply );
 int config_set_disk_logging_critical( const char *attrname, char *value, char *errorbuf, int apply );
 int config_set_auditlog_unhashed_pw(const char *attrname, char *value, char *errorbuf, int apply);
+int config_set_ndn_cache_enabled(const char *attrname, char *value, char *errorbuf, int apply);
+int config_set_ndn_cache_max_size(const char *attrname, char *value, char *errorbuf, int apply);
 
 #if !defined(_WIN32) && !defined(AIX)
 int config_set_maxdescriptors( const char *attrname, char *value, char *errorbuf, int apply );
@@ -543,6 +545,11 @@ long config_get_disk_threshold();
 int config_get_disk_grace_period();
 int config_get_disk_preserve_logging();
 int config_get_disk_logging_critical();
+int config_get_ndn_cache_count();
+size_t config_get_ndn_cache_size();
+int config_get_ndn_cache_enabled();
+PLHashNumber hashNocaseString(const void *key);
+PRIntn hashNocaseCompare(const void *v1, const void *v2);
 
 int is_abspath(const char *);
 char* rel2abspath( char * );
diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c
index ba76b65..c66c320 100644
--- a/ldap/servers/slapd/schema.c
+++ b/ldap/servers/slapd/schema.c
@@ -250,22 +250,6 @@ dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int
     return SLAPI_DSE_CALLBACK_ERROR;
 }
 
-#if 0
-/*
- * hashNocaseString - used for case insensitive hash lookups
- */
-static PLHashNumber
-hashNocaseString(const void *key)
-{
-    PLHashNumber h = 0;
-    const unsigned char *s;
-
-    for (s = key; *s; s++)
-        h = (h >> 28) ^ (h << 4) ^ (tolower(*s));
-    return h;
-}
-#endif
-
 static const char *
 skipWS(const char *s)
 {
@@ -278,7 +262,6 @@ skipWS(const char *s)
 	return s;
 }
 
-
 /*
  * like strchr() but strings within single quotes are skipped.
  */
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index ca1cb87..33607cb 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -2012,6 +2012,8 @@ typedef struct _slapdEntryPoints {
 #define CONFIG_DISK_GRACE_PERIOD "nsslapd-disk-monitoring-grace-period"
 #define CONFIG_DISK_PRESERVE_LOGGING "nsslapd-disk-monitoring-preserve-logging"
 #define CONFIG_DISK_LOGGING_CRITICAL "nsslapd-disk-monitoring-logging-critical"
+#define CONFIG_NDN_CACHE "nsslapd-ndn-cache-enabled"
+#define CONFIG_NDN_CACHE_SIZE "nsslapd-ndn-cache-max-size"
 
 #ifdef MEMPOOL_EXPERIMENTAL
 #define CONFIG_MEMPOOL_SWITCH_ATTRIBUTE "nsslapd-mempool"
@@ -2247,6 +2249,10 @@ typedef struct _slapdFrontendConfig {
   int disk_grace_period;
   int disk_preserve_logging;
   int disk_logging_critical;
+
+  /* normalized dn cache */
+  int ndn_cache_enabled;
+  size_t ndn_cache_max_size;
 } slapdFrontendConfig_t;
 
 /* possible values for slapdFrontendConfig_t.schemareplace */
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
index a57b453..6c2781c 100644
--- a/ldap/servers/slapd/slapi-private.h
+++ b/ldap/servers/slapd/slapi-private.h
@@ -384,12 +384,14 @@ Slapi_DN *slapi_sdn_init_normdn_ndn_passin(Slapi_DN *sdn, const char *dn);
 Slapi_DN *slapi_sdn_init_normdn_passin(Slapi_DN *sdn, const char *dn);
 char *slapi_dn_normalize_original( char *dn );
 char *slapi_dn_normalize_case_original( char *dn );
+void ndn_cache_init();
+void ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, long *count);
+#define NDN_DEFAULT_SIZE 20971520 /* 20mb - size of normalized dn cache */
 
 /* filter.c */
 int filter_flag_is_set(const Slapi_Filter *f,unsigned char flag);
 char *slapi_filter_to_string(const Slapi_Filter *f, char *buffer, size_t bufsize);
-char *
-slapi_filter_to_string_internal( const struct slapi_filter *f, char *buf, size_t *bufsize );
+char *slapi_filter_to_string_internal( const struct slapi_filter *f, char *buf, size_t *bufsize );
 
 /* operation.c */
 
-- 
1.7.1

