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

Last change on this file since 1145 was 1145, checked in by joergs, on Apr 17, 2013 at 1:03:03 PM

initial, multi date formats

  • 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 print "regex: $regex\n";
377 if ( $line =~ /^($regex)/mx ) {
378 $logarray{$logfile}{SDATE} = $1;
379 $regex_date = $regex;
380 last;
381 }
382 }
383 if( ! $regex_date ) {
384 print "ERROR: unable to determine date format in '$logfile'\n";
385 next FILE;
386 }
387 }
388
389 ### check start and end dates
390 if ( $line =~ /^($regex_date)/mx ) {
391 $logarray{$logfile}{EDATE} = $1;
392 }
393
394 ### Check to see if we have processed $lines lines
395 if ( ( $lines > 0 ) && ( $counter == $lines ) ) {
396 print " Processed $lines lines in \"$logfile\"\n";
397 $lines += $increment;
398 }
399
400 ### Check for a new connection
401 if ( $line =~
402/^$regex_date .*conn=(\d+) [ ] fd=\d+ [ ] (?:ACCEPT|connection) [ ] from/mx
403 )
404 {
405 my $month = $1;
406 my $day = $2;
407 my $hour = $3;
408 my $conn = $6;
409 my $host;
410
411 if ( $line =~ /IP=(\d+\.\d+\.\d+\.\d+):/mx ) {
412 $host = $1;
413 }
414 elsif ( $line =~ /PATH=(\S+)/mx ) {
415 $host = 'LOCAL-SOCKET';
416 }
417 else {
418 $host = 'UNKNOWN';
419 }
420
421 ### Create an array to store the list of hosts
422 if ( !( defined $hosts{$host} ) ) {
423 $hosts{$host} = {
424 CONNECT => 1,
425 AUTHFAILURES => 0,
426 BIND => 0,
427 UNBIND => 0,
428 SRCH => 0,
429 ADD => 0,
430 CMP => 0,
431 MOD => 0,
432 MODRDN => 0,
433 DEL => 0,
434 };
435 }
436 else {
437 ### Entry exists, increment the CONNECT value
438 $hosts{$host}{CONNECT}++;
439 }
440
441 ### Create an array to store the hours
442 if ( !( defined $hours{$hour} ) ) {
443 $hours{$hour} = {
444 CONNECT => 1,
445 AUTHFAILURES => 0,
446 BIND => 0,
447 UNBIND => 0,
448 SRCH => 0,
449 ADD => 0,
450 CMP => 0,
451 MOD => 0,
452 MODRDN => 0,
453 DEL => 0,
454 };
455 }
456 else {
457 ### Entry exists, increment the CONNECT value
458 $hours{$hour}{CONNECT}++;
459 }
460
461 ### Create an array to store the months
462 if ( !( defined $months{$month} ) ) {
463 $months{$month} = {
464 CONNECT => 1,
465 AUTHFAILURES => 0,
466 BIND => 0,
467 UNBIND => 0,
468 SRCH => 0,
469 ADD => 0,
470 CMP => 0,
471 MOD => 0,
472 MODRDN => 0,
473 DEL => 0,
474 };
475 }
476 else {
477 ### Entry exists, increment the CONNECT value
478 $months{$month}{CONNECT}++;
479 }
480
481 ### Create an array to store the days
482 if ( !( defined $days{$day} ) ) {
483 $days{$day} = {
484 CONNECT => 1,
485 AUTHFAILURES => 0,
486 BIND => 0,
487 UNBIND => 0,
488 SRCH => 0,
489 ADD => 0,
490 CMP => 0,
491 MOD => 0,
492 MODRDN => 0,
493 DEL => 0,
494 };
495 }
496 else {
497 ### Entry exists, increment the CONNECT value
498 $days{$day}{CONNECT}++;
499 }
500
501 ### Add the host to the connection table
502 $conns{$conn} = $host;
503
504 ### Increment the total number of connections
505 $stats{TOTAL_CONNECT}++;
506
507 ### Check for anonymous binds
508 }
509 elsif ( $line =~
510/^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] BIND [ ] dn="" [ ] method=128/mx
511 )
512 {
513 my $month = $1;
514 my $day = $2;
515 my $hour = $3;
516 my $conn = $6;
517
518 ### Increment the counters
519 if ( defined $conns{$conn}
520 && defined $hosts{ $conns{$conn} } )
521 {
522 $hosts{ $conns{$conn} }{BIND}++;
523 $hours{$hour}{BIND}++;
524 $days{$day}{BIND}++;
525 $months{$month}{BIND}++;
526 $stats{TOTAL_BIND}++;
527 }
528
529 ### Add the binddn to the binddns array
530 $binddns{anonymous}++;
531
532 ### Check for non-anonymous binds
533 }
534 elsif ( $line =~
535/^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] BIND [ ] dn="([^"]+)" [ ] mech=/mx
536 )
537 {
538 my $month = $1;
539 my $day = $2;
540 my $hour = $3;
541 my $conn = $6;
542 my $binddn = lc $7;
543
544 ### Increment the counters
545 if ( defined $conns{$conn}
546 && defined $hosts{ $conns{$conn} } )
547 {
548 $hosts{ $conns{$conn} }{BIND}++;
549 $hours{$hour}{BIND}++;
550 $days{$day}{BIND}++;
551 $months{$month}{BIND}++;
552 $stats{TOTAL_BIND}++;
553 }
554
555 ### Add the binddn to the binddns array
556 $binddns{$binddn}++;
557
558 ### Check the search base
559 }
560 elsif ( $line =~
561/\bconn=\d+ [ ] op=\d+ [ ] SRCH [ ] base="([^"]*?)" [ ] .*filter="([^"]*?)"/mx
562 )
563 {
564 my $base = lc $1;
565 my $filter = $2;
566
567 ### Stuff the search base into an array
568 if ( defined $base ) {
569 $search{$base}++;
570 }
571
572 if ( defined $filter ) {
573 $filters{$filter}++;
574 }
575
576 ### Check for search attributes
577 }
578 elsif ( $line =~ /\bconn=\d+ [ ] op=\d+ [ ] SRCH [ ] attr=(.+)/mx ) {
579 my $attrs = lc $1;
580
581 if ($splitattrs) {
582 for my $attr ( split q{ }, $attrs ) {
583 $searchattributes{$attr}++;
584 }
585 }
586 else {
587 $searchattributes{$attrs}++;
588 }
589
590 ### Check for SEARCHES
591 }
592 elsif ( $line =~
593/^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] SEARCH [ ] RESULT/mx
594 )
595 {
596 my $month = $1;
597 my $day = $2;
598 my $hour = $3;
599 my $conn = $6;
600
601 ### Increment the counters
602 if ( defined $conns{$conn}
603 && defined $hosts{ $conns{$conn} } )
604 {
605 $hosts{ $conns{$conn} }{SRCH}++;
606 $hours{$hour}{SRCH}++;
607 $days{$day}{SRCH}++;
608 $months{$month}{SRCH}++;
609 $stats{TOTAL_SRCH}++;
610 }
611
612 ### Check for unbinds
613 }
614 elsif ( $line =~
615 /^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] UNBIND/mx
616 )
617 {
618 my $month = $1;
619 my $day = $2;
620 my $hour = $3;
621 my $conn = $6;
622
623 ### Increment the counters
624 if ( defined $conns{$conn}
625 && defined $hosts{ $conns{$conn} } )
626 {
627 $hosts{ $conns{$conn} }{UNBIND}++;
628 $hours{$hour}{UNBIND}++;
629 $days{$day}{UNBIND}++;
630 $months{$month}{UNBIND}++;
631 $stats{TOTAL_UNBIND}++;
632 }
633
634 ### Check the result of the last operation
635 ### TODO: Add other err=X values from contrib/ldapc++/src/LDAPResult.h
636 }
637 elsif ( $line =~
638/^$regex_date .*conn=(\d+) [ ] op=\d+(?: SEARCH)? [ ] RESULT [ ]/mx
639 )
640 {
641 my $month = $1;
642 my $day = $2;
643 my $hour = $3;
644 my $conn = $6;
645
646 if ( $line =~ /\berr=49\b/mx ) {
647 ### Increment the counters
648 if ( defined $conns{$conn}
649 && defined $hosts{ $conns{$conn} } )
650 {
651 $hosts{ $conns{$conn} }{AUTHFAILURES}++;
652 $hours{$hour}{AUTHFAILURES}++;
653 $days{$day}{AUTHFAILURES}++;
654 $months{$month}{AUTHFAILURES}++;
655 $stats{TOTAL_AUTHFAILURES}++;
656 }
657 }
658
659 ### Check for entry changes: add, modify modrdn, delete
660 }
661 elsif ( $line =~
662/^$regex_date .*conn=(\d+) [ ] op=\d+ [ ] (ADD|CMP|MOD|MODRDN|DEL) [ ] dn=/mx
663 )
664 {
665 my $month = $1;
666 my $day = $2;
667 my $hour = $3;
668 my $conn = $6;
669 my $type = $7;
670
671 ### Increment the counters
672 if ( defined $conns{$conn}
673 && defined $hosts{ $conns{$conn} } )
674 {
675 $hosts{ $conns{$conn} }{$type}++;
676 $hours{$hour}{$type}++;
677 $days{$day}{$type}++;
678 $months{$month}{$type}++;
679 $stats{ 'TOTAL_' . $type }++;
680 }
681
682 ### Check for unindexed searches
683 }
684 elsif ( $line =~
685 /: [ ] \(([a-zA-Z0-9\;\-]+)\) [ ] index_param [ ] failed/mx )
686 {
687 my $attr = $1;
688
689 $unindexed{$attr}++;
690 $stats{TOTAL_UNINDEXED}++;
691 }
692 $counter++;
693 }
694 close LOGFILE;
695}
696
697###################################################################
698### Print a nice header with the logfiles and date ranges processed
699###################################################################
700## Please see file perltidy.ERR
701print "\n\n"
702 . "Report Generated on $date\n"
703 . q{-} x ( 20 + length $date ) . "\n";
704
705for my $logfile ( sort keys %logarray ) {
706 if ( !-z $logfile ) {
707 printf "Processed \"$logfile\": %s - %s\n", $logarray{$logfile}{SDATE},
708 $logarray{$logfile}{EDATE};
709 }
710 else {
711 printf "Processed \"$logfile\": no data\n";
712 }
713}
714
715#######################################
716### Print an overall report with totals
717#######################################
718
719my $total_operations =
720 $stats{TOTAL_BIND} + $stats{TOTAL_UNBIND} + $stats{TOTAL_SRCH} +
721 $stats{TOTAL_MOD} + $stats{TOTAL_ADD} + $stats{TOTAL_MODRDN} +
722 $stats{TOTAL_DEL};
723
724print "\n\n" . "Operation totals\n" . "----------------\n";
725printf "Total operations : %d\n", $total_operations;
726printf "Total connections : %d\n", $stats{TOTAL_CONNECT};
727printf "Total authentication failures : %d\n", $stats{TOTAL_AUTHFAILURES};
728printf "Total binds : %d\n", $stats{TOTAL_BIND};
729printf "Total unbinds : %d\n", $stats{TOTAL_UNBIND};
730printf "Total searches : %d\n", $stats{TOTAL_SRCH};
731printf "Total compares : %d\n", $stats{TOTAL_CMP};
732printf "Total modifications : %d\n", $stats{TOTAL_MOD};
733printf "Total modrdns : %d\n", $stats{TOTAL_MODRDN};
734printf "Total additions : %d\n", $stats{TOTAL_ADD};
735printf "Total deletions : %d\n", $stats{TOTAL_DEL};
736printf "Unindexed attribute requests : %d\n", $stats{TOTAL_UNINDEXED};
737printf "Operations per connection : %.2f\n",
738 $stats{TOTAL_CONNECT} ? $total_operations / $stats{TOTAL_CONNECT} : 0;
739
740###################################################
741### Process the host information and print a report
742###################################################
743for my $selected (@operations) {
744 $selected = uc $selected;
745
746 my $ops_ref = {
747 CONNECT => sub { $operations{CONNECT}{DATA} = 1 },
748 FAILURES => sub { $operations{FAILURES}{DATA} = 1 },
749 BIND => sub { $operations{BIND}{DATA} = 1 },
750 UNBIND => sub { $operations{UNBIND}{DATA} = 1 },
751 SRCH => sub { $operations{SRCH}{DATA} = 1 },
752 CMP => sub { $operations{CMP}{DATA} = 1 },
753 ADD => sub { $operations{ADD}{DATA} = 1 },
754 MOD => sub { $operations{MOD}{DATA} = 1 },
755 MODRDN => sub { $operations{MODRDN}{DATA} = 1 },
756 DEL => sub { $operations{DEL}{DATA} = 1 },
757 ALL => sub {
758 $operations{CONNECT}{DATA} = 1;
759 $operations{FAILURES}{DATA} = 1;
760 $operations{BIND}{DATA} = 1;
761 $operations{UNBIND}{DATA} = 1;
762 $operations{SRCH}{DATA} = 1;
763 $operations{CMP}{DATA} = 1;
764 $operations{ADD}{DATA} = 1;
765 $operations{MOD}{DATA} = 1;
766 $operations{MODRDN}{DATA} = 1;
767 $operations{DEL}{DATA} = 1;
768 },
769 READ => sub {
770 $operations{CONNECT}{DATA} = 1;
771 $operations{BIND}{DATA} = 1;
772 $operations{UNBIND}{DATA} = 1;
773 $operations{SRCH}{DATA} = 1;
774 $operations{CMP}{DATA} = 1;
775 },
776 WRITE => sub {
777 $operations{CONNECT}{DATA} = 1;
778 $operations{BIND}{DATA} = 1;
779 $operations{UNBIND}{DATA} = 1;
780 $operations{ADD}{DATA} = 1;
781 $operations{MOD}{DATA} = 1;
782 $operations{MODRDN}{DATA} = 1;
783 $operations{DEL}{DATA} = 1;
784 },
785 };
786 if ( $ops_ref->{$selected} ) { $ops_ref->{$selected}->() }
787 else { croak "Unknown operation: '$selected';\n" }
788}
789
790print "\n\n";
791my $printstr = 'Hostname ';
792$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{STRING} : q{};
793$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
794$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{STRING} : q{};
795$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{STRING} : q{};
796$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{STRING} : q{};
797$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{STRING} : q{};
798$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{STRING} : q{};
799$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{STRING} : q{};
800$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{STRING} : q{};
801$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{STRING} : q{};
802$printstr .= "\n";
803print $printstr;
804$printstr = '---------------';
805$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{SPACING} : q{};
806$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
807$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{SPACING} : q{};
808$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{SPACING} : q{};
809$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{SPACING} : q{};
810$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{SPACING} : q{};
811$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{SPACING} : q{};
812$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{SPACING} : q{};
813$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{SPACING} : q{};
814$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{SPACING} : q{};
815print "$printstr\n";
816
817for my $index ( sort keys %hosts ) {
818
819 ### Resolve IP addresses to names if requested
820 my $host = $index;
821
822 ### Convert the IP address to an Internet address, and resolve with gethostbyaddr()
823 if ( $resolvename && ( $index =~ /\d+\.\d+\.\d+\.\d+/mx ) ) {
824 my $ipaddr = inet_aton($index);
825 $host = gethostbyaddr $ipaddr, AF_INET;
826 if ( !defined $host ) {
827 $host = $index;
828 }
829 }
830 printf '%-15.15s', $host;
831 if ( $operations{CONNECT}{DATA} ) {
832 printf " $operations{CONNECT}{FIELD}",
833 $hosts{$index}{CONNECT} ? $hosts{$index}{CONNECT} : 0;
834 }
835 if ( $operations{FAILURES}{DATA} ) {
836 printf " $operations{FAILURES}{FIELD}",
837 $hosts{$index}{AUTHFAILURES} ? $hosts{$index}{AUTHFAILURES} : 0;
838 }
839 if ( $operations{BIND}{DATA} ) {
840 printf " $operations{BIND}{FIELD}",
841 $hosts{$index}{BIND} ? $hosts{$index}{BIND} : 0;
842 }
843 if ( $operations{UNBIND}{DATA} ) {
844 printf " $operations{UNBIND}{FIELD}",
845 $hosts{$index}{UNBIND} ? $hosts{$index}{UNBIND} : 0;
846 }
847 if ( $operations{SRCH}{DATA} ) {
848 printf " $operations{SRCH}{FIELD}",
849 $hosts{$index}{SRCH} ? $hosts{$index}{SRCH} : 0;
850 }
851 if ( $operations{CMP}{DATA} ) {
852 printf " $operations{CMP}{FIELD}",
853 $hosts{$index}{CMP} ? $hosts{$index}{CMP} : 0;
854 }
855 if ( $operations{ADD}{DATA} ) {
856 printf " $operations{ADD}{FIELD}",
857 $hosts{$index}{ADD} ? $hosts{$index}{ADD} : 0;
858 }
859 if ( $operations{MOD}{DATA} ) {
860 printf " $operations{MOD}{FIELD}",
861 $hosts{$index}{MOD} ? $hosts{$index}{MOD} : 0;
862 }
863 if ( $operations{MODRDN}{DATA} ) {
864 printf " $operations{MODRDN}{FIELD}",
865 $hosts{$index}{MODRDN} ? $hosts{$index}{MODRDN} : 0;
866 }
867 if ( $operations{DEL}{DATA} ) {
868 printf " $operations{DEL}{FIELD}",
869 $hosts{$index}{DEL} ? $hosts{$index}{DEL} : 0;
870 }
871 print "\n";
872}
873
874#######################################################
875### Process the hours information and print a report
876########################################################
877print "\n\n";
878$printstr = 'Hour of Day ';
879$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{STRING} : q{};
880$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
881$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{STRING} : q{};
882$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{STRING} : q{};
883$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{STRING} : q{};
884$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{STRING} : q{};
885$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{STRING} : q{};
886$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{STRING} : q{};
887$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{STRING} : q{};
888$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{STRING} : q{};
889$printstr .= "\n";
890print $printstr;
891$printstr = '-------------';
892$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{SPACING} : q{};
893$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
894$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{SPACING} : q{};
895$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{SPACING} : q{};
896$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{SPACING} : q{};
897$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{SPACING} : q{};
898$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{SPACING} : q{};
899$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{SPACING} : q{};
900$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{SPACING} : q{};
901$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{SPACING} : q{};
902print "$printstr\n";
903
904for my $index ( sort keys %hours ) {
905 printf '%-2s:00 - %2s:59', $index, $index;
906 if ( $operations{CONNECT}{DATA} ) {
907 printf " $operations{CONNECT}{FIELD}",
908 $hours{$index}{CONNECT} ? $hours{$index}{CONNECT} : 0;
909 }
910 if ( $operations{FAILURES}{DATA} ) {
911 printf " $operations{FAILURES}{FIELD}",
912 $hours{$index}{AUTHFAILURES} ? $hours{$index}{AUTHFAILURES} : 0;
913 }
914 if ( $operations{BIND}{DATA} ) {
915 printf " $operations{BIND}{FIELD}",
916 $hours{$index}{BIND} ? $hours{$index}{BIND} : 0;
917 }
918 if ( $operations{UNBIND}{DATA} ) {
919 printf " $operations{UNBIND}{FIELD}",
920 $hours{$index}{UNBIND} ? $hours{$index}{UNBIND} : 0;
921 }
922 if ( $operations{SRCH}{DATA} ) {
923 printf " $operations{SRCH}{FIELD}",
924 $hours{$index}{SRCH} ? $hours{$index}{SRCH} : 0;
925 }
926 if ( $operations{CMP}{DATA} ) {
927 printf " $operations{CMP}{FIELD}",
928 $hours{$index}{CMP} ? $hours{$index}{CMP} : 0;
929 }
930 if ( $operations{ADD}{DATA} ) {
931 printf " $operations{ADD}{FIELD}",
932 $hours{$index}{ADD} ? $hours{$index}{ADD} : 0;
933 }
934 if ( $operations{MOD}{DATA} ) {
935 printf " $operations{MOD}{FIELD}",
936 $hours{$index}{MOD} ? $hours{$index}{MOD} : 0;
937 }
938 if ( $operations{MODRDN}{DATA} ) {
939 printf " $operations{MODRDN}{FIELD}",
940 $hours{$index}{MODRDN} ? $hours{$index}{MODRDN} : 0;
941 }
942 if ( $operations{DEL}{DATA} ) {
943 printf " $operations{DEL}{FIELD}",
944 $hours{$index}{DEL} ? $hours{$index}{DEL} : 0;
945 }
946 print "\n";
947}
948
949#######################################################
950### Process the month information and print a report
951########################################################
952print "\n\n";
953$printstr = 'Day of Month ';
954$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{STRING} : q{};
955$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
956$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{STRING} : q{};
957$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{STRING} : q{};
958$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{STRING} : q{};
959$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{STRING} : q{};
960$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{STRING} : q{};
961$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{STRING} : q{};
962$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{STRING} : q{};
963$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{STRING} : q{};
964$printstr .= "\n";
965print $printstr;
966$printstr = '-------------';
967$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{SPACING} : q{};
968$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
969$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{SPACING} : q{};
970$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{SPACING} : q{};
971$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{SPACING} : q{};
972$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{SPACING} : q{};
973$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{SPACING} : q{};
974$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{SPACING} : q{};
975$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{SPACING} : q{};
976$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{SPACING} : q{};
977print "$printstr\n";
978
979for ( 1 .. 31 ) {
980 if ( defined $days{$_} || $printdays ) {
981 printf ' %-11s', $_;
982 if ( $operations{CONNECT}{DATA} ) {
983 printf " $operations{CONNECT}{FIELD}",
984 $days{$_}{CONNECT} ? $days{$_}{CONNECT} : 0;
985 }
986 if ( $operations{FAILURES}{DATA} ) {
987 printf " $operations{FAILURES}{FIELD}",
988 $days{$_}{AUTHFAILURES} ? $days{$_}{AUTHFAILURES} : 0;
989 }
990 if ( $operations{BIND}{DATA} ) {
991 printf " $operations{BIND}{FIELD}",
992 $days{$_}{BIND} ? $days{$_}{BIND} : 0;
993 }
994 if ( $operations{UNBIND}{DATA} ) {
995 printf " $operations{UNBIND}{FIELD}",
996 $days{$_}{UNBIND} ? $days{$_}{UNBIND} : 0;
997 }
998 if ( $operations{SRCH}{DATA} ) {
999 printf " $operations{SRCH}{FIELD}",
1000 $days{$_}{SRCH} ? $days{$_}{SRCH} : 0;
1001 }
1002 if ( $operations{CMP}{DATA} ) {
1003 printf " $operations{CMP}{FIELD}",
1004 $days{$_}{CMP} ? $days{$_}{CMP} : 0;
1005 }
1006 if ( $operations{ADD}{DATA} ) {
1007 printf " $operations{ADD}{FIELD}",
1008 $days{$_}{ADD} ? $days{$_}{ADD} : 0;
1009 }
1010 if ( $operations{MOD}{DATA} ) {
1011 printf " $operations{MOD}{FIELD}",
1012 $days{$_}{MOD} ? $days{$_}{MOD} : 0;
1013 }
1014 if ( $operations{MODRDN}{DATA} ) {
1015 printf " $operations{MODRDN}{FIELD}",
1016 $days{$_}{MODRDN} ? $days{$_}{MODRDN} : 0;
1017 }
1018 if ( $operations{DEL}{DATA} ) {
1019 printf " $operations{DEL}{FIELD}",
1020 $days{$_}{DEL} ? $days{$_}{DEL} : 0;
1021 }
1022 print "\n";
1023 }
1024}
1025#######################################################
1026### Process the month information and print a report
1027########################################################
1028print "\n\n";
1029$printstr = ' Month ';
1030$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{STRING} : q{};
1031$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{STRING} : q{};
1032$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{STRING} : q{};
1033$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{STRING} : q{};
1034$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{STRING} : q{};
1035$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{STRING} : q{};
1036$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{STRING} : q{};
1037$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{STRING} : q{};
1038$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{STRING} : q{};
1039$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{STRING} : q{};
1040$printstr .= "\n";
1041print $printstr;
1042$printstr = '-------------';
1043$printstr .= $operations{CONNECT}{DATA} ? $operations{CONNECT}{SPACING} : q{};
1044$printstr .= $operations{FAILURES}{DATA} ? $operations{FAILURES}{SPACING} : q{};
1045$printstr .= $operations{BIND}{DATA} ? $operations{BIND}{SPACING} : q{};
1046$printstr .= $operations{UNBIND}{DATA} ? $operations{UNBIND}{SPACING} : q{};
1047$printstr .= $operations{SRCH}{DATA} ? $operations{SRCH}{SPACING} : q{};
1048$printstr .= $operations{CMP}{DATA} ? $operations{CMP}{SPACING} : q{};
1049$printstr .= $operations{ADD}{DATA} ? $operations{ADD}{SPACING} : q{};
1050$printstr .= $operations{MOD}{DATA} ? $operations{MOD}{SPACING} : q{};
1051$printstr .= $operations{MODRDN}{DATA} ? $operations{MODRDN}{SPACING} : q{};
1052$printstr .= $operations{DEL}{DATA} ? $operations{DEL}{SPACING} : q{};
1053print "$printstr\n";
1054
1055for my $index qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) {
1056 if ( defined $months{$index} || $printmonths ) {
1057 printf ' %-11s', $index;
1058 if ( $operations{CONNECT}{DATA} ) {
1059 printf " $operations{CONNECT}{FIELD}",
1060 $months{$index}{CONNECT} ? $months{$index}{CONNECT} : 0;
1061 }
1062 if ( $operations{FAILURES}{DATA} ) {
1063 printf " $operations{FAILURES}{FIELD}",
1064 $months{$index}{AUTHFAILURES}
1065 ? $months{$index}{AUTHFAILURES}
1066 : 0;
1067 }
1068 if ( $operations{BIND}{DATA} ) {
1069 printf " $operations{BIND}{FIELD}",
1070 $months{$index}{BIND} ? $months{$index}{BIND} : 0;
1071 }
1072 if ( $operations{UNBIND}{DATA} ) {
1073 printf " $operations{UNBIND}{FIELD}",
1074 $months{$index}{UNBIND} ? $months{$index}{UNBIND} : 0;
1075 }
1076 if ( $operations{SRCH}{DATA} ) {
1077 printf " $operations{SRCH}{FIELD}",
1078 $months{$index}{SRCH} ? $months{$index}{SRCH} : 0;
1079 }
1080 if ( $operations{CMP}{DATA} ) {
1081 printf " $operations{CMP}{FIELD}",
1082 $months{$index}{CMP} ? $months{$index}{CMP} : 0;
1083 }
1084 if ( $operations{ADD}{DATA} ) {
1085 printf " $operations{ADD}{FIELD}",
1086 $months{$index}{ADD} ? $months{$index}{ADD} : 0;
1087 }
1088 if ( $operations{MOD}{DATA} ) {
1089 printf " $operations{MOD}{FIELD}",
1090 $months{$index}{MOD} ? $months{$index}{MOD} : 0;
1091 }
1092 if ( $operations{MODRDN}{DATA} ) {
1093 printf " $operations{MODRDN}{FIELD}",
1094 $months{$index}{MODRDN} ? $months{$index}{MODRDN} : 0;
1095 }
1096 if ( $operations{DEL}{DATA} ) {
1097 printf " $operations{DEL}{FIELD}",
1098 $months{$index}{DEL} ? $months{$index}{DEL} : 0;
1099 }
1100 print "\n";
1101 }
1102}
1103
1104####################################################
1105### Process the unindexed searches and print a report
1106####################################################
1107my @sarray; # sort array
1108if ( $stats{TOTAL_UNINDEXED} > 0 ) {
1109
1110 print "\n\n"
1111 . "# Uses Unindexed attribute\n"
1112 . "---------- -----------------------------------------------------------\n";
1113
1114 @sarray =
1115 reverse sort { $unindexed{$a} <=> $unindexed{$b} } keys %unindexed;
1116 UNINDEXED:
1117 for my $num ( 0 .. $#sarray ) {
1118 if ( $num > $count ) {
1119 last UNINDEXED;
1120 }
1121 printf " %-8d %-60s\n", $unindexed{ $sarray[$num] }, $sarray[$num];
1122 }
1123}
1124
1125######################################################
1126### Process the stored search bases and print a report
1127######################################################
1128print "\n\n"
1129 . "# Searches Search base\n"
1130 . "---------- -----------------------------------------------------------\n";
1131
1132@sarray = reverse sort { $search{$a} <=> $search{$b} } keys %search;
1133SEARCH:
1134for my $num ( 0 .. $#sarray ) {
1135 if ( $num > $count ) {
1136 last SEARCH;
1137 }
1138 printf " %-8d %-60s\n", $search{ $sarray[$num] },
1139 $sarray[$num] || 'RootDSE';
1140}
1141
1142######################################################
1143### Process the stored search filters
1144######################################################
1145print "\n\n"
1146 . "# Uses Filter\n"
1147 . "---------- -----------------------------------------------------------\n";
1148
1149@sarray = reverse sort { $filters{$a} <=> $filters{$b} } keys %filters;
1150FILTER:
1151for my $num ( 0 .. $#sarray ) {
1152 if ( $num > $count ) {
1153 last FILTER;
1154 }
1155 printf " %-8d %-60s\n", $filters{ $sarray[$num] }, $sarray[$num];
1156}
1157
1158######################################################
1159### Process the stored attribute array
1160######################################################
1161print "\n\n"
1162 . "# Uses Attributes explicitly requested in search string\n"
1163 . "---------- -------------------------------------------------\n";
1164
1165@sarray =
1166 reverse sort { $searchattributes{$a} <=> $searchattributes{$b} }
1167 keys %searchattributes;
1168SEARCHATTR:
1169for my $num ( 0 .. $#sarray ) {
1170 if ( $num > $count ) {
1171 last SEARCHATTR;
1172 }
1173 printf " %-8d %-60s\n", $searchattributes{ $sarray[$num] },
1174 $sarray[$num];
1175}
1176
1177######################################################
1178### Process the stored binddns and print a report
1179######################################################
1180print "\n\n"
1181 . "# Binds Bind DN\n"
1182 . "---------- --------------------------------------------------------------\n";
1183
1184@sarray = reverse sort { $binddns{$a} <=> $binddns{$b} } keys %binddns;
1185BINDDN:
1186for my $num ( 0 .. $#sarray ) {
1187 if ( $num > $count ) {
1188 last BINDDN;
1189 }
1190 printf " %-8d %-60s\n", $binddns{ $sarray[$num] }, $sarray[$num];
1191}
1192
1193print "\n\n";
1194
1195# EOF
Note: See TracBrowser for help on using the repository browser.