/* $NetBSD: slapd-watcher.c,v 1.2 2021/08/14 16:15:03 christos Exp $ */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software <http://www.openldap.org/>. * * Copyright 1999-2021 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 * <http://www.OpenLDAP.org/license.html>. */ /* ACKNOWLEDGEMENTS: * This work was initially developed by Howard Chu for inclusion * in OpenLDAP Software. */ #include <sys/cdefs.h> __RCSID("$NetBSD: slapd-watcher.c,v 1.2 2021/08/14 16:15:03 christos Exp $"); #include "portable.h" #include <stdio.h> #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 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 <dn> [ -w <passwd> ]] " "[-d <level>] " "[-O <SASL secprops>] " "[-R <SASL realm>] " "[-U <SASL authcid> [-X <SASL authzid>]] " "[-x | -Y <SASL mech>] " "[-i <interval>] " "[-s <sids>] " "[-b <baseDN> ] URI[...]\n", name ); exit( EXIT_FAILURE ); } struct berval base; 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; i<numservers; i++) { if ( sv->csn_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<numservers; i++) { printf("\n%s", servers[i].url ); if ( servers[i].flags & WAS_DOWN ) { printf(", down@"); timestamp( &servers[i].down ); } if ( servers[i].flags & WAS_LATE ) { printf(", late@"); timestamp( &servers[i].late ); } printf("\n"); if ( servers[i].flags & HAS_MONITOR ) { struct timeval tv; double rate, duration; long delta; printf(" "); if ( servers[i].flags & HAS_ENTRIES ) printf(" Entries "); for ( j = 0; j<SLAP_OP_LAST; j++ ) printf(" %9s ", opnames[j].display); printf("\n"); printf("Num "); if ( servers[i].flags & HAS_ENTRIES ) printf("%10lu ", servers[i].c_curr.entries); for ( j = 0; j<SLAP_OP_LAST; j++ ) printf("%10lu ", servers[i].c_curr.ops[j]); printf("\n"); printf("Num/s "); tv.tv_usec = now.tv_usec - servers[i].c_prev.time.tv_usec; tv.tv_sec = now.tv_sec - servers[i].c_prev.time.tv_sec; if ( tv.tv_usec < 0 ) { tv.tv_usec += 1000000; tv.tv_sec--; } duration = tv.tv_sec + (tv.tv_usec / (double)1000000); if ( servers[i].flags & HAS_ENTRIES ) { delta = servers[i].c_curr.entries - servers[i].c_prev.entries; rate = delta / duration; printf("%10.2f ", rate); } for ( j = 0; j<SLAP_OP_LAST; j++ ) { delta = servers[i].c_curr.ops[j] - servers[i].c_prev.ops[j]; rate = delta / duration; printf("%10.2f ", rate); } printf("\n"); } if ( servers[i].flags & HAS_BASE ) { for (j=0; j<numservers; j++) { /* skip empty CSNs */ if (!servers[i].csn_curr.vals[j].bv_len || !servers[i].csn_curr.vals[j].bv_val[0]) continue; printf("contextCSN: %s", servers[i].csn_curr.vals[j].bv_val ); if (ber_bvcmp(&servers[i].csn_curr.vals[j], &servers[i].csn_prev.vals[j])) { /* a difference */ if (servers[i].times[j].idle) { servers[i].times[j].idle = 0; servers[i].times[j].active = 0; servers[i].times[j].maxlag = 0; servers[i].times[j].lag = 0; } active: if (!servers[i].times[j].active) servers[i].times[j].active = now_t; printf(" actv@"); timestamp(&servers[i].times[j].active); } else if ( servers[i].times[j].lag || ( servers[i].flags & WAS_LATE )) { goto active; } else { if (servers[i].times[j].active && !servers[i].times[j].idle) servers[i].times[j].idle = now_t; if (servers[i].times[j].active) { printf(" actv@"); timestamp(&servers[i].times[j].active); printf(", idle@"); timestamp(&servers[i].times[j].idle); } else { printf(" idle"); } } if (i != j) { if (ber_bvcmp(&servers[i].csn_curr.vals[j], &servers[j].csn_curr.vals[j])) { struct timeval delta; int ahead = 0; time_t deltatt; delta.tv_sec = servers[j].csn_curr.tvs[j].tv_sec - servers[i].csn_curr.tvs[j].tv_sec; delta.tv_usec = servers[j].csn_curr.tvs[j].tv_usec - servers[i].csn_curr.tvs[j].tv_usec; if (delta.tv_usec < 0) { delta.tv_usec += 1000000; delta.tv_sec--; } if (delta.tv_sec < 0) { delta.tv_sec = -delta.tv_sec; ahead = 1; } deltatt = delta.tv_sec; if (ahead) printf(", ahead "); else printf(", behind "); deltat( &deltatt ); servers[i].times[j].lag = deltatt; if (deltatt > 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; i<numservers; i++) if ( c->vals[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; j<numservers; j++) if (sid == servers[j].sid) break; if (j < numservers) { ber_bvreplace( &c->vals[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, int first ) { config->uri = sv->url; tester_init_ld( &sv->ld, config, first ? 0 : 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 ); if ( first ) exit( EXIT_FAILURE ); } ldap_msgfree( res ); if ( base.bv_val ) { char *attr2[] = { at_contextCSN.bv_val, NULL }; rc = ldap_search_ext_s( ld, base.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; } } } ldap_msgfree( res ); break; default: tester_ldap_error( ld, "ldap_search_ext_s(baseDN)", sv->url ); if ( first ) exit( EXIT_FAILURE ); } } } 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 ( first ) rotate_stats( sv ); return 0; } int main( int argc, char **argv ) { int i, rc, *msg1, *msg2; char **sids = NULL; struct tester_conn_args *config; int first = 1; config = tester_init( "slapd-watcher", TESTER_TESTER ); config->authmethod = LDAP_AUTH_SIMPLE; while ( ( i = getopt( argc, argv, "D:O:R:U:X:Y:b:d:i:s:w:x" ) ) != EOF ) { switch ( i ) { case 'b': /* base DN for contextCSN lookups */ ber_str2bv( optarg, 0, 0, &base ); 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 ( numservers > 1 ) { for ( i=0; i<numservers; i++ ) if ( sids ) servers[i].sid = atoi(sids[i]); else servers[i].sid = i+1; } for ( i = 0; i < numservers; i++ ) { servers[i].url = argv[i]; servers[i].times = calloc( numservers, sizeof(activity)); servers[i].csn_curr.vals = calloc( numservers, sizeof(struct berval)); servers[i].csn_prev.vals = calloc( numservers, sizeof(struct berval)); servers[i].csn_curr.tvs = calloc( numservers, sizeof(struct timeval)); servers[i].csn_prev.tvs = calloc( numservers, sizeof(struct timeval)); } msg1 = malloc( numservers * 2 * sizeof(int)); msg2 = msg1 + numservers; for (;;) { LDAPMessage *res = NULL, *e = NULL; BerElement *ber = NULL; struct berval dn, bv, *bvals, **bvp = &bvals; struct timeval tv; LDAP *ld; for (i=0; i<numservers; i++) { if ( !servers[i].ld || !(servers[i].flags & WAS_LATE )) { msg1[i] = 0; msg2[i] = 0; } if ( !servers[i].ld ) { setup_server( config, &servers[i], first ); } else { ld = servers[i].ld; rc = -1; if ( servers[i].flags & WAS_DOWN ) servers[i].flags ^= WAS_DOWN; if (( servers[i].flags & HAS_MONITOR ) && !msg1[i] ) { char *attrs[3] = { at_monitorOpCompleted.bv_val }; if ( servers[i].flags & HAS_ENTRIES ) attrs[1] = at_olmMDBEntries.bv_val; rc = ldap_search_ext( ld, "cn=monitor", LDAP_SCOPE_SUBTREE, servers[i].monitorfilter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msg1[i] ); if ( rc != LDAP_SUCCESS ) { tester_ldap_error( ld, "ldap_search_ext(cn=Monitor)", servers[i].url ); if ( first ) exit( EXIT_FAILURE ); else { server_down1: ldap_unbind_ext( ld, NULL, NULL ); servers[i].flags |= WAS_DOWN; servers[i].ld = NULL; gettimeofday( &tv, NULL ); servers[i].down = tv.tv_sec; msg1[i] = 0; msg2[i] = 0; continue; } } } if (( servers[i].flags & HAS_BASE ) && !msg2[i] ) { char *attrs[2] = { at_contextCSN.bv_val }; rc = ldap_search_ext( ld, base.bv_val, LDAP_SCOPE_BASE, "(objectClass=*)", attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msg2[i] ); if ( rc != LDAP_SUCCESS ) { tester_ldap_error( ld, "ldap_search_ext(baseDN)", servers[i].url ); if ( first ) exit( EXIT_FAILURE ); else goto server_down1; } } if ( rc != -1 ) gettimeofday( &servers[i].c_curr.time, 0 ); } } for (i=0; i<numservers; i++) { ld = servers[i].ld; if ( msg1[i] ) { tv.tv_sec = 0; tv.tv_usec = 250000; rc = ldap_result( ld, msg1[i], LDAP_MSG_ALL, &tv, &res ); if ( rc < 0 ) { tester_ldap_error( ld, "ldap_result(cn=Monitor)", servers[i].url ); if ( first ) exit( EXIT_FAILURE ); else { server_down2: ldap_unbind_ext( ld, NULL, NULL ); servers[i].flags |= WAS_DOWN; servers[i].ld = NULL; servers[i].down = servers[i].c_curr.time.tv_sec; msg1[i] = 0; msg2[i] = 0; continue; } } if ( rc == 0 ) { if ( !( servers[i].flags & WAS_LATE )) servers[i].late = servers[i].c_curr.time.tv_sec; servers[i].flags |= WAS_LATE; continue; } if ( servers[i].flags & WAS_LATE ) servers[i].flags ^= WAS_LATE; 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 )) { 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_olmMDBEntries )) { if ( !BER_BVISNULL( &servers[i].monitorbase )) { servers[i].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, &servers[i].c_curr ); break; } if ( ber ) ber_free( ber, 0 ); } ldap_msgfree( res ); } if ( msg2[i] ) { tv.tv_sec = 0; tv.tv_usec = 250000; rc = ldap_result( ld, msg2[i], LDAP_MSG_ALL, &tv, &res ); if ( rc < 0 ) { tester_ldap_error( ld, "ldap_result(baseDN)", servers[i].url ); if ( first ) exit( EXIT_FAILURE ); else goto server_down2; } if ( rc == 0 ) { if ( !( servers[i].flags & WAS_LATE )) servers[i].late = servers[i].c_curr.time.tv_sec; servers[i].flags |= WAS_LATE; continue; } if ( servers[i].flags & WAS_LATE ) servers[i].flags ^= WAS_LATE; e = ldap_first_entry( ld, res ); if ( e ) { 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( &servers[i].csn_curr, bvals ); done = 1; } ber_memfree( bvals ); bvals = NULL; if ( done ) break; } } } ldap_msgfree( res ); } } display(); sleep(interval); first = 0; } exit( EXIT_SUCCESS ); }