/* $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