source: people/joerg.steffens/technical/ldap-stats/ldap-stats.pl@ 1208

Last change on this file since 1208 was 1146, checked in by joergs, on Apr 17, 2013 at 1:06:51 PM

remove debug output

  • Property svn:executable set to *
File size: 42.9 KB
Line 
1#!/usr/bin/env perl
2#
3# Program: Generate LDAP Statistics Reports <ldap-stats.pl>
4#
5# Source code home: http://prefetch.net/code/ldap-stats.pl
6#
7# Author: Matty < matty91 @ gmail dot com >
8#
9# Current Version: 5.2
10#
11# Revision History:
12#
13# - add support for rsyslog log format -- Joerg Steffens, dass II GmbH
14#
15# Version 5.2
16# Perl::Tidy and Perl::Critic -- Gavin Henry, Suretec Systems Ltd.
17#
18# Version 5.1
19# - Changed the location of the uc() statement -- Quanah Gibson-Mount
20#
21# Version 5.0
22# - Changed reporting structure to be dynamic -- Quanah Gibson-Mount
23# - Fixed a bug with name resolution -- Quanah Gibson-Mount
24# - Added the URL to the script -- Quanah Gibson-Mount
25#
26# Version 4.2
27# - Utilize strict mode -- Peter Schober
28#
29# Version 4.1
30# - Fixed a typo in the length() function -- Peter Schober
31#
32# Version 4.0
33# - Added "-d" option to print all days
34# - Fixed day sort order
35# - Added "-m" option to print all months
36# - Fixed month sort order
37# - Correct spelling. -- Dave Horsfall
38# - Align headings. -- Dave Horsfall
39# - Support ldapi:// connections ("LOCAL-SOCKET"). -- Dave Horsfall
40# - Only do lookup if numeric IP. -- Dave Horsfall
41#
42# Version 3.0 - 3.4
43# - Added ability to resolve IP addresses to hostnames with "-n" option
44# - Adjusted print() routines to limit lines to 80-characters -- Dave Horsfall
45# - Clean up unnecessary (..) in regexes -- Peter Marschall
46# - Split attributes found in searches (controlled by new option -s) -- Peter Marschall
47# - Added report to print which filters are used
48# - Added report to print explicit attributes requested -- Francis Swasey
49# - Fix usage: correct line break, all lines < 80 chars -- Peter Marschall
50# - Replace unnecessary printf() by print -- Peter Marschall
51# - Concatenate arguments into one call to print instead of multiple calls -- Peter Marschall
52# - Adapt underlining of some headers to length of logfile / date -- Peter Marschall
53# - Added additional checks to address missing entries during logfile rotation
54# - Fixed "uninitialized value in hash element" -- Todd Lyons
55# - Added additional comments to code
56# - Added report for operations by time of day
57# - Added report for operations per day
58# - Added report for operations per month
59# - Removed debug statements to speedup logfile processing
60# - Changed printf() format specifiers to match column definitions
61#
62# Version 2.0 - 2.2
63# - Adjusted the Search base comparison to catch ""
64# - Translate "" to RootDSE in the search base results
65# - Only print "Unindexed attribute" if unindexed attributes exist
66# - Normalize the bind DN and search base to avoid duplicates
67# - Fix typo with binddn array
68# - Improved filter for anonymous and authenticated binds -- Peter Marschall
69# - Logfiles are now passed as arguments to ldap-stats.pl
70# (e.g, ldap-stats.pl openldap1 openldap2 openldap3 old* ) -- Peter Marschall
71# - Cleaned up and combined filters for ADDs, MODs, DELs -- Peter Marschall
72# - Added support for CMPs & MODRDNs -- Peter Marschall
73# - Reduced number of regular expressions to one per filter -- Peter Marschall
74# - Removed head and tail program requirements, as dates are read on the fly from the
75# decoded logfile -- Peter Marschall
76# - Support for gzip and bzip2 compressed files -- Peter Marschall
77# - Optimized some expressions -- Peter Marschall
78# - Removed several Perl warnings, and added "-w" to default runtime options -- Peter Marschall
79# - Support for regular expressions in logfile names (e.g., ldap-stats.pl /var/log/openldap* ) -- Peter Marschall
80# - Changed default Perl interpreter to /usr/bin/perl
81# - Changed to OpenLDAP license
82#
83# Version 1.1 - 1.9
84# - Updated the bind, binddn, search, search base, and unindexed search regexs to
85# match a wider array of characters -- added by Peter Marschall
86# - Shortened several regular expressions by replacing "[0-9]" with "\d" -- added by Peter Marschall
87# - Fixed a divide by zero bug when logfiles contain 0 connections -- added by Dave Horsfall
88# - Removed unnecessary file open(s)
89# - Removed end of line ($) character from anonymous BIND regular expressions
90# - Added "-l" option to print lines as they are processed from a logfile
91# - Updated documentation
92# - Updated formatting of search dn report
93# - Updated formatting of search base report
94# - Added an additional report with the number of binds per DN
95# - Updated examples
96# - Added additional debug messages to connection setup
97# - Fixed documentation issues
98# - Added debugging flag (-d) to give detailed information on logfile processing
99# - Added "usage" subroutine to ease option maintenance
100# - Fixed a bug in the BIND calculations -- found and fixed by Quanah Gibson-Mount
101# - Fixed a bug in the MOD calculations -- found and fixed by Quanah Gibson-Mount
102# - Fixed a bug in the SRCH calculations -- found and fixed by Quanah Gibson-Mount
103# - Added a connection associative array to coorelate conn identifiers w/hosts -- Quanah Gibson-Mount
104# - Updated the usage message with information on "-c" option
105# - The "-f" option now accepts multiple logfiles
106# - Changed the headers to include information on all logfiles processed
107# - Added the day the report was run to the report headers
108#
109# Version 1.0
110# Original release
111#
112# Last Updated: 13-11-2006
113#
114# Purpose:
115# Produces numerous reports from OpenLDAP 2.1, 2.2 and 2.3 logfiles.
116#
117# License:
118#
119# Redistribution and use in source and binary forms, with or without
120# modification, are permitted only as authorized by the OpenLDAP
121# Public License.
122#
123# A copy of this license is available in the file LICENSE in the
124# top-level directory of the distribution or, alternatively, at
125# <http://www.OpenLDAP.org/license.html>.
126#
127# Installation:
128# 1. Enable a minimum of 'loglevel 256' in the slapd.conf configuration file.
129# 2. Copy the shell script to a suitable location.
130# 3. Refer to the usage section for options and examples.
131#
132# Usage:
133# Refer to the usage subroutine,
134#
135# Example:
136# Refer to http://prefetch.net/code/ldap-stats.pl.txt to see sample output
137
138use strict;
139use warnings;
140use Getopt::Long;
141use Socket;
142use Carp;
143use 5.006; # As returned by Perl::MinimumVersion
144
145#######################
146### usage subroutine
147### Parameters: None
148#######################
149sub usage {
150 print
151"Usage: ldap-stats.pl [ -s ] [ -c <count> ] [ -l <count> ] [ -h ] <logfile> ...\n"
152 . " -c <count> Number of lines to display for each report [25]\n"
153 . " -d Display all available days in the day of month report\n"
154 . " -h Display a usage help screen\n"
155 . " -l <count> Print status message after processing <count> lines [0]\n"
156 . " -m Display all available months in the month of year report\n"
157 . " -n Resolve IP addresses to hostnames\n"
158 . " -o <ops> -o <ops> ... Operations to print in the reports [ALL]\n"
159 . " Valid operations are: CONNECT, FAILURES, BIND, UNBIND,\n"
160 . " SRCH, CMP, ADD, MOD, MODRDN, DEL\n"
161 . " Predefined reports are: ALL, READ, WRITE\n"
162 . " -s Split attributes found used in searches\n";
163 return;
164}
165
166### Declare lexical variables
167my ( $logfile, $i, $counter, $help );
168my ( %unindexed, %search, @operations );
169
170### Allow the number of entries displayed to be variable
171my $count = 25;
172
173### Figure out if we need to print "Processing X lines"
174my $increment = 0;
175
176## tell whether to split attributes in searches
177my $splitattrs = 0;
178
179# Tell whether to lookup names
180my $resolvename = 0;
181
182# Print all months
183my $printmonths = 0;
184
185# Print all days
186my $printdays = 0;
187
188###################################
189#### Get some options from the user
190###################################
191#getopts("o:l:c:nhsmd", \%options);
192
193GetOptions(
194 'count|c=i' => \$count,
195 'days|d' => \$printdays,
196 'help|h' => \$help,
197 'length|l=i' => \$increment,
198 'months|m' => \$printmonths,
199 'network|n' => \$resolvename,
200 'operations|o=s' => \@operations,
201 'split|s' => \$splitattrs
202);
203
204### print a nice usage message
205if ($help) {
206 usage;
207 exit 1;
208}
209
210### Make sure there is at least one logfile
211if ( !@ARGV ) {
212 usage;
213 exit 1;
214}
215
216############################
217### Define various variables
218############################
219my $date = localtime time;
220
221if ( !@operations ) {
222 @operations = ('ALL');
223}
224
225my %stats = (
226 TOTAL_CONNECT => 0,
227 TOTAL_BIND => 0,
228 TOTAL_UNBIND => 0,
229 TOTAL_SRCH => 0,
230 TOTAL_DEL => 0,
231 TOTAL_ADD => 0,
232 TOTAL_CMP => 0,
233 TOTAL_MOD => 0,
234 TOTAL_MODRDN => 0,
235 TOTAL_UNINDEXED => 0,
236 TOTAL_AUTHFAILURES => 0,
237);
238
239my %hours; # Hash to store the time of day (e.g., 21st of August)
240my %days; # Hash to store the days of each month (e.g., 21st)
241my %months; # Hash to store the day of the month (e.g., Dec)
242my %hosts; # Hash to store client IP addresses
243my %conns; # Hash to store connection identifiers
244my %binddns; # Hash to store bind DNs
245my %logarray; # Hash to store logfiles
246my %filters; # Hash to store search filters
247my %searchattributes; # Hash to store specific attributes that are requested
248my %operations; # Hash to store operations information
249
250$operations{CONNECT} = {
251 DATA => 0,
252 STRING => ' Connect',
253 SPACING => ' --------',
254 FIELD => '%8s',
255};
256
257$operations{FAILURES} = {
258 DATA => 0,
259 STRING => ' Failed',
260 SPACING => ' ------',
261 FIELD => '%6s',
262};
263
264$operations{BIND} = {
265 DATA => 0,
266 STRING => ' Bind',
267 SPACING => ' -------',
268 FIELD => '%7s',
269};
270
271$operations{UNBIND} = {
272 DATA => 0,
273 STRING => ' Unbind',
274 SPACING => ' -------',
275 FIELD => '%7s',
276};
277
278$operations{SRCH} = {
279 DATA => 0,
280 STRING => ' Search',
281 SPACING => ' --------',
282 FIELD => '%8s',
283};
284
285$operations{ADD} = {
286 DATA => 0,
287 STRING => ' Add',
288 SPACING => ' -----',
289 FIELD => '%5s',
290};
291
292$operations{CMP} = {
293 DATA => 0,
294 STRING => ' Cmp',
295 SPACING => ' -----',
296 FIELD => '%5s',
297};
298
299$operations{MOD} = {
300 DATA => 0,
301 STRING => ' Mod',
302 SPACING => ' -----',
303 FIELD => '%5s',
304};
305
306$operations{MODRDN} = {
307 DATA => 0,
308 STRING => ' ModRDN',
309 SPACING => ' ------',
310 FIELD => '%6s',
311};
312
313$operations{DEL} = {
314 DATA => 0,
315 STRING => ' Del',
316 SPACING => ' ----',
317 FIELD => '%4s',
318};
319
320# slapd log file can use different date formats:
321# rsyslog: 2013-04-17T10:45:15.576386+02:00
322# syslog: Mar 23 09:54:34
323# We check the first lone of the log file
324# and use the first date regex that matches.
325my @date_formats=('\d\d\d\d-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)\.\d\d\d\d\d\d\+\d\d:\d\d', '(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)');
326my $regex_date;
327
328###################################################
329### Open the logfile and process all of the entries
330###################################################
331FILE: for my $file (@ARGV) {
332 $logfile = $file;
333 my $lines = 0;
334
335 ### find open filter to use
336 my $openfilter = '<' . $logfile . q{};
337
338 ### decode gzipped / bzip2-compressed files
339 if ( $logfile =~ /\.bz2$/mx ) {
340 $openfilter = q{bzip2 -dc "} . $logfile . q{"|}
341 or carp "Problem decompressing!: $!\n";
342 }
343
344 if ( $logfile =~ /\.(gz|Z)$/mx ) {
345 $openfilter = q{gzip -dc "} . $logfile . q{"|}
346 or carp "Problem decompressing!: $!\n";
347 }
348
349 ### If the logfile isn't valid, move on to the next one
350 if ( !open LOGFILE, $openfilter ) {
351 print "ERROR: unable to open '$logfile': $!\n";
352 next;
353 }
354
355 ### setup the arrray to hold the start/stop times
356 $logarray{$logfile} = {
357 SDATE => q{},
358 EDATE => q{},
359 };
360
361 ### Only print banner if requested
362 if ( $increment > 0 ) {
363 ### Print a banner and initialize the $counter variable
364 print "\nProcessing file \"$logfile\"\n"
365 . q{-} x ( 18 + length ${$logfile} ) . "\n";
366 $counter = 0;
367 $lines = $increment;
368 }
369
370 while ( my $line = <LOGFILE> ) {
371
372 # first line
373 if ( !$logarray{$logfile}{SDATE} ) {
374 # check date format in logfile
375 for my $regex (@date_formats) {
376 if ( $line =~ /^($regex)/mx ) {
377 $logarray{$logfile}{SDATE} = $1;
378 $regex_date = $regex;
379 last;
380 }
381 }
382 if( ! $regex_date ) {
383 print "ERROR: unable to determine date format in '$logfile'\n";
384 next FILE;
385 }
386 }
387
388 ### check start and end dates
389 if ( $line =~ /^($regex_date)/mx ) {
390 $logarray{$logfile}{EDATE} = $1;
391 }
392
393 ### Check to see if we have processed $lines lines
394 if ( ( $lines > 0 ) && ( $counter == $lines ) ) {
395 print " Processed $lines lines in \"$logfile\"\n";
396 $lines += $increment;
397 }
398
399 ### Check for a new connection
400 if ( $line =~
401/^$regex_date .*conn=(\d+) [ ] fd=\d+ [ ] (?:ACCEPT|connection) [ ] from/mx
402 )
403 {
404 my $month = $1;
405 my $day = $2;
406 my $hour = $3;
407 my $conn = $6;
408 my $host;
409
410 if ( $line =~ /IP=(\d+\.\d+\.\d+\.\d+):/mx ) {
411 $host = $1;
412 }
413 elsif ( $line =~ /PATH=(\S+)/mx ) {
414 $host = 'LOCAL-SOCKET';
415 }
416 else {
417 $host = 'UNKNOWN';
418 }
419
420 ### Create an array to store the list of hosts
421 if ( !( defined $hosts{$host} ) ) {
422 $hosts{$host} = {
423 CONNECT => 1,
424 AUTHFAILURES => 0,
425 BIND => 0,
426 UNBIND => 0,
427 SRCH => 0,
428 ADD => 0,
429 CMP => 0,
430 MOD => 0,
431 MODRDN => 0,
432 DEL => 0,
433 };
434 }
435 else {
436 ### Entry exists, increment the CONNECT value
437 $hosts{$host}{CONNECT}++;
438 }
439
440 ### Create an array to store the hours
441 if ( !( defined $hours{$hour} ) ) {
442 $hours{$hour} = {
443 CONNECT => 1,
444 AUTHFAILURES => 0,
445 BIND => 0,
446 UNBIND => 0,
447 SRCH => 0,
448 ADD => 0,
449 CMP => 0,
450 MOD => 0,
451 MODRDN => 0,
452 DEL => 0,
453 };
454 }
455 else {
456 ### Entry exists, increment the CONNECT value
457 $hours{$hour}{CONNECT}++;
458 }
459
460 ### Create an array to store the months
461 if ( !( defined $months{$month} ) ) {
462 $months{$month} = {
463 CONNECT => 1,
464 AUTHFAILURES => 0,
465 BIND => 0,
466 UNBIND => 0,
467 SRCH => 0,
468 ADD => 0,
469 CMP => 0,
470 MOD => 0,
471 MODRDN => 0,
472 DEL => 0,
473 };
474 }
475 else {
476 ### Entry exists, increment the CONNECT value
477 $months{$month}{CONNECT}++;
478 }
479
480 ### Create an array to store the days
481 if ( !( defined $days{$day} ) ) {
482 $days{$day} = {
483 CONNECT => 1,
484 AUTHFAILURES => 0,
485 BIND => 0,
486 UNBIND => 0,
487 SRCH => 0,
488 ADD => 0,
489 CMP => 0,
490 MOD => 0,
491 MODRDN => 0,
492 DEL => 0,
493 };
494 }
495 else {
496 ### Entry exists, increment the CONNECT value
497 $days{$day}{CONNECT}++;
498 }
499
500 ### Add the host to the connection table
501 $conns{$conn} = $host;
502
503 ### Increment the total number of connections
504 $stats{TOTAL_CONNECT}++;
505
506 ### Check for anonymous binds
507 }
508 elsif ( $line =~
509/^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] BIND [ ] dn="" [ ] method=128/mx
510 )
511 {
512 my $month = $1;
513 my $day = $2;
514 my $hour = $3;
515 my $conn = $6;
516
517 ### Increment the counters
518 if ( defined $conns{$conn}
519 && defined $hosts{ $conns{$conn} } )
520 {
521 $hosts{ $conns{$conn} }{BIND}++;
522 $hours{$hour}{BIND}++;
523 $days{$day}{BIND}++;
524 $months{$month}{BIND}++;
525 $stats{TOTAL_BIND}++;
526 }
527
528 ### Add the binddn to the binddns array
529 $binddns{anonymous}++;
530
531 ### Check for non-anonymous binds
532 }
533 elsif ( $line =~
534/^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] BIND [ ] dn="([^"]+)" [ ] mech=/mx
535 )
536 {
537 my $month = $1;
538 my $day = $2;
539 my $hour = $3;
540 my $conn = $6;
541 my $binddn = lc $7;
542
543 ### Increment the counters
544 if ( defined $conns{$conn}
545 && defined $hosts{ $conns{$conn} } )
546 {
547 $hosts{ $conns{$conn} }{BIND}++;
548 $hours{$hour}{BIND}++;
549 $days{$day}{BIND}++;
550 $months{$month}{BIND}++;
551 $stats{TOTAL_BIND}++;
552 }
553
554 ### Add the binddn to the binddns array
555 $binddns{$binddn}++;
556
557 ### Check the search base
558 }
559 elsif ( $line =~
560/\bconn=\d+ [ ] op=\d+ [ ] SRCH [ ] base="([^"]*?)" [ ] .*filter="([^"]*?)"/mx
561 )
562 {
563 my $base = lc $1;
564 my $filter = $2;
565
566 ### Stuff the search base into an array
567 if ( defined $base ) {
568 $search{$base}++;
569 }
570
571 if ( defined $filter ) {
572 $filters{$filter}++;
573 }
574
575 ### Check for search attributes
576 }
577 elsif ( $line =~ /\bconn=\d+ [ ] op=\d+ [ ] SRCH [ ] attr=(.+)/mx ) {
578 my $attrs = lc $1;
579
580 if ($splitattrs) {
581 for my $attr ( split q{ }, $attrs ) {
582 $searchattributes{$attr}++;
583 }
584 }
585 else {
586 $searchattributes{$attrs}++;
587 }
588
589 ### Check for SEARCHES
590 }
591 elsif ( $line =~
592/^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] SEARCH [ ] RESULT/mx
593 )
594 {
595 my $month = $1;
596 my $day = $2;
597 my $hour = $3;
598 my $conn = $6;
599
600 ### Increment the counters
601 if ( defined $conns{$conn}
602 && defined $hosts{ $conns{$conn} } )
603 {
604 $hosts{ $conns{$conn} }{SRCH}++;
605 $hours{$hour}{SRCH}++;
606 $days{$day}{SRCH}++;
607 $months{$month}{SRCH}++;
608 $stats{TOTAL_SRCH}++;
609 }
610
611 ### Check for unbinds
612 }
613 elsif ( $line =~
614 /^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] UNBIND/mx
615 )
616 {
617 my $month = $1;
618 my $day = $2;
619 my $hour = $3;
620 my $conn = $6;
621
622 ### Increment the counters
623 if ( defined $conns{$conn}
624 && defined $hosts{ $conns{$conn} } )
625 {
626 $hosts{ $conns{$conn} }{UNBIND}++;
627 $hours{$hour}{UNBIND}++;
628 $days{$day}{UNBIND}++;
629 $months{$month}{UNBIND}++;
630 $stats{TOTAL_UNBIND}++;
631 }
632
633 ### Check the result of the last operation
634 ### TODO: Add other err=X values from contrib/ldapc++/src/LDAPResult.h
635 }
636 elsif ( $line =~
637/^$regex_date .*conn=(\d+) [ ] op=\d+(?: SEARCH)? [ ] RESULT [ ]/mx
638 )
639 {
640 my $month = $1;
641 my $day = $2;
642 my $hour = $3;
643 my $conn = $6;
644
645 if ( $line =~ /\berr=49\b/mx ) {
646 ### Increment the counters
647 if ( defined $conns{$conn}
648 && defined $hosts{ $conns{$conn} } )
649 {
650 $hosts{ $conns{$conn} }{AUTHFAILURES}++;
651 $hours{$hour}{AUTHFAILURES}++;
652 $days{$day}{AUTHFAILURES}++;
653 $months{$month}{AUTHFAILURES}++;
654 $stats{TOTAL_AUTHFAILURES}++;
655 }
656 }
657
658 ### Check for entry changes: add, modify modrdn, delete
659 }
660 elsif ( $line =~
661/^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] (ADD|CMP|MOD|MODRDN|DEL) [ ] dn=/mx
662 )
663 {
664 my $month = $1;
665 my $day = $2;
666 my $hour = $3;
667 my $conn = $6;
668 my $type = $7;
669
670 ### Increment the counters
671 if ( defined $conns{$conn}
672 && defined $hosts{ $conns{$conn} } )
673 {
674 $hosts{ $conns{$conn} }{$type}++;
675 $hours{$hour}{$type}++;
676 $days{$day}{$type}++;
677 $months{$month}{$type}++;
678 $stats{ 'TOTAL_' . $type }++;
679 }
680
681 ### Check for unindexed searches
682 }
683 elsif ( $line =~
684 /: [ ] \(([a-zA-Z0-9\;\-]+)\) [ ] index_param [ ] failed/mx )
685 {
686 my $attr = $1;
687
688 $unindexed{$attr}++;
689 $stats{TOTAL_UNINDEXED}++;
690 }
691 $counter++;
692 }
693 close LOGFILE;
694}
695
696###################################################################
697### Print a nice header with the logfiles and date ranges processed
698###################################################################
699## Please see file perltidy.ERR
700print "\n\n"
701 . "Report Generated on $date\n"
702 . q{-} x ( 20 + length $date ) . "\n";
703
704for my $logfile ( sort keys %logarray ) {
705 if ( !-z $logfile ) {
706 printf "Processed \"$logfile\": %s - %s\n", $logarray{$logfile}{SDATE},
707 $logarray{$logfile}{EDATE};
708 }
709 else {
710 printf "Processed \"$logfile\": no data\n";
711 }
712}
713
714#######################################
715### Print an overall report with totals
716#######################################
717
718my $total_operations =
719 $stats{TOTAL_BIND} + $stats{TOTAL_UNBIND} + $stats{TOTAL_SRCH} +
720 $stats{TOTAL_MOD} + $stats{TOTAL_ADD} + $stats{TOTAL_MODRDN} +
721 $stats{TOTAL_DEL};
722
723print "\n\n" . "Operation totals\n" . "----------------\n";
724printf "Total operations : %d\n", $total_operations;
725printf "Total connections : %d\n", $stats{TOTAL_CONNECT};
726printf "Total authentication failures : %d\n", $stats{TOTAL_AUTHFAILURES};
727printf "Total binds : %d\n", $stats{TOTAL_BIND};
728printf "Total unbinds : %d\n", $stats{TOTAL_UNBIND};
729printf "Total searches : %d\n", $stats{TOTAL_SRCH};
730printf "Total compares : %d\n", $stats{TOTAL_CMP};
731printf "Total modifications : %d\n", $stats{TOTAL_MOD};
732printf "Total modrdns : %d\n", $stats{TOTAL_MODRDN};
733printf "Total additions : %d\n", $stats{TOTAL_ADD};
734printf "Total deletions : %d\n", $stats{TOTAL_DEL};
735printf "Unindexed attribute requests : %d\n", $stats{TOTAL_UNINDEXED};
736printf "Operations per connection : %.2f\n",
737 $stats{TOTAL_CONNECT} ? $total_operations / $stats{TOTAL_CONNECT} : 0;
738
739###################################################
740### Process the host information and print a report
741###################################################
742for my $selected (@operations) {
743 $selected = uc $selected;
744
745 my $ops_ref = {
746 CONNECT => sub { $operations{CONNECT}{DATA} = 1 },
747 FAILURES => sub { $operations{FAILURES}{DATA} = 1 },
748 BIND => sub { $operations{BIND}{DATA} = 1 },
749 UNBIND => sub { $operations{UNBIND}{DATA} = 1 },
750 SRCH => sub { $operations{SRCH}{DATA} = 1 },
751 CMP => sub { $operations{CMP}{DATA} = 1 },
752 ADD => sub { $operations{ADD}{DATA} = 1 },
753 MOD => sub { $operations{MOD}{DATA} = 1 },
754 MODRDN => sub { $operations{MODRDN}{DATA} = 1 },
755 DEL => sub { $operations{DEL}{DATA} = 1 },
756 ALL => sub {
757 $operations{CONNECT}{DATA} = 1;
758 $operations{FAILURES}{DATA} = 1;
759 $operations{BIND}{DATA} = 1;
760 $operations{UNBIND}{DATA} = 1;
761 $operations{SRCH}{DATA} = 1;
762 $operations{CMP}{DATA} = 1;
763 $operations{ADD}{DATA} = 1;
764 $operations{MOD}{DATA} = 1;
765 $operations{MODRDN}{DATA} = 1;
766 $operations{DEL}{DATA} = 1;
767 },
768 READ => sub {
769 $operations{CONNECT}{DATA} = 1;
770 $operations{BIND}{DATA} = 1;
771 $operations{UNBIND}{DATA} = 1;
772 $operations{SRCH}{DATA} = 1;
773 $operations{CMP}{DATA} = 1;
774 },
775 WRITE => sub {
776 $operations{CONNECT}{DATA} = 1;
777 $operations{BIND}{DATA} = 1;
778 $operations{UNBIND}{DATA} = 1;
779 $operations{ADD}{DATA} = 1;
780 $operations{MOD}{DATA} = 1;
781 $operations{MODRDN}{DATA} = 1;
782 $operations{DEL}{DATA} = 1;
783 },
784 };
785 if ( $ops_ref->{$selected} ) { $ops_ref->{$selected}->() }
786 else { croak "Unknown operation: '$selected';\n" }
787}
788
789print "\n\n";
790my $printstr = 'Hostname ';
791$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{STRING} : q{};
792$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
793$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{STRING} : q{};
794$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{STRING} : q{};
795$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{STRING} : q{};
796$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{STRING} : q{};
797$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{STRING} : q{};
798$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{STRING} : q{};
799$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{STRING} : q{};
800$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{STRING} : q{};
801$printstr .= "\n";
802print $printstr;
803$printstr = '---------------';
804$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{SPACING} : q{};
805$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
806$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{SPACING} : q{};
807$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{SPACING} : q{};
808$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{SPACING} : q{};
809$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{SPACING} : q{};
810$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{SPACING} : q{};
811$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{SPACING} : q{};
812$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{SPACING} : q{};
813$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{SPACING} : q{};
814print "$printstr\n";
815
816for my $index ( sort keys %hosts ) {
817
818 ### Resolve IP addresses to names if requested
819 my $host = $index;
820
821 ### Convert the IP address to an Internet address, and resolve with gethostbyaddr()
822 if ( $resolvename && ( $index =~ /\d+\.\d+\.\d+\.\d+/mx ) ) {
823 my $ipaddr = inet_aton($index);
824 $host = gethostbyaddr $ipaddr, AF_INET;
825 if ( !defined $host ) {
826 $host = $index;
827 }
828 }
829 printf '%-15.15s', $host;
830 if ( $operations{CONNECT}{DATA} ) {
831 printf " $operations{CONNECT}{FIELD}",
832 $hosts{$index}{CONNECT} ? $hosts{$index}{CONNECT} : 0;
833 }
834 if ( $operations{FAILURES}{DATA} ) {
835 printf " $operations{FAILURES}{FIELD}",
836 $hosts{$index}{AUTHFAILURES} ? $hosts{$index}{AUTHFAILURES} : 0;
837 }
838 if ( $operations{BIND}{DATA} ) {
839 printf " $operations{BIND}{FIELD}",
840 $hosts{$index}{BIND} ? $hosts{$index}{BIND} : 0;
841 }
842 if ( $operations{UNBIND}{DATA} ) {
843 printf " $operations{UNBIND}{FIELD}",
844 $hosts{$index}{UNBIND} ? $hosts{$index}{UNBIND} : 0;
845 }
846 if ( $operations{SRCH}{DATA} ) {
847 printf " $operations{SRCH}{FIELD}",
848 $hosts{$index}{SRCH} ? $hosts{$index}{SRCH} : 0;
849 }
850 if ( $operations{CMP}{DATA} ) {
851 printf " $operations{CMP}{FIELD}",
852 $hosts{$index}{CMP} ? $hosts{$index}{CMP} : 0;
853 }
854 if ( $operations{ADD}{DATA} ) {
855 printf " $operations{ADD}{FIELD}",
856 $hosts{$index}{ADD} ? $hosts{$index}{ADD} : 0;
857 }
858 if ( $operations{MOD}{DATA} ) {
859 printf " $operations{MOD}{FIELD}",
860 $hosts{$index}{MOD} ? $hosts{$index}{MOD} : 0;
861 }
862 if ( $operations{MODRDN}{DATA} ) {
863 printf " $operations{MODRDN}{FIELD}",
864 $hosts{$index}{MODRDN} ? $hosts{$index}{MODRDN} : 0;
865 }
866 if ( $operations{DEL}{DATA} ) {
867 printf " $operations{DEL}{FIELD}",
868 $hosts{$index}{DEL} ? $hosts{$index}{DEL} : 0;
869 }
870 print "\n";
871}
872
873#######################################################
874### Process the hours information and print a report
875########################################################
876print "\n\n";
877$printstr = 'Hour of Day ';
878$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{STRING} : q{};
879$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
880$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{STRING} : q{};
881$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{STRING} : q{};
882$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{STRING} : q{};
883$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{STRING} : q{};
884$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{STRING} : q{};
885$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{STRING} : q{};
886$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{STRING} : q{};
887$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{STRING} : q{};
888$printstr .= "\n";
889print $printstr;
890$printstr = '-------------';
891$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{SPACING} : q{};
892$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
893$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{SPACING} : q{};
894$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{SPACING} : q{};
895$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{SPACING} : q{};
896$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{SPACING} : q{};
897$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{SPACING} : q{};
898$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{SPACING} : q{};
899$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{SPACING} : q{};
900$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{SPACING} : q{};
901print "$printstr\n";
902
903for my $index ( sort keys %hours ) {
904 printf '%-2s:00 - %2s:59', $index, $index;
905 if ( $operations{CONNECT}{DATA} ) {
906 printf " $operations{CONNECT}{FIELD}",
907 $hours{$index}{CONNECT} ? $hours{$index}{CONNECT} : 0;
908 }
909 if ( $operations{FAILURES}{DATA} ) {
910 printf " $operations{FAILURES}{FIELD}",
911 $hours{$index}{AUTHFAILURES} ? $hours{$index}{AUTHFAILURES} : 0;
912 }
913 if ( $operations{BIND}{DATA} ) {
914 printf " $operations{BIND}{FIELD}",
915 $hours{$index}{BIND} ? $hours{$index}{BIND} : 0;
916 }
917 if ( $operations{UNBIND}{DATA} ) {
918 printf " $operations{UNBIND}{FIELD}",
919 $hours{$index}{UNBIND} ? $hours{$index}{UNBIND} : 0;
920 }
921 if ( $operations{SRCH}{DATA} ) {
922 printf " $operations{SRCH}{FIELD}",
923 $hours{$index}{SRCH} ? $hours{$index}{SRCH} : 0;
924 }
925 if ( $operations{CMP}{DATA} ) {
926 printf " $operations{CMP}{FIELD}",
927 $hours{$index}{CMP} ? $hours{$index}{CMP} : 0;
928 }
929 if ( $operations{ADD}{DATA} ) {
930 printf " $operations{ADD}{FIELD}",
931 $hours{$index}{ADD} ? $hours{$index}{ADD} : 0;
932 }
933 if ( $operations{MOD}{DATA} ) {
934 printf " $operations{MOD}{FIELD}",
935 $hours{$index}{MOD} ? $hours{$index}{MOD} : 0;
936 }
937 if ( $operations{MODRDN}{DATA} ) {
938 printf " $operations{MODRDN}{FIELD}",
939 $hours{$index}{MODRDN} ? $hours{$index}{MODRDN} : 0;
940 }
941 if ( $operations{DEL}{DATA} ) {
942 printf " $operations{DEL}{FIELD}",
943 $hours{$index}{DEL} ? $hours{$index}{DEL} : 0;
944 }
945 print "\n";
946}
947
948#######################################################
949### Process the month information and print a report
950########################################################
951print "\n\n";
952$printstr = 'Day of Month ';
953$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{STRING} : q{};
954$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
955$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{STRING} : q{};
956$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{STRING} : q{};
957$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{STRING} : q{};
958$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{STRING} : q{};
959$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{STRING} : q{};
960$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{STRING} : q{};
961$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{STRING} : q{};
962$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{STRING} : q{};
963$printstr .= "\n";
964print $printstr;
965$printstr = '-------------';
966$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{SPACING} : q{};
967$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
968$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{SPACING} : q{};
969$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{SPACING} : q{};
970$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{SPACING} : q{};
971$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{SPACING} : q{};
972$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{SPACING} : q{};
973$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{SPACING} : q{};
974$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{SPACING} : q{};
975$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{SPACING} : q{};
976print "$printstr\n";
977
978for ( 1 .. 31 ) {
979 if ( defined $days{$_} || $printdays ) {
980 printf ' %-11s', $_;
981 if ( $operations{CONNECT}{DATA} ) {
982 printf " $operations{CONNECT}{FIELD}",
983 $days{$_}{CONNECT} ? $days{$_}{CONNECT} : 0;
984 }
985 if ( $operations{FAILURES}{DATA} ) {
986 printf " $operations{FAILURES}{FIELD}",
987 $days{$_}{AUTHFAILURES} ? $days{$_}{AUTHFAILURES} : 0;
988 }
989 if ( $operations{BIND}{DATA} ) {
990 printf " $operations{BIND}{FIELD}",
991 $days{$_}{BIND} ? $days{$_}{BIND} : 0;
992 }
993 if ( $operations{UNBIND}{DATA} ) {
994 printf " $operations{UNBIND}{FIELD}",
995 $days{$_}{UNBIND} ? $days{$_}{UNBIND} : 0;
996 }
997 if ( $operations{SRCH}{DATA} ) {
998 printf " $operations{SRCH}{FIELD}",
999 $days{$_}{SRCH} ? $days{$_}{SRCH} : 0;
1000 }
1001 if ( $operations{CMP}{DATA} ) {
1002 printf " $operations{CMP}{FIELD}",
1003 $days{$_}{CMP} ? $days{$_}{CMP} : 0;
1004 }
1005 if ( $operations{ADD}{DATA} ) {
1006 printf " $operations{ADD}{FIELD}",
1007 $days{$_}{ADD} ? $days{$_}{ADD} : 0;
1008 }
1009 if ( $operations{MOD}{DATA} ) {
1010 printf " $operations{MOD}{FIELD}",
1011 $days{$_}{MOD} ? $days{$_}{MOD} : 0;
1012 }
1013 if ( $operations{MODRDN}{DATA} ) {
1014 printf " $operations{MODRDN}{FIELD}",
1015 $days{$_}{MODRDN} ? $days{$_}{MODRDN} : 0;
1016 }
1017 if ( $operations{DEL}{DATA} ) {
1018 printf " $operations{DEL}{FIELD}",
1019 $days{$_}{DEL} ? $days{$_}{DEL} : 0;
1020 }
1021 print "\n";
1022 }
1023}
1024#######################################################
1025### Process the month information and print a report
1026########################################################
1027print "\n\n";
1028$printstr = ' Month ';
1029$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{STRING} : q{};
1030$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
1031$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{STRING} : q{};
1032$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{STRING} : q{};
1033$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{STRING} : q{};
1034$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{STRING} : q{};
1035$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{STRING} : q{};
1036$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{STRING} : q{};
1037$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{STRING} : q{};
1038$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{STRING} : q{};
1039$printstr .= "\n";
1040print $printstr;
1041$printstr = '-------------';
1042$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{SPACING} : q{};
1043$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
1044$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{SPACING} : q{};
1045$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{SPACING} : q{};
1046$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{SPACING} : q{};
1047$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{SPACING} : q{};
1048$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{SPACING} : q{};
1049$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{SPACING} : q{};
1050$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{SPACING} : q{};
1051$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{SPACING} : q{};
1052print "$printstr\n";
1053
1054for my $index qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) {
1055 if ( defined $months{$index} || $printmonths ) {
1056 printf ' %-11s', $index;
1057 if ( $operations{CONNECT}{DATA} ) {
1058 printf " $operations{CONNECT}{FIELD}",
1059 $months{$index}{CONNECT} ? $months{$index}{CONNECT} : 0;
1060 }
1061 if ( $operations{FAILURES}{DATA} ) {
1062 printf " $operations{FAILURES}{FIELD}",
1063 $months{$index}{AUTHFAILURES}
1064 ? $months{$index}{AUTHFAILURES}
1065 : 0;
1066 }
1067 if ( $operations{BIND}{DATA} ) {
1068 printf " $operations{BIND}{FIELD}",
1069 $months{$index}{BIND} ? $months{$index}{BIND} : 0;
1070 }
1071 if ( $operations{UNBIND}{DATA} ) {
1072 printf " $operations{UNBIND}{FIELD}",
1073 $months{$index}{UNBIND} ? $months{$index}{UNBIND} : 0;
1074 }
1075 if ( $operations{SRCH}{DATA} ) {
1076 printf " $operations{SRCH}{FIELD}",
1077 $months{$index}{SRCH} ? $months{$index}{SRCH} : 0;
1078 }
1079 if ( $operations{CMP}{DATA} ) {
1080 printf " $operations{CMP}{FIELD}",
1081 $months{$index}{CMP} ? $months{$index}{CMP} : 0;
1082 }
1083 if ( $operations{ADD}{DATA} ) {
1084 printf " $operations{ADD}{FIELD}",
1085 $months{$index}{ADD} ? $months{$index}{ADD} : 0;
1086 }
1087 if ( $operations{MOD}{DATA} ) {
1088 printf " $operations{MOD}{FIELD}",
1089 $months{$index}{MOD} ? $months{$index}{MOD} : 0;
1090 }
1091 if ( $operations{MODRDN}{DATA} ) {
1092 printf " $operations{MODRDN}{FIELD}",
1093 $months{$index}{MODRDN} ? $months{$index}{MODRDN} : 0;
1094 }
1095 if ( $operations{DEL}{DATA} ) {
1096 printf " $operations{DEL}{FIELD}",
1097 $months{$index}{DEL} ? $months{$index}{DEL} : 0;
1098 }
1099 print "\n";
1100 }
1101}
1102
1103####################################################
1104### Process the unindexed searches and print a report
1105####################################################
1106my @sarray; # sort array
1107if ( $stats{TOTAL_UNINDEXED} > 0 ) {
1108
1109 print "\n\n"
1110 . "# Uses Unindexed attribute\n"
1111 . "---------- -----------------------------------------------------------\n";
1112
1113 @sarray =
1114 reverse sort { $unindexed{$a} <=> $unindexed{$b} } keys %unindexed;
1115 UNINDEXED:
1116 for my $num ( 0 .. $#sarray ) {
1117 if ( $num > $count ) {
1118 last UNINDEXED;
1119 }
1120 printf " %-8d %-60s\n", $unindexed{ $sarray[$num] }, $sarray[$num];
1121 }
1122}
1123
1124######################################################
1125### Process the stored search bases and print a report
1126######################################################
1127print "\n\n"
1128 . "# Searches Search base\n"
1129 . "---------- -----------------------------------------------------------\n";
1130
1131@sarray = reverse sort { $search{$a} <=> $search{$b} } keys %search;
1132SEARCH:
1133for my $num ( 0 .. $#sarray ) {
1134 if ( $num > $count ) {
1135 last SEARCH;
1136 }
1137 printf " %-8d %-60s\n", $search{ $sarray[$num] },
1138 $sarray[$num] || 'RootDSE';
1139}
1140
1141######################################################
1142### Process the stored search filters
1143######################################################
1144print "\n\n"
1145 . "# Uses Filter\n"
1146 . "---------- -----------------------------------------------------------\n";
1147
1148@sarray = reverse sort { $filters{$a} <=> $filters{$b} } keys %filters;
1149FILTER:
1150for my $num ( 0 .. $#sarray ) {
1151 if ( $num > $count ) {
1152 last FILTER;
1153 }
1154 printf " %-8d %-60s\n", $filters{ $sarray[$num] }, $sarray[$num];
1155}
1156
1157######################################################
1158### Process the stored attribute array
1159######################################################
1160print "\n\n"
1161 . "# Uses Attributes explicitly requested in search string\n"
1162 . "---------- -------------------------------------------------\n";
1163
1164@sarray =
1165 reverse sort { $searchattributes{$a} <=> $searchattributes{$b} }
1166 keys %searchattributes;
1167SEARCHATTR:
1168for my $num ( 0 .. $#sarray ) {
1169 if ( $num > $count ) {
1170 last SEARCHATTR;
1171 }
1172 printf " %-8d %-60s\n", $searchattributes{ $sarray[$num] },
1173 $sarray[$num];
1174}
1175
1176######################################################
1177### Process the stored binddns and print a report
1178######################################################
1179print "\n\n"
1180 . "# Binds Bind DN\n"
1181 . "---------- --------------------------------------------------------------\n";
1182
1183@sarray = reverse sort { $binddns{$a} <=> $binddns{$b} } keys %binddns;
1184BINDDN:
1185for my $num ( 0 .. $#sarray ) {
1186 if ( $num > $count ) {
1187 last BINDDN;
1188 }
1189 printf " %-8d %-60s\n", $binddns{ $sarray[$num] }, $sarray[$num];
1190}
1191
1192print "\n\n";
1193
1194# EOF
Note: See TracBrowser for help on using the repository browser.