]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#9186 Implement result code counters
authorNadezhda Ivanova <nivanova@symas.com>
Wed, 29 Jan 2025 14:26:10 +0000 (16:26 +0200)
committerNadezhda Ivanova <nivanova@symas.com>
Wed, 11 Jun 2025 11:29:37 +0000 (14:29 +0300)
15 files changed:
contrib/ConfigOIDs
contrib/slapd-modules/resultstats/.gitignore [new file with mode: 0644]
contrib/slapd-modules/resultstats/Makefile [new file with mode: 0644]
contrib/slapd-modules/resultstats/resultstats.c [new file with mode: 0644]
contrib/slapd-modules/resultstats/slapo-resultstats.5 [new file with mode: 0644]
contrib/slapd-modules/resultstats/tests/.gitignore [new file with mode: 0644]
contrib/slapd-modules/resultstats/tests/Rules.mk [new file with mode: 0644]
contrib/slapd-modules/resultstats/tests/data/slapd.conf [new file with mode: 0644]
contrib/slapd-modules/resultstats/tests/data/test001-add.ldif [new file with mode: 0644]
contrib/slapd-modules/resultstats/tests/data/test001-bind.ldif [new file with mode: 0644]
contrib/slapd-modules/resultstats/tests/data/test001-modify.ldif [new file with mode: 0644]
contrib/slapd-modules/resultstats/tests/data/test001-reset.ldif [new file with mode: 0644]
contrib/slapd-modules/resultstats/tests/data/test001-search.ldif [new file with mode: 0644]
contrib/slapd-modules/resultstats/tests/run [new file with mode: 0755]
contrib/slapd-modules/resultstats/tests/scripts/test001-resultstats [new file with mode: 0755]

index ce90eff750e247f4b610d1cf8ec452757abc813a..e6ee90a90e33d516b6e2b3bad24eec02f7d319c8 100644 (file)
@@ -11,3 +11,4 @@ OLcfgCt{Oc|At}:8      datamorph
 OLcfgCt{Oc|At}:9       variant
 OLcfgCt{Oc|At}:10      alias
 OLcfgCt{Oc|At}:11      dsaschema
+OLcfgCt{Oc|At}:12      resultstats
diff --git a/contrib/slapd-modules/resultstats/.gitignore b/contrib/slapd-modules/resultstats/.gitignore
new file mode 100644 (file)
index 0000000..8ba2139
--- /dev/null
@@ -0,0 +1,2 @@
+servers
+clients
diff --git a/contrib/slapd-modules/resultstats/Makefile b/contrib/slapd-modules/resultstats/Makefile
new file mode 100644 (file)
index 0000000..e6e6506
--- /dev/null
@@ -0,0 +1,80 @@
+# $OpenLDAP$
+# This work is part of OpenLDAP Software <http://www.openldap.org/>.
+#
+# Copyright 1998-2024 The OpenLDAP Foundation.
+# Copyright 2024 Symas Corp. All Rights Reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted only as authorized by the OpenLDAP
+# Public License.
+#
+# A copy of this license is available in the file LICENSE in the
+# top-level directory of the distribution or, alternatively, at
+# <http://www.OpenLDAP.org/license.html>.
+
+LDAP_SRC = ../../..
+LDAP_BUILD = $(LDAP_SRC)
+LDAP_INC = -I$(LDAP_BUILD)/include -I$(LDAP_SRC)/include -I$(LDAP_SRC)/servers/slapd
+LDAP_LIB = $(LDAP_BUILD)/libraries/libldap/libldap.la \
+       $(LDAP_BUILD)/libraries/liblber/liblber.la
+
+SRCDIR = ./
+
+PLAT = UNIX
+NT_LIB = -L$(LDAP_BUILD)/servers/slapd -lslapd
+NT_LDFLAGS = -no-undefined -avoid-version
+UNIX_LDFLAGS = -version-info $(LTVER)
+
+LIBTOOL = $(LDAP_BUILD)/libtool
+INSTALL = /usr/bin/install
+CC = gcc
+OPT = -g -O0
+DEFS = -DSLAPD_OVER_RESULTSTATS=SLAPD_MOD_DYNAMIC
+INCS = $(LDAP_INC)
+LIBS = $($(PLAT)_LIB) $(LDAP_LIB)
+LD_FLAGS = $(LDFLAGS) $($(PLAT)_LDFLAGS) -rpath $(moduledir) -module
+
+PROGRAMS = resultstats.la
+MANPAGES = slapo_resultstats.5
+LTVER = 0:0:0
+CLEAN = *.o *.lo *.la .libs
+prefix=/usr/local
+exec_prefix=$(prefix)
+ldap_subdir=/openldap
+
+libdir=$(exec_prefix)/lib
+libexecdir=$(exec_prefix)/libexec
+moduledir = $(libexecdir)$(ldap_subdir)
+mandir = $(exec_prefix)/share/man
+man5dir = $(mandir)/man5
+
+.SUFFIXES: .c .o .lo
+
+.c.lo:
+       $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(OPT) $(CPPFLAGS) $(DEFS) $(INCS) -c $<
+
+all: $(PROGRAMS)
+
+d :=
+sp :=
+dir := tests
+include $(dir)/Rules.mk
+
+
+resultstats.la: resultstats.lo
+       $(LIBTOOL) --mode=link $(CC) $(LD_FLAGS) -o $@ $? $(LIBS)
+
+clean:
+       rm -rf $(CLEAN)
+
+install: install-lib install-man
+
+install-lib: $(PROGRAMS)
+       mkdir -p $(DESTDIR)$(moduledir)
+       for p in $(PROGRAMS) ; do \
+               $(LIBTOOL) --mode=install cp $$p $(DESTDIR)$(moduledir) ; \
+       done
+
+install-man: $(MANPAGES)
+       mkdir -p  $(DESTDIR)$(man5dir)
+       $(INSTALL) -m 644 $(MANPAGES) $(DESTDIR)$(man5dir)
diff --git a/contrib/slapd-modules/resultstats/resultstats.c b/contrib/slapd-modules/resultstats/resultstats.c
new file mode 100644 (file)
index 0000000..d24102f
--- /dev/null
@@ -0,0 +1,515 @@
+/* resultstats.c - gather result code statistics per operation */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2025 The OpenLDAP Foundation.
+ * Copyright 2025 Symas Corp. All Rights Reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Nadezhda Ivanova for Symas Corp. for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RESULTSTATS
+
+#include <stdio.h>
+
+#include <ac/unistd.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "back-monitor/back-monitor.h"
+
+static slap_overinst           resultstats;
+
+#define STATS_SIZE LDAP_OTHER+2
+
+static struct resultstats_ops_t {
+       struct berval   rdn;
+       struct berval   nrdn;
+} resultstats_op[] = {
+       { BER_BVC( "cn=Bind" ),         BER_BVC( "cn=bind" ), },
+       { BER_BVC( "cn=Unbind" ),       BER_BVC( "cn=unbind" ), },
+       { BER_BVC( "cn=Search" ),       BER_BVC( "cn=search" ), },
+       { BER_BVC( "cn=Compare" ),      BER_BVC( "cn=compare" ), },
+       { BER_BVC( "cn=Modify" ),       BER_BVC( "cn=modify" ), },
+       { BER_BVC( "cn=Modrdn" ),       BER_BVC( "cn=modrdn" ), },
+       { BER_BVC( "cn=Add" ),          BER_BVC( "cn=add" ), },
+       { BER_BVC( "cn=Delete" ),       BER_BVC( "cn=delete" ), },
+       { BER_BVC( "cn=Abandon" ),      BER_BVC( "cn=abandon" ), },
+       { BER_BVC( "cn=Extended" ),     BER_BVC( "cn=extended" ), },
+       { BER_BVNULL,                   BER_BVNULL }
+};
+
+typedef struct resultstats_t {
+       uintptr_t stats[ SLAP_OP_LAST ][ STATS_SIZE ];
+       struct berval           monitor_ndn;
+       struct berval       rslt_rdn;
+       struct berval       mss_ndn;
+       monitor_subsys_t    *mss;
+} resultstats_t;
+
+
+static int resultstats_monitor_db_init( void );
+static int resultstats_monitor_db_open( BackendDB *be );
+static int resultstats_monitor_db_close( BackendDB *be );
+//static int resultstats_monitor_db_destroy( BackendDB *be );
+
+static AttributeDescription    *ad_olmResultCodeStat;
+static ObjectClass             *oc_olmResultStatOperation;
+static ObjectClass             *oc_monitorContainer;
+
+
+static struct {
+       char            *desc;
+       ObjectClass     **ocp;
+}              s_oc[] = {
+       { "( OLcfgCtOc:12.1 "
+         "NAME ( 'olmResultStatOperation' ) "
+         "SUP monitoredObject "
+         "MAY ( "
+         "olmResultCodeStat"
+         " ) )",
+         &oc_olmResultStatOperation },
+
+       { NULL }
+};
+
+static struct {
+       char    *desc;
+       AttributeDescription **adp;
+} s_ad[] = {
+       { "( OLcfgCtAt:12.1 "
+         "NAME 'olmResultCodeStat' "
+         "DESC 'Number of times an LDAP code result has been sent for this operation type' "
+         "EQUALITY integerMatch "
+         "ORDERING integerOrderingMatch "
+         "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
+         &ad_olmResultCodeStat },
+
+       { NULL }
+};
+
+static AttributeDescription *ads[ STATS_SIZE ];
+
+static int
+resultstats_monitor_ops_update(
+       Operation       *op,
+       SlapReply       *rs,
+       Entry           *e,
+       void            *priv )
+{
+       uintptr_t *stats = (uintptr_t *)priv;
+       int i, rc;
+       for ( i = 0; i < STATS_SIZE; i++ ) {
+               Attribute *a;
+               AttributeDescription *ad = NULL;
+               char name_buf[ BUFSIZ ];
+               char val_buf[ BUFSIZ ];
+               const char *text;
+               struct berval bv;
+               unsigned long int value = __atomic_load_n( &stats[i], __ATOMIC_RELAXED );
+               if ( value == 0 ) {
+                       continue;
+               }
+
+               /* TODO This should be optimised by maintaining the attributes in the entry sorted by code,
+                  and avoid searching through the full list every time */
+               ad = ads[i];
+               if ( ad == NULL ) {
+                       if ( i <= LDAP_OTHER )
+                               snprintf( name_buf, sizeof( name_buf ), "olmResultCodeStat;x-resultcode-%d", i );
+                       else
+                               snprintf( name_buf, sizeof( name_buf ), "olmResultCodeStat;x-resultcode-more");
+
+                       rc = slap_str2ad( name_buf, &ad, &text );
+                       if ( rc != 0 ) {
+                               Debug( LDAP_DEBUG_ANY, "resultstats_monitor_ops_update: "
+                                          "unable to find attribute description %s \n ", name_buf );
+                               return 0;
+                       }
+               }
+               ads[i] = ad;
+               bv.bv_val = val_buf;
+               bv.bv_len = snprintf( val_buf, sizeof( val_buf ), "%lu", value );
+               a = attr_find( e->e_attrs, ad );
+               if ( a != NULL ) {
+                       ber_bvreplace( &a->a_vals[ 0 ], &bv );
+               } else {
+                       attr_merge_one( e, ad, &bv, NULL );
+               }
+       }
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+resultstats_monitor_initialize( void )
+{
+       static int      resultstats_monitor_initialized = 0;
+       int code, i;
+
+       if ( backend_info( "monitor" ) == NULL ) {
+               Debug( LDAP_DEBUG_ANY,
+                          "resultstats_monitor_initialize: resultstats overlay requires cn=monitor");
+               return -1;
+       }
+
+       if ( resultstats_monitor_initialized++ ) {
+               return 0;
+       }
+
+       ad_define_option( "x-resultcode-", __FILE__, __LINE__ );
+       for ( i = 0; s_ad[i].desc != NULL; i++ ) {
+               code = register_at( s_ad[i].desc, s_ad[i].adp, 0 );
+               if ( code ) {
+                       Debug( LDAP_DEBUG_ANY,
+                                  "resultstats_monitor_initialize: register_at #%d failed\n", i );
+                       return code;
+               }
+       }
+
+       for ( i = 0; s_oc[i].desc != NULL; i++ ) {
+               code = register_oc( s_oc[i].desc, s_oc[i].ocp, 0 );
+               if ( code ) {
+                       Debug( LDAP_DEBUG_ANY,
+                                  "resultstat_monitor_initialize: register_oc #%d failed\n", i );
+                       return code;
+               }
+       }
+
+       oc_monitorContainer = oc_find( "monitorContainer" );
+       if ( !oc_monitorContainer ) {
+               Debug( LDAP_DEBUG_ANY,
+                          "resultstats_monitor_initialize: failed to find objectClass (monitorContainer)\n" );
+               return 5;
+       }
+       return 0;
+}
+
+static int
+resultstats_monitor_register_entries( monitor_extra_t  *mbe,
+                                                                         resultstats_t *rslt,
+                                                                         monitor_subsys_t      *ms,
+                                                                         Entry *parent )
+{
+       int i, rc;
+       Entry *e;
+       for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+               monitor_callback_t *cb;
+
+               e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn,
+                                                        &resultstats_op[i].rdn,
+                                                        oc_olmResultStatOperation, NULL, NULL );
+               if ( e == NULL ) {
+                       Debug( LDAP_DEBUG_ANY,
+                                  "resultstats_monitor_register_entries: "
+                                  "unable to create entry \"%s,%s\"\n",
+                                  resultstats_op[i].rdn.bv_val,
+                                  rslt->monitor_ndn.bv_val );
+                       return( -1 );
+               }
+               cb = ch_calloc( sizeof( monitor_callback_t ), 1 );
+               cb->mc_update = resultstats_monitor_ops_update;
+               cb->mc_private = (void *)rslt->stats[i];
+
+               rc = mbe->register_entry( e, cb, ms, 0 );
+               if ( rc != LDAP_SUCCESS ) {
+                       Debug( LDAP_DEBUG_ANY,
+                                  "resultstats_monitor_register_entries: "
+                                  "unable to register entry \"%s\" for monitoring\n",
+                                  e->e_name.bv_val );
+                       ch_free( cb );
+                       entry_free( e );
+                       return rc;
+               }
+               entry_free( e );
+       }
+       return 0;
+}
+
+static int
+resultstats_monitor_db_init( void )
+{
+       return resultstats_monitor_initialize();
+}
+
+static int
+resultstats_monitor_mss_init(
+       BackendDB               *be,
+       monitor_subsys_t        *ms )
+{
+       slap_overinst   *on = (slap_overinst *)ms->mss_private;
+       resultstats_t   *rslt = (resultstats_t *)on->on_bi.bi_private;
+       monitor_extra_t *mbe;
+
+       Entry   *parent;
+       int             rc;
+
+       assert( be != NULL );
+       mbe = (monitor_extra_t *) be->bd_info->bi_extra;
+
+       parent = mbe->entry_stub( &rslt->monitor_ndn, &rslt->monitor_ndn,
+                                                         &rslt->rslt_rdn, oc_monitorContainer, NULL, NULL );
+       if ( parent == NULL ) {
+               Debug( LDAP_DEBUG_ANY,
+                          "resultstats_monitor_mss_init: "
+                          "unable to create entry \"%s,%s\"\n",
+                          rslt->rslt_rdn.bv_val,
+                          rslt->monitor_ndn.bv_val );
+               return( -1 );
+       }
+
+       ber_dupbv( &ms->mss_dn, &parent->e_name );
+       ber_dupbv( &ms->mss_ndn, &parent->e_nname );
+       ber_dupbv( &ms->mss_rdn, &rslt->rslt_rdn );
+
+       rc = mbe->register_entry( parent, NULL, ms, MONITOR_F_PERSISTENT_CH );
+       if ( rc != LDAP_SUCCESS ) {
+               Debug( LDAP_DEBUG_ANY,
+                          "resultstats_monitor_mss_init: "
+                          "unable to register entry \"%s,%s\"\n",
+                          ms->mss_rdn.bv_val,
+                          ms->mss_ndn.bv_val );
+               entry_free( parent );
+               return ( -1 );
+       }
+
+       rc = resultstats_monitor_register_entries( mbe, rslt, ms, parent );
+
+       entry_free( parent );
+
+       return rc;
+}
+
+static int
+resultstats_monitor_mss_destroy (
+       BackendDB               *be,
+       monitor_subsys_t        *ms )
+{
+       if ( ms->mss_ndn.bv_len > 0 ) {
+               ch_free( ms->mss_ndn.bv_val );
+       }
+       if ( ms->mss_dn.bv_len > 0 ) {
+               ch_free( ms->mss_dn.bv_val );
+       }
+       return 0;
+}
+
+static int
+resultstats_monitor_db_open( BackendDB *be )
+{
+       slap_overinst   *on = (slap_overinst *)be->bd_info;
+       resultstats_t   *rslt = (resultstats_t *)on->on_bi.bi_private;
+       BackendInfo             *bi;
+       int rc = 0;
+       monitor_extra_t         *mbe;
+       Entry * parent;
+
+       /* check if monitor is configured and usable */
+       bi = backend_info( "monitor" );
+       if ( !bi || !bi->bi_extra ) {
+               return -1;
+       }
+       mbe = bi->bi_extra;
+
+       /* don't bother if monitor is not configured */
+       if ( !mbe->is_configured() ) {
+               Debug( LDAP_DEBUG_CONFIG, "resultstats_monitor_db_open: "
+                          "monitoring disabled; "
+                          "configure monitor database to enable\n" );
+               return -1;
+       }
+
+       BER_BVZERO( &rslt->monitor_ndn );
+       if ( ( rc = mbe->register_overlay( be, on, &rslt->monitor_ndn ) ) != 0 ) {
+               return rc;
+       }
+       parent = mbe->entry_stub( &rslt->monitor_ndn, &rslt->monitor_ndn,
+                                                         &rslt->rslt_rdn, oc_monitorContainer, NULL, NULL );
+       if ( parent == NULL ) {
+               Debug( LDAP_DEBUG_ANY,
+                          "resultstats_monitor_mss_init: "
+                          "unable to create entry \"%s,%s\"\n",
+                          rslt->rslt_rdn.bv_val,
+                          rslt->monitor_ndn.bv_val );
+               return( -1 );
+       }
+
+       ber_dupbv( &rslt->mss_ndn, &parent->e_nname );
+       /* Check if the subsystem already exsists. This can happen if the overlay
+          has previously added and removed. For now it is safe to assume
+          that the dn will be unique, as databases cannot be removed.
+          This should be re-done when we enable database removal and fix monitor
+          so that subsystems can be unregistered */
+       rslt->mss = monitor_back_get_subsys_by_dn( &rslt->mss_ndn, 0 );
+       if ( rslt->mss == NULL ) {
+               /* this will leak at monitor_db_destroy, but it can't be helped */
+               rslt->mss = (monitor_subsys_t *)ch_calloc( 1, sizeof( monitor_subsys_t ) );
+               rslt->mss->mss_name = "Result code statistics";
+               rslt->mss->mss_flags = MONITOR_F_PERSISTENT_CH;
+               rslt->mss->mss_open = resultstats_monitor_mss_init;
+               rslt->mss->mss_destroy = resultstats_monitor_mss_destroy;
+               rslt->mss->mss_private = on;
+
+               if ( mbe->register_subsys_late( rslt->mss ) ) {
+                       Debug( LDAP_DEBUG_ANY,
+                                  "resultsats_monitor_db_open: "
+                                  "failed to register result statistics subsystem" );
+                       return -1;
+               }
+       } else {
+               rslt->mss->mss_private = on;
+               rc = mbe->register_entry( parent, NULL, rslt->mss, MONITOR_F_PERSISTENT_CH );
+               if ( rc != LDAP_SUCCESS ) {
+                       Debug( LDAP_DEBUG_ANY,
+                                  "resultstats_monitor_db_open: "
+                                  "unable to register entry \"%s,%s\"\n",
+                                  rslt->mss->mss_rdn.bv_val,
+                                  rslt->mss->mss_ndn.bv_val );
+                       entry_free( parent );
+                       return ( -1 );
+               }
+               rc = resultstats_monitor_register_entries( mbe, rslt, rslt->mss, parent );
+       }
+       entry_free( parent );
+
+       return rc;
+}
+
+static int
+resultstats_monitor_db_close( BackendDB *be )
+{
+
+       slap_overinst *on = (slap_overinst *)be->bd_info;
+       resultstats_t   *rslt = (resultstats_t *)on->on_bi.bi_private;
+       monitor_extra_t *mbe;
+       BackendInfo             *mi = backend_info( "monitor" );
+       
+       if ( mi && mi->bi_extra ) {
+               int i;
+               mbe = mi->bi_extra;
+               for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+                       struct berval  bv;
+                       char        buf[ BACKMONITOR_BUFSIZE ];
+                       bv.bv_len = snprintf( buf, sizeof( buf ), "%s,%s",
+                                                                 resultstats_op[i].nrdn.bv_val,
+                                                                 rslt->mss_ndn.bv_val );
+                       bv.bv_val = buf;
+                       mbe->unregister_entry( &bv );
+
+               }
+               mbe->unregister_entry( &rslt->mss_ndn );
+       }
+       if ( !BER_BVISNULL(&rslt->mss_ndn) ) {
+               ch_free( rslt->mss_ndn.bv_val );
+               BER_BVZERO( &rslt->mss_ndn );
+       }
+       /* Make sure this does not point to a non-existent overlay instance */
+       rslt->mss->mss_private = NULL;
+       return 0;
+}
+
+static int
+resultstats_response( Operation *op, SlapReply *rs )
+{
+       slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
+       resultstats_t   *rslt = (resultstats_t *)on->on_bi.bi_private;
+       int code, opidx;
+
+       /* skip internal ops */
+       if ( rs->sr_type != REP_RESULT || op->o_do_not_cache ) {
+               return SLAP_CB_CONTINUE;
+       }
+
+       code = slap_map_api2result( rs );
+       if ( code > LDAP_OTHER )
+               code = LDAP_OTHER+1;
+
+       opidx = slap_req2op( op->o_tag );
+       __atomic_fetch_add( &(rslt->stats[opidx][code]), 1, __ATOMIC_RELAXED );
+
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+resultstats_db_init( BackendDB *be, ConfigReply *cr )
+{
+       slap_overinst   *on = (slap_overinst *)be->bd_info;
+       resultstats_t   *rslt;
+
+       rslt = (resultstats_t *)ch_calloc( 1, sizeof( resultstats_t ) );
+
+       on->on_bi.bi_private = (void *)rslt;
+       ber_str2bv( "cn=Result Stats", 0, 1,
+                               &rslt->rslt_rdn );
+       return resultstats_monitor_db_init();
+}
+
+static int
+resultstats_db_open( BackendDB *be, ConfigReply *cr )
+{
+       return resultstats_monitor_db_open( be );
+}
+
+static int
+resultstats_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+       slap_overinst   *on = (slap_overinst *)be->bd_info;
+       resultstats_t   *rslt = (resultstats_t *)on->on_bi.bi_private;
+       if ( rslt->rslt_rdn.bv_len > 0 ) {
+               ch_free( rslt->rslt_rdn.bv_val );
+       }
+       ch_free( rslt );
+       return 0;
+}
+
+static int
+resultstats_db_close( BackendDB *be, ConfigReply *cr )
+{
+       return resultstats_monitor_db_close( be );
+}
+
+int
+resultstats_initialize( void )
+{
+       int             code;
+
+       resultstats.on_bi.bi_type = "resultstats";
+       resultstats.on_bi.bi_db_init = resultstats_db_init;
+       resultstats.on_bi.bi_db_open = resultstats_db_open;
+       resultstats.on_bi.bi_db_destroy = resultstats_db_destroy;
+       resultstats.on_bi.bi_db_close = resultstats_db_close;
+       resultstats.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+       resultstats.on_response = resultstats_response;
+
+       code = resultstats_monitor_initialize();
+       if ( code != 0)
+               return code;
+
+       return overlay_register( &resultstats );
+}
+
+#if SLAPD_OVER_RESULTSTATS == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+       return resultstats_initialize();
+}
+#endif /* SLAPD_OVER_RESULTSTATS == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_RESULTSTATS */
diff --git a/contrib/slapd-modules/resultstats/slapo-resultstats.5 b/contrib/slapd-modules/resultstats/slapo-resultstats.5
new file mode 100644 (file)
index 0000000..fabb917
--- /dev/null
@@ -0,0 +1,96 @@
+.TH SLAPO-RESULTSTATS 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2005-2025 The OpenLDAP Foundation All Rights Reserved.
+.\" Copying restrictions apply.  See COPYRIGHT/LICENSE.
+.\" $OpenLDAP$
+.SH NAME
+slapo\-resultstats \- Operation result statistics for slapd
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The
+.B resultstats
+overlay adds per-operation counters for LDAP operation results and
+exposes them via
+.B cn=monitor.
+Result codes from
+.B LdapSuccess(0)
+to
+.B LdapOther(80)
+are counted separately as individual values. Codes higher than that are counted
+together.
+.P
+The overlay can be configured on a particular database, or on the front-end,
+or both. When configured on the front-end, it will count operations
+processed by all databases. When configured on a particular databases,
+only operations processed by that database will be counted.  Monitoring
+must be enabled for the database (mdb databases have monitoring on by default),
+and
+.B cn=monitor
+bust be configured.
+.P
+.B Note:
+Bind request responses will be counted for the database where
+the identity used is located. For example, binding with the rootdn of
+the database will be counted for that database, but anonymous binds
+will be counted only at the front-end level. Failed schema checks
+(such as undefinedAttributeType) cannot be counted at all, since they
+happen before a database is chosen.
+.P
+When enabled, the resultstats overlay will add a
+.B cn=Result Stats
+container under the database entry in cn=monitor, and under it entries
+for all types of LDAP operations. The statistic is exposed via the
+.B olmResultCodeStat
+attribute with the
+.B x-resultcode-<number>
+tag. Errors higher than 80 will be exposed under the
+.B x-resultcode-more
+tag. Only result codes with counters higher than 0
+will be displayed.
+
+.SH EXAPLES
+This is an example entry for search requests, where we have 1 successful operation and one that returned
+.B NoSuchObject(32)
+.P
+.nf
+dn: cn=Search,cn=Result Stats,cn=database 2,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+olmResultCodeStat;x-resultcode-0: 1
+olmResultCodeStat;x-resultcode-32: 1
+entryDN: cn=Search,cn=Result Stats,cn=database 2,cn=databases,cn=monitor
+
+.SH CONFIGURATION
+This overlay does not have any configuration directives.
+.SH Configuration Example
+.nf
+moduleload resultstats.la
+
+database config
+rootdn cn=config
+rootpw password
+
+#resultstats configured on the front-end
+#entries will appear under cn=Result Stats,cn=database 0,cn=databases,cn=monitor
+overlay resultstats
+
+database monitor
+
+database mdb
+
+suffix dc=example,dc=com
+rootdn cn=admin,dc=example,dc=com
+rootpw password
+
+#resultstats configured on this database
+#entries will appear under cn=Result Stats,cn=database 2,cn=databases,cn=monitor
+overlay resultstats
+
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5),
+.BR slapd\-monitor (5),
+.SH AUTHOR
+Nadezhda Ivanova, on behalf of Symas Corp.
diff --git a/contrib/slapd-modules/resultstats/tests/.gitignore b/contrib/slapd-modules/resultstats/tests/.gitignore
new file mode 100644 (file)
index 0000000..9253831
--- /dev/null
@@ -0,0 +1,4 @@
+progs
+schema
+testdata
+testrun
diff --git a/contrib/slapd-modules/resultstats/tests/Rules.mk b/contrib/slapd-modules/resultstats/tests/Rules.mk
new file mode 100644 (file)
index 0000000..9884b7d
--- /dev/null
@@ -0,0 +1,23 @@
+sp := $(sp).x
+dirstack_$(sp) := $(d)
+d := $(dir)
+
+.PHONY: test
+
+CLEAN += clients servers tests/progs tests/schema tests/testdata tests/testrun
+
+test: all clients servers tests/progs
+
+test:
+       cd tests; \
+               SRCDIR=$(abspath $(LDAP_SRC)) \
+               LDAP_BUILD=$(abspath $(LDAP_BUILD)) \
+               TOPDIR=$(abspath $(SRCDIR)) \
+               LIBTOOL=$(abspath $(LIBTOOL)) \
+                       $(abspath $(SRCDIR))/tests/run test001-resultstats
+
+servers clients tests/progs:
+       ln -s $(abspath $(LDAP_BUILD))/$@ $@
+
+d := $(dirstack_$(sp))
+sp := $(basename $(sp))
diff --git a/contrib/slapd-modules/resultstats/tests/data/slapd.conf b/contrib/slapd-modules/resultstats/tests/data/slapd.conf
new file mode 100644 (file)
index 0000000..0df804c
--- /dev/null
@@ -0,0 +1,33 @@
+# provider slapd config -- for testing
+# $OpenLDAP$
+
+include                @SCHEMADIR@/core.schema
+include                @SCHEMADIR@/cosine.schema
+include                @SCHEMADIR@/inetorgperson.schema
+
+pidfile                @TESTDIR@/slapd.m.pid
+
+argsfile       @TESTDIR@/slapd.m.args
+
+#mod#modulepath        ../servers/slapd/back-@BACKEND@/
+#mod#moduleload        back_@BACKEND@.la
+
+moduleload ../resultstats.la
+
+pidfile @TESTDIR@/slapd.1.pid
+
+#######################################################################
+# database definitions
+#######################################################################
+database @BACKEND@
+suffix "dc=example,dc=com"
+
+directory  @TESTDIR@/db.1.a
+# root or superuser
+rootdn "cn=Manager,dc=example,dc=com"
+rootpw secret
+
+database config
+include   @TESTDIR@/configpw.conf
+
+database monitor
diff --git a/contrib/slapd-modules/resultstats/tests/data/test001-add.ldif b/contrib/slapd-modules/resultstats/tests/data/test001-add.ldif
new file mode 100644 (file)
index 0000000..6d9f0de
--- /dev/null
@@ -0,0 +1,99 @@
+dn: dc=example,dc=com
+objectClass: top
+objectClass: organization
+objectClass: domainRelatedObject
+objectClass: dcObject
+dc: example
+l: Anytown, Michigan
+st: Michigan
+o: Example, Inc.
+o: EX
+o: Ex.
+description: The Example, Inc. at Anytown
+postalAddress: Example, Inc. $ 535 W. William St. $ Anytown, MI 48109 $ US
+telephoneNumber: +1 313 555 1817
+associatedDomain: example.com
+
+dn: ou=people,dc=example,dc=com
+objectClass: organizationalUnit
+ou: people
+description: All domain members
+
+dn: cn=user01,ou=people,dc=example,dc=com
+cn: user01
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 101
+carLicense: 1234ha
+sn: user01
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user01
+
+dn: cn=user02,ou=people,dc=example,dc=com
+cn: user02
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 102
+carLicense: 1234hb
+sn: user02
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user02
+
+dn: cn=user03,ou=people,dc=example,dc=com
+cn: user03
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 103
+carLicense: 1234hc
+sn: user03
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user03
+
+dn: cn=user04,ou=people,dc=example,dc=com
+cn: user04
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 104
+carLicense: 1234ha
+sn: user04
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user04
+
+dn: cn=user05,ou=people,dc=example,dc=com
+cn: user05
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 105
+carLicense: 1234hb
+sn: user05
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user05
+
+dn: cn=user06,ou=people,dc=example,dc=com
+cn: user06
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 106
+carLicense: 1234hc
+sn: user06
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user06
+
diff --git a/contrib/slapd-modules/resultstats/tests/data/test001-bind.ldif b/contrib/slapd-modules/resultstats/tests/data/test001-bind.ldif
new file mode 100644 (file)
index 0000000..e751812
--- /dev/null
@@ -0,0 +1,6 @@
+dn: cn=Bind,cn=Result Stats,cn=frontend,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Bind
+olmResultCodeStat;x-resultcode-0: 28
+olmResultCodeStat;x-resultcode-49: 5
+
diff --git a/contrib/slapd-modules/resultstats/tests/data/test001-modify.ldif b/contrib/slapd-modules/resultstats/tests/data/test001-modify.ldif
new file mode 100644 (file)
index 0000000..5195394
--- /dev/null
@@ -0,0 +1,6 @@
+dn: cn=Modify,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Modify
+olmResultCodeStat;x-resultcode-0: 5
+olmResultCodeStat;x-resultcode-32: 5
+
diff --git a/contrib/slapd-modules/resultstats/tests/data/test001-reset.ldif b/contrib/slapd-modules/resultstats/tests/data/test001-reset.ldif
new file mode 100644 (file)
index 0000000..06678bb
--- /dev/null
@@ -0,0 +1,45 @@
+#      base="cn=Result Stats,cn=database 1,cn=databases,cn=monitor"...
+dn: cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: monitorContainer
+cn: Result Stats
+
+dn: cn=Bind,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Bind
+
+dn: cn=Unbind,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Unbind
+
+dn: cn=Search,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Search
+
+dn: cn=Compare,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Compare
+
+dn: cn=Modify,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Modify
+
+dn: cn=Modrdn,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Modrdn
+
+dn: cn=Add,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Add
+
+dn: cn=Delete,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Delete
+
+dn: cn=Abandon,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Abandon
+
+dn: cn=Extended,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Extended
+
diff --git a/contrib/slapd-modules/resultstats/tests/data/test001-search.ldif b/contrib/slapd-modules/resultstats/tests/data/test001-search.ldif
new file mode 100644 (file)
index 0000000..0dee906
--- /dev/null
@@ -0,0 +1,6 @@
+dn: cn=Search,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Search
+olmResultCodeStat;x-resultcode-0: 5
+olmResultCodeStat;x-resultcode-32: 5
+
diff --git a/contrib/slapd-modules/resultstats/tests/run b/contrib/slapd-modules/resultstats/tests/run
new file mode 100755 (executable)
index 0000000..9f24063
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2024 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+TOPSRCDIR="$SRCDIR" OBJDIR="${LDAP_BUILD}" SRCDIR="${SRCDIR}/tests" DEFSDIR="${SRCDIR}/scripts" SCRIPTDIR="${TOPDIR}/tests/scripts" "${LDAP_BUILD}/tests/run" $*
+
diff --git a/contrib/slapd-modules/resultstats/tests/scripts/test001-resultstats b/contrib/slapd-modules/resultstats/tests/scripts/test001-resultstats
new file mode 100755 (executable)
index 0000000..080cddf
--- /dev/null
@@ -0,0 +1,372 @@
+#! /bin/sh
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2025 The OpenLDAP Foundation.
+## Copyright 2025 Symas Corp.
+##
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This work was developed in 2025 by Nadezhda Ivanova for Symas Corp.
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+echo ""
+
+rm -rf $TESTDIR
+
+mkdir -p $TESTDIR $DBDIR1
+
+$SLAPPASSWD -g -n >$CONFIGPWF
+echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >$TESTDIR/configpw.conf
+
+echo "Starting slapd on TCP/IP port $PORT1..."
+. $CONFFILTER $BACKEND < data/slapd.conf > $CONF1
+$SLAPD -f $CONF1 -h $URI1 -d $LVL > $LOG1 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+       echo PID $PID
+       read foo
+fi
+KILLPIDS="$PID"
+
+sleep 1
+
+echo "Using ldapsearch to check that slapd is running..."
+for i in 0 1 2 3 4 5; do
+       $LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \
+               'objectclass=*' > /dev/null 2>&1
+       RC=$?
+       if test $RC = 0 ; then
+               break
+       fi
+       echo "Waiting 5 seconds for slapd to start..."
+       sleep 5
+done
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Using ldapadd to populate the database..."
+$LDAPADD -D "$MANAGERDN" -H $URI1 -w $PASSWD < \
+       data/test001-add.ldif > $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapadd failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+cat /dev/null > $SEARCHOUT
+
+echo "Searching base=\"$BASEDN\"..."
+echo "# searching base=\"$BASEDN\"..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -H $URI1 -b "$BASEDN" >> $SEARCHOUT 2>&1
+RC=$?
+
+if test $RC != 0 ; then
+       echo "Search failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+#enable the overlay
+echo "Modifying cn=config, enabling resultstats overlay on $BACKEND"
+$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1 << EOMODS
+dn: olcOverlay=resultstats,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+echo "Modifying cn=config, enabling resultstats overlay on frontend"
+$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1 << EOMODS
+dn: olcOverlay=resultstats,olcDatabase={-1}frontend,cn=config
+objectClass: olcOverlayConfig
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+#run successful searches
+echo "Performing successful search..."
+for i in 1 2 3 4 5; do
+       $LDAPSEARCH -S "" -H $URI1 -b "cn=user0$i,ou=people,$BASEDN" "(objectClass=*)" '*' \
+                               > /dev/null 2>&1
+       RC=$?
+       if test $RC != 0 ; then
+               echo "Expected successful search ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+done
+#verify
+
+RC=$?
+if test $RC != 0 ; then
+       echo "Expected successful search ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+#run noSuchObject searches
+echo "Performing incorrect search (noSuchObject).."
+for i in 1 2 3 4 5; do
+       $LDAPSEARCH -S "" -H $URI1 -b "cn=user0$i,$BASEDN" "(objectClass=*)" '*' \
+                               > /dev/null 2>&1
+       RC=$?
+       if test $RC != 32 ; then
+               echo "Expected noSuchObject ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+done
+#verify
+SEARCHDN="cn=Search,cn=Result Stats,cn=database 1,cn=databases,cn=monitor"
+echo "Verifying search statistics..."
+cat /dev/null > $SEARCHOUT
+
+echo " base=\"$SEARCHDN\"..."
+echo "#        base=\"$SEARCHDN\"..." >> $SEARCHOUT
+$LDAPSEARCH  -H $URI1 \
+                        -b "$SEARCHDN" \
+                        "(objectClass=*)" '*' >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "Unable to read monitor stats ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+$LDIFFILTER < data/test001-search.ldif > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "comparison failed - search statistics incorrect"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+#run successful mods
+echo "Performing successful modify..."
+for i in 1 2 3 4 5; do
+       $LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD \
+                               >> $TESTOUT 2>&1 << EOMODS
+dn: cn=user0$i,ou=people,$BASEDN
+changetype: modify
+replace: roomNumber
+roomNumber: 200
+EOMODS
+       RC=$?
+       if test $RC != 0 ; then
+               echo "Expected successful modify ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+done
+
+#run incorrect mods
+#this will not be counted, as it happens before database choice
+echo "Performing incorrect modify (undefinedAttributeType)..."
+for i in 1 2 3 4 5; do
+       $LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD \
+                               >> $TESTOUT 2>&1 << EOMODS
+dn: cn=user0$i,ou=people,$BASEDN
+changetype: modify
+replace: IsBusy
+IsBusy: TRUE
+EOMODS
+       RC=$?
+       if test $RC != 17 ; then
+               echo "Expected undefinedAttributeType ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+done
+echo "Performing incorrect modify (noSuchObject)..."
+for i in 1 2 3 4 5; do
+       $LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD \
+                               >> $TESTOUT 2>&1 << EOMODS
+dn: cn=user0$i,$BASEDN
+changetype: modify
+replace: roomNumber
+roomNumber: 200
+EOMODS
+       RC=$?
+       if test $RC != 32 ; then
+               echo "Expected noSuchObject ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+done
+echo "Performing incorrect modify (invalidCredentials)..."
+#this will only be counted on the frontend
+for i in 1 2 3 4 5; do
+       $LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w "wrongpassword" \
+                               >> $TESTOUT 2>&1 << EOMODS
+dn: cn=user0$i,ou=people,$BASEDN
+changetype: modify
+replace: roomNumber
+roomNumber: 200
+EOMODS
+       RC=$?
+       if test $RC != 49 ; then
+               echo "Expected invalidCredentials ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+done
+#verify
+echo "Verifying modify statistics..."
+cat /dev/null > $SEARCHOUT
+MODIFYDN="cn=Modify,cn=Result Stats,cn=database 1,cn=databases,cn=monitor"
+echo " base=\"$MODIFYDN\"..."
+echo "#        base=\"$MODIFYDN\"..." >> $SEARCHOUT
+$LDAPSEARCH  -H $URI1 \
+                        -b "$MODIFYDN" \
+                        "(objectClass=*)" '*' >> $SEARCHOUT 2>&1
+
+RC=$?
+if test $RC != 0 ; then
+       echo "Unable to read monitor stats ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+$LDIFFILTER < data/test001-modify.ldif > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+if test $? != 0 ; then
+       echo "comparison failed - modify statistics incorrect"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+echo "Verifying bind statistics..."
+cat /dev/null > $SEARCHOUT
+BINDDN="cn=Bind,cn=Result Stats,cn=frontend,cn=databases,cn=monitor"
+echo " base=\"$BINDDN\"..."
+echo "#        base=\"$BINDDN\"..." >> $SEARCHOUT
+$LDAPSEARCH  -H $URI1 \
+                        -b "$BINDDN" \
+                        "(objectClass=*)" '*' >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "Unable to read monitor stats ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+$LDIFFILTER < data/test001-bind.ldif > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "comparison failed - bind statistics incorrect"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+cat /dev/null > $SEARCHOUT
+
+OVERLAYDN="olcOverlay={0}resultstats,olcDatabase={1}$BACKEND,cn=config"
+echo "Modifying cn=config, deleting resultstats overlay on $BACKEND"
+$LDAPDELETE -D cn=config -H $URI1 -y $CONFIGPWF $OVERLAYDN\
+       >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapdelete failed ($RC)! Unable to delete resultstats overlay"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Modifying cn=config, enabling resultstats overlay on $BACKEND"
+$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1 << EOMODS
+dn: olcOverlay=resultstats,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Verifying statistics are reset..."
+cat /dev/null > $SEARCHOUT
+RESDN="cn=Result Stats,cn=database 1,cn=databases,cn=monitor"
+echo " base=\"$RESDN\"..."
+echo "#        base=\"$RESDN\"..." >> $SEARCHOUT
+$LDAPSEARCH  -H $URI1 \
+                        -b "$RESDN" \
+                        "(objectClass=*)" '*' >> $SEARCHOUT 2>&1
+
+RC=$?
+if test $RC != 0 ; then
+       echo "Unable to read monitor stats ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+$LDIFFILTER < data/test001-reset.ldif > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+if test $? != 0 ; then
+       echo "comparison failed - reset statistics incorrect"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+OVERLAYDN="olcOverlay={0}resultstats,olcDatabase={1}$BACKEND,cn=config"
+echo "Modifying cn=config, deleting resultstats overlay on $BACKEND"
+$LDAPDELETE -D cn=config -H $URI1 -y $CONFIGPWF $OVERLAYDN\
+       >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapdelete failed ($RC)! Unable to delete resultstats overlay"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Modifying cn=config, enabling resultstats overlay on $BACKEND"
+$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1 << EOMODS
+dn: olcOverlay=resultstats,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0