/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software .
*
* Copyright 1999-2022 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 file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* .
*/
/* ACKNOWLEDGEMENTS:
* This work was initially developed by Howard Chu for inclusion
* in OpenLDAP Software.
*/
#include "portable.h"
#include
#include "ac/signal.h"
#include "ac/stdlib.h"
#include "ac/time.h"
#include "ac/ctype.h"
#include "ac/param.h"
#include "ac/socket.h"
#include "ac/string.h"
#include "ac/unistd.h"
#include "ac/wait.h"
#include "ac/time.h"
#include "ldap.h"
#include "lutil.h"
#include "lutil_ldap.h"
#include "lber_pvt.h"
#include "ldap_pvt.h"
#include "slapd-common.h"
#define SLAP_SYNC_SID_MAX 4095
#define HAS_MONITOR 1
#define HAS_BASE 2
#define HAS_ENTRIES 4
#define HAS_SREPL 8
#define HAS_ALL (HAS_MONITOR|HAS_BASE|HAS_ENTRIES|HAS_SREPL)
#define WAS_LATE 0x100
#define WAS_DOWN 0x200
#define WAS_INIT 0x400
#define MONFILTER "(objectClass=monitorOperation)"
static const char *default_monfilter = MONFILTER;
typedef enum {
SLAP_OP_BIND = 0,
SLAP_OP_UNBIND,
SLAP_OP_SEARCH,
SLAP_OP_COMPARE,
SLAP_OP_MODIFY,
SLAP_OP_MODRDN,
SLAP_OP_ADD,
SLAP_OP_DELETE,
SLAP_OP_ABANDON,
SLAP_OP_EXTENDED,
SLAP_OP_LAST
} slap_op_t;
struct opname {
struct berval rdn;
char *display;
} opnames[] = {
{ BER_BVC("cn=Bind"), "Bind" },
{ BER_BVC("cn=Unbind"), "Unbind" },
{ BER_BVC("cn=Search"), "Search" },
{ BER_BVC("cn=Compare"), "Compare" },
{ BER_BVC("cn=Modify"), "Modify" },
{ BER_BVC("cn=Modrdn"), "ModDN" },
{ BER_BVC("cn=Add"), "Add" },
{ BER_BVC("cn=Delete"), "Delete" },
{ BER_BVC("cn=Abandon"), "Abandon" },
{ BER_BVC("cn=Extended"), "Extended" },
{ BER_BVNULL, NULL }
};
typedef struct counters {
struct timeval time;
unsigned long entries;
unsigned long ops[SLAP_OP_LAST];
} counters;
typedef struct csns {
struct berval *vals;
struct timeval *tvs;
} csns;
typedef struct activity {
time_t active;
time_t idle;
time_t maxlag;
time_t lag;
} activity;
typedef struct server {
char *url;
LDAP *ld;
int flags;
int sid;
struct berval monitorbase;
char *monitorfilter;
time_t late;
time_t down;
counters c_prev;
counters c_curr;
csns csn_prev;
csns csn_curr;
activity *times;
} server;
static void
usage( char *name, char opt )
{
if ( opt ) {
fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
name, opt );
}
fprintf( stderr, "usage: %s "
"[-D [ -w ]] "
"[-d ] "
"[-O ] "
"[-R ] "
"[-U [-X ]] "
"[-x | -Y ] "
"[-i ] "
"[-s ] "
"[-c ] "
"[-b ] URI[...]\n",
name );
exit( EXIT_FAILURE );
}
struct berval base, cbase;
int interval = 10;
int numservers;
server *servers;
char *monfilter;
struct berval at_namingContexts = BER_BVC("namingContexts");
struct berval at_monitorOpCompleted = BER_BVC("monitorOpCompleted");
struct berval at_olmMDBEntries = BER_BVC("olmMDBEntries");
struct berval at_contextCSN = BER_BVC("contextCSN");
void timestamp(time_t *tt)
{
struct tm *tm = gmtime(tt);
printf("%d-%02d-%02d %02d:%02d:%02d",
tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
}
void deltat(time_t *tt)
{
struct tm *tm = gmtime(tt);
if (tm->tm_mday-1)
printf("%02d+", tm->tm_mday-1);
printf("%02d:%02d:%02d",
tm->tm_hour, tm->tm_min, tm->tm_sec);
}
static char *clearscreen = "\033[H\033[2J";
void rotate_stats( server *sv )
{
if ( sv->flags & HAS_MONITOR )
sv->c_prev = sv->c_curr;
if ( sv->flags & HAS_BASE ) {
int i;
for (i=0; icsn_curr.vals[i].bv_len ) {
ber_bvreplace(&sv->csn_prev.vals[i],
&sv->csn_curr.vals[i]);
sv->csn_prev.tvs[i] = sv->csn_curr.tvs[i];
} else {
if ( sv->csn_prev.vals[i].bv_val )
sv->csn_prev.vals[i].bv_val[0] = '\0';
}
}
}
}
void display()
{
int i, j;
struct timeval now;
time_t now_t;
gettimeofday(&now, NULL);
now_t = now.tv_sec;
printf("%s", clearscreen);
timestamp(&now_t);
printf("\n");
for (i=0; i servers[i].times[j].maxlag)
servers[i].times[j].maxlag = deltatt;
} else {
servers[i].times[j].lag = 0;
printf(", sync'd");
}
if (servers[i].times[j].maxlag) {
printf(", max delta ");
deltat( &servers[i].times[j].maxlag );
}
}
printf("\n");
}
}
if ( !( servers[i].flags & WAS_LATE ))
rotate_stats( &servers[i] );
}
}
void get_counters(
LDAP *ld,
LDAPMessage *e,
BerElement *ber,
counters *c )
{
int rc;
slap_op_t op = SLAP_OP_BIND;
struct berval dn, bv, *bvals, **bvp = &bvals;
do {
int done = 0;
for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp );
rc == LDAP_SUCCESS;
rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) {
if ( bv.bv_val == NULL ) break;
if ( !ber_bvcmp( &bv, &at_monitorOpCompleted ) && bvals ) {
c->ops[op] = strtoul( bvals[0].bv_val, NULL, 0 );
done = 1;
}
if ( bvals ) {
ber_memfree( bvals );
bvals = NULL;
}
if ( done )
break;
}
ber_free( ber, 0 );
e = ldap_next_entry( ld, e );
if ( !e )
break;
ldap_get_dn_ber( ld, e, &ber, &dn );
op++;
} while ( op < SLAP_OP_LAST );
}
int
slap_parse_csn_sid( struct berval *csnp )
{
char *p, *q;
struct berval csn = *csnp;
int i;
p = ber_bvchr( &csn, '#' );
if ( !p )
return -1;
p++;
csn.bv_len -= p - csn.bv_val;
csn.bv_val = p;
p = ber_bvchr( &csn, '#' );
if ( !p )
return -1;
p++;
csn.bv_len -= p - csn.bv_val;
csn.bv_val = p;
q = ber_bvchr( &csn, '#' );
if ( !q )
return -1;
csn.bv_len = q - p;
i = strtol( p, &q, 16 );
if ( p == q || q != p + csn.bv_len || i < 0 || i > SLAP_SYNC_SID_MAX ) {
i = -1;
}
return i;
}
void get_csns(
csns *c,
struct berval *bvs
)
{
int i, j;
/* clear old values if any */
for (i=0; ivals[i].bv_val )
c->vals[i].bv_val[0] = '\0';
for (i=0; bvs[i].bv_val; i++) {
struct lutil_tm tm;
struct lutil_timet tt;
int sid = slap_parse_csn_sid( &bvs[i] );
for (j=0; jvals[j], &bvs[i] );
lutil_parsetime(bvs[i].bv_val, &tm);
c->tvs[j].tv_usec = tm.tm_nsec / 1000;
lutil_tm2time( &tm, &tt );
c->tvs[j].tv_sec = tt.tt_sec;
}
}
}
int
setup_server( struct tester_conn_args *config, server *sv )
{
config->uri = sv->url;
tester_init_ld( &sv->ld, config, TESTER_INIT_NOEXIT );
if ( !sv->ld )
return -1;
sv->flags &= ~HAS_ALL;
{
char *attrs[] = { at_namingContexts.bv_val, at_monitorOpCompleted.bv_val,
at_olmMDBEntries.bv_val, NULL };
LDAPMessage *res = NULL, *e = NULL;
BerElement *ber = NULL;
LDAP *ld = sv->ld;
struct berval dn, bv, *bvals, **bvp = &bvals;
int j, rc;
rc = ldap_search_ext_s( ld, "cn=monitor", LDAP_SCOPE_SUBTREE, monfilter,
attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
switch(rc) {
case LDAP_SIZELIMIT_EXCEEDED:
case LDAP_TIMELIMIT_EXCEEDED:
case LDAP_SUCCESS:
gettimeofday( &sv->c_curr.time, 0 );
sv->flags |= HAS_MONITOR;
for ( e = ldap_first_entry( ld, res ); e; e = ldap_next_entry( ld, e )) {
ldap_get_dn_ber( ld, e, &ber, &dn );
if ( !strncasecmp( dn.bv_val, "cn=Database", sizeof("cn=Database")-1 ) ||
!strncasecmp( dn.bv_val, "cn=Frontend", sizeof("cn=Frontend")-1 )) {
int matched = 0;
for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp );
rc == LDAP_SUCCESS;
rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) {
if ( bv.bv_val == NULL ) break;
if (!ber_bvcmp( &bv, &at_namingContexts ) && bvals ) {
for (j=0; bvals[j].bv_val; j++) {
if ( !ber_bvstrcasecmp( &base, &bvals[j] )) {
matched = 1;
break;
}
}
if (!matched) {
ber_memfree( bvals );
bvals = NULL;
break;
}
}
if (!ber_bvcmp( &bv, &at_olmMDBEntries )) {
ber_bvreplace( &sv->monitorbase, &dn );
sv->flags |= HAS_ENTRIES;
sv->c_curr.entries = strtoul( bvals[0].bv_val, NULL, 0 );
}
ber_memfree( bvals );
bvals = NULL;
}
} else if (!strncasecmp( dn.bv_val, opnames[0].rdn.bv_val,
opnames[0].rdn.bv_len )) {
get_counters( ld, e, ber, &sv->c_curr );
break;
}
if ( ber )
ber_free( ber, 0 );
}
break;
case LDAP_NO_SUCH_OBJECT:
/* no cn=monitor */
break;
default:
tester_ldap_error( ld, "ldap_search_ext_s(cn=Monitor)", sv->url );
}
ldap_msgfree( res );
if ( cbase.bv_val ) {
char *attr2[] = { at_contextCSN.bv_val, NULL };
rc = ldap_search_ext_s( ld, cbase.bv_val, LDAP_SCOPE_BASE, "(objectClass=*)",
attr2, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
switch(rc) {
case LDAP_SUCCESS:
e = ldap_first_entry( ld, res );
if ( e ) {
sv->flags |= HAS_BASE;
ldap_get_dn_ber( ld, e, &ber, &dn );
for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp );
rc == LDAP_SUCCESS;
rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) {
int done = 0;
if ( bv.bv_val == NULL ) break;
if ( bvals ) {
if ( !ber_bvcmp( &bv, &at_contextCSN )) {
get_csns( &sv->csn_curr, bvals );
done = 1;
}
ber_memfree( bvals );
bvals = NULL;
if ( done )
break;
}
}
}
break;
default:
tester_ldap_error( ld, "ldap_search_ext_s(baseDN)", sv->url );
}
ldap_msgfree( res );
}
}
if ( sv->monitorfilter != default_monfilter )
free( sv->monitorfilter );
if ( sv->flags & HAS_ENTRIES ) {
int len = sv->monitorbase.bv_len + sizeof("(|(entryDN=)" MONFILTER ")");
char *ptr = malloc(len);
sprintf(ptr, "(|(entryDN=%s)" MONFILTER ")", sv->monitorbase.bv_val );
sv->monitorfilter = ptr;
} else if ( sv->flags & HAS_MONITOR ) {
sv->monitorfilter = (char *)default_monfilter;
}
if ( !( sv->flags & WAS_INIT )) {
sv->flags |= WAS_INIT;
rotate_stats( sv );
}
return 0;
}
int
main( int argc, char **argv )
{
int i, rc, *msg1, *msg2;
char **sids = NULL;
struct tester_conn_args *config;
config = tester_init( "slapd-watcher", TESTER_TESTER );
config->authmethod = LDAP_AUTH_SIMPLE;
while ( ( i = getopt( argc, argv, "D:O:R:U:X:Y:b:c:d:i:s:w:x" ) ) != EOF )
{
switch ( i ) {
case 'b': /* base DN for DB entrycount lookups */
ber_str2bv( optarg, 0, 0, &base );
if ( !cbase.bv_val )
cbase = base;
break;
case 'c': /* base DN for contextCSN lookups */
ber_str2bv( optarg, 0, 0, &cbase );
break;
case 'i':
interval = atoi(optarg);
break;
case 's':
sids = ldap_str2charray( optarg, "," );
break;
default:
if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS )
break;
usage( argv[0], i );
break;
}
}
tester_config_finish( config );
#ifdef SIGPIPE
(void) SIGNAL(SIGPIPE, SIG_IGN);
#endif
/* don't clear the screen if debug is enabled */
if (debug)
clearscreen = "\n\n";
numservers = argc - optind;
if ( !numservers )
usage( argv[0], 0 );
if ( sids ) {
for (i=0; sids[i]; i++ );
if ( i != numservers ) {
fprintf(stderr, "Number of sids doesn't equal number of server URLs\n");
exit( EXIT_FAILURE );
}
}
argv += optind;
argc -= optind;
servers = calloc( numservers, sizeof(server));
if ( base.bv_val ) {
monfilter = "(|(entryDN:dnOneLevelMatch:=cn=Databases,cn=Monitor)" MONFILTER ")";
} else {
monfilter = MONFILTER;
}
if ( sids || numservers > 1 ) {
for ( i=0; i