5 =head1 NAME $RCSfile: viewager.cgi,v $
15 Andrew DeFaria <Andrew@ClearSCM.com>
23 Mon Oct 25 11:10:47 PDT 2008
27 $Date: 2011/01/14 16:50:54 $
33 This script serves 4 distinct functions. One function is to find
34 old views and report them to their owners via email so that view cleanup can be
35 done. Another function just does a quick report stdout. Yet another function is
36 to present the list of views in a web page. Finally there is a function
37 (generate) which generates a cache file containing information about views. This
38 function is designed to be run by a scheduler such as cron. Note that the web
39 page function relies on and uses this cache file too.
43 Most Clearcase administrators wrestle with trying to keep the number of views
44 under control. Users often create views but seldom think to remove them. Views
45 grow old and forgotten.
47 Many approaches have been taken, usally emailing the users telling them to clean
48 up their views. This script, viewager.cgi, attempts to encapsulate the task of
49 gathering information about old views, informing users of which of their views
50 are old and presenting reports in the form of a web page showing all views
53 =head1 USAGE Email, Report and Generate modes
55 Usage viewager.cgi: [-u|sage] [-region <region>] [-e|mail]
56 [-a|gethreshold <n>] [-n|brThreshold <n>]
57 [-ac|tion <act>] [-s|ort <field>]
61 -u|sage: Displays usage
62 -region <region>: Region to use when looking for views (Default
63 for generate action: all)
64 -e|mail: Send email to owners of old views
65 -ag|eThreshold: Number of days before a view is considered old
67 -n|brThreshold <n>: Number of views to report. Can be used for say a
68 "top 10" old views. Useful with -action report
69 (Default: Report all views)
70 -ac|tion <act> Valid actions include 'generate' or 'report'.
71 Generate mode merely regenerates the cache file.
72 Report produces a quick report to stdout.
73 -s|ort <field>: Where <field> is one of <tag|ownerName|type|age>
76 -d|ebug: Output debug messages
78 =head1 USAGE Web Page mode
80 Parameters for the web page mode are provided by the CPAN module CGI and are
81 normally passed in as part of the URL. These parameters are specified as
84 sortby=<tag|ownerName|type|age>
85 Note: age will sort in a reverse numerical fashion
88 <username> can be a partial name (e.g. 'defaria')
92 This script seek to handle the general issue of handling old views. In generate
93 mode this script goes through all views collecting data about all of the views
94 and creates a cache file. The reason for this is that this process is length
95 (At one client's site with ~2500 views takes about 1 hour). As such you'd
96 probably want to schedule the running of this for once a day.
98 Once the cache file is created other modes will read that file and report on it.
99 In report mode you can report to stdout. For example, the following will give
100 you a quick "top 10" oldest views:
102 $ viewager.cgi -action report -n 10
104 You may wish to add the following to your conrtabe to generated the cachefile
107 0 0 * * * cd /<DocumentRoot>/viewager && /<path>/viewager.cgi -action=generate
111 Since the method for translating a user's userid into other attributes like
112 the users fullname and email, we rely on a User.pm module to implement a User
113 object that takes a string identifying the user and return useful informaiton
114 about the user, specifically the fullname and email address.
123 use CGI qw(:standard :cgi-lib *table start_Tr end_Tr);
124 use CGI::Carp 'fatalsToBrowser';
128 use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib";
134 use Clearcase::Views;
141 my $VERSION = '$Revision: 1.11 $';
142 ($VERSION) = ($VERSION =~ /\$Revision: (.*) /);
147 $opts{sortby} ||= 'age';
148 $opts{ageThreshold} = 180; # Default number of days a view must be older than
150 my $subtitle = 'View Aging Report';
153 my $port = CGI::server_port;
154 $port = ($port == 80) ? '' : ":$port";
155 my $scriptName = CGI::script_name;
156 $scriptName =~ s/index.cgi//;
157 my $script = 'http://'
158 . $Clearadm::CLEAROPTS{CLEARADM_SERVER}
163 my $nbrThreshold; # Number of views threshold - think top 10
165 sub GenerateRegion($) {
168 verbose "Processing region $region";
171 my $views = Clearcase::Views->new ($region);
172 my @Views = $views->views;
175 verbose scalar @Views . " views to process";
179 for my $name (@Views) {
182 if (++$i % 100 == 0) {
184 } elsif ($i % 25 == 0) {
188 my $view = Clearcase::View->new($name, $region);
192 if ($view->webview) {
193 # TODO: There doesn't appear to be a good way to get the gpath for a
194 # webview since it's set to <nogpath>! Here we try to compose one using
195 # $view->host and $view->access_path but this is decidedly Windows centric
196 # and thus not portable. This needs to be fixed!
197 $gpath = '\\\\' . $view->host . '\\' . $view->access_path;
199 # Change any ":" to "$". This is to change things like D:\path -> D$\path.
200 # This assumes we have permissions to access through the administrative
204 $gpath = $view->gpath;
207 # Note if the view server is unreachable (e.g. user puts view on laptop and
208 # the laptop is powered off), then these fields will be undef. Change them
209 # to Unknown. (Should Clearcase::View.pm do this instead?).
210 my $type = $view->type;
212 my $ownerid = $view->owner;
213 $ownerid ||= 'Unknown';
217 if ($ownerid =~ /^\w+(\\|\/)(\w+)/) {
218 # TODO: Handle user identification better
219 #$user = User->new ($ownerid);
223 $user->{email} = "$2\@gddsi.com";
225 $ownerid = 'Unknown';
226 $user->{name} = 'Unknown';
227 $user->{email} = 'unknown@gddsi.com';
233 my $modified_date = $view->modified_date;
235 if ($modified_date) {
236 $modified_date = substr $modified_date, 0, 16;
237 $modified_date =~ s/T/\@/;
240 $age = Age ($modified_date);
241 $ageSuffix = $age != 1 ? 'days' : 'day';
244 my %oldView = $clearadm->GetView($view->tag, $view->region);
249 region => $view->region,
252 ownerName => $user->{name},
253 email => $user->{email},
257 ageSuffix => $ageSuffix,
260 # Some views have not yet been modified
261 $viewRec{modified} = $modified_date if $modified_date;
264 ($err, $msg) = $clearadm->UpdateView(%viewRec);
266 error "Unable to update view $name in Clearadm\n$msg", $err if $err;
268 ($err, $msg) = $clearadm->AddView(%viewRec);
270 error "Unable to add view $name to Clearadm\n$msg", $err if $err;
274 verbose "\nProcessed region $region";
283 GenerateRegion $region;
285 GenerateRegion $_ for $Clearcase::CC->regions;
294 $total{'Views processed'} = @views;
298 if ($opts{sortby} eq 'age') {
299 # Sort by age numerically decending
300 @sortedViews = sort { $$b{$opts{sortby}} <=> $$a{$opts{sortby}} } @views;
302 @sortedViews = sort { $$a{$opts{sortby}} cmp $$b{$opts{sortby}} } @views;
305 $total{Reported} = 0;
311 if ($nbrThreshold and $total{Reported} + 1 > $nbrThreshold) or
312 ($view{age} < $opts{ageThreshold});
317 if ($view{type} eq 'dynamic') {
319 } elsif ($view{type} eq 'snapshot') {
321 } elsif ($view{type} eq 'webview') {
324 $total{$view{type}}++;
331 View Name Owner View Type Last Modified Age
332 ------------------------------------- ---------------------- ----------- ---------------- -----------
335 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<< @<<<<<<<<<<<<<<< @>>>> @<<<<
336 $view{tag},$view{owner},$view{type},$view{modified},$view{age},$view{ageSuffix}
345 sub FormatTable($@) {
346 my ($style, @views) = @_;
350 my $nbrViews = @views;
353 font ({-class => 'label'}, 'View type: ') .
354 font ({-class => 'dynamic'}, 'Dyanmic') .
356 font ({-class => 'snapshot'}, 'Snapshot') .
358 font ({-class => 'web'}, 'Web') .
360 font ({-class => 'unknown'}, 'Unknown');
364 my $regionDropdown = start_form(
368 $regionDropdown .= font {-class => 'captionLabel'}, 'Region: ';
369 $regionDropdown .= popup_menu(
371 -values => [$Clearcase::CC->regions],
372 -default => $Clearcase::CC->region,
373 -onchange => 'submit();',
376 $regionDropdown .= end_form;
378 $caption .= start_table {
384 $caption .= start_Tr;
388 }, font ({-class => 'label'}, 'Registry: '),
389 setField($Clearcase::CC->registry_host), '<br>',
390 font ({-class => 'label'}, 'Views: '),
402 $caption .= end_table;
404 $table .= start_table {
409 $table .= caption $caption;
410 $table .= start_Tr {-class => 'heading'};
413 # Set defaults if not set already
414 $opts{sortby} ||= 'age';
415 $opts{reverse} ||= 0;
417 my $parms = $opts{user} ? "&user=$opts{user}" : '';
418 $parms .= $opts{reverse} == 1 ? '&reverse=0' : '&reverse=1';
420 if ($style eq 'full') {
421 my $tagLabel = 'Tag ';
422 my $ownerLabel = 'Owner ';
423 my $typeLabel = 'Type ';
424 my $ageLabel = 'Age ';
426 if ($opts{sortby} eq 'tag') {
427 $tagLabel .= $opts{reverse} == 1
428 ? img {src => 'up.png', border => 0}
429 : img {src => 'down.png', border => 0};
430 } elsif ($opts{sortby} eq 'ownerName') {
431 $ownerLabel .= $opts{reverse} == 1
432 ? img {src => 'up.png', border => 0}
433 : img {src => 'down.png', border => 0};
434 } elsif ($opts{sortby} eq 'type') {
435 $typeLabel .= $opts{reverse} == 1
436 ? img {src => 'up.png', border => 0}
437 : img {src => 'down.png', border => 0};
438 } elsif ($opts{sortby} eq 'age') {
439 $ageLabel .= $opts{reverse} == 1
440 ? img {src => 'down.png', border => 0}
441 : img {src => 'up.png', border => 0};
444 $table .= th a {href => "$script?region=$opts{region}&sortby=tag$parms"},
446 $table .= th a {href => "$script?region=$opts{region}&sortby=ownerName$parms"},
448 $table .= th a {href => "$script?region=$opts{region}&sortby=type$parms"},
450 $table .= th a {href => "$script?region=$opts{region}&sortby=age$parms"},
454 $table .= th 'Owner';
460 if ($opts{sortby} eq 'age') {
461 # Sort by age numerically decending
462 @views = $opts{reverse} == 1
463 ? sort { $$a{$opts{sortby}} <=> $$b{$opts{sortby}} } @views
464 : sort { $$b{$opts{sortby}} <=> $$a{$opts{sortby}} } @views;
466 @views = $opts{reverse} == 1
467 ? sort { $$b{$opts{sortby}} cmp $$a{$opts{sortby}} } @views
468 : sort { $$a{$opts{sortby}} cmp $$b{$opts{sortby}} } @views;
476 next if $view{region} ne $opts{region};
478 my $owner = $view{owner};
480 if ($view{owner} =~ /\S+(\\|\/)(\S+)/) {
484 $owner = $view{ownerName} ? $view{ownerName} : 'Unknown';
486 next if $opts{user} and $owner ne $opts{user};
488 my $rowClass= $view{age} > $opts{ageThreshold} ? 'oldview' : 'view';
499 href => "viewdetails.cgi?tag=$view{tag}®ion=$opts{region}"
504 href => "$script?region=$opts{region}&user=$owner"
515 }, $view{age}, ' ', $view{ageSuffix});
524 # TODO: Add an option to remove views older than a certain date
527 my ($emailTo, @oldViews) = @_;
529 @oldViews = sort { $$b{age} <=> $$a{age} } @oldViews;
531 my $msg = '<style>' . join("\n", ReadFile 'viewager.css') . '</style>';
533 <h1 align="center">You have old Clearcase Views</h1>
535 <p>Won't you take a moment to review this message and clean up any views you no
538 <p>The following views are owned by you and have not been modified in $opts{ageThreshold}
542 $msg .= FormatTable 'partial', @oldViews;
545 <h3>How to remove views you no longer need</h3>
547 <p>There are several ways to remove Clearcase views, depending on the view
548 type and the tools you are using.</p>
551 <p><b>Dynamic Views</b>: If the view is a dynamic view you can use Clearcase
552 Explorer to remove the view. Find the view in your Clearcase Explorer. If
553 it's not there then add it as a standard view shortcut. Then right click on
554 the view shortcut and select <b>Remove View</b> (not <b>Remove View
557 <p><b>Snapshot Views</b>: A snapshot view is a view who's source storage can
558 be located locally. You can remove a snapshot view in a similar manner as a
559 dynamic view, by adding it to Clearcase Explorer if not already present. By
560 doing so you need to tell Clearcase Explorer where the snapshot view storage
563 <p><b>Webviews</b>: Webviews are like snapshot views but stored on the web
564 server. If you are using CCRC or the CCRC plugin to Eclipse you would select
565 the view and then do <b>Environment: Remove Clearcase View</b>.</p>
568 <p>If you have any troubles removing your old views then submit a case and we
569 will be happy to assist you.</p>
571 <h3>But I need for my view to stay around even if it hasn't been modified</h3>
573 <p>If you have a long lasting view who does not get modified but needs to
574 remain, contact us and we can arrange for it to be removed from consideration
575 which will stop it from being reported as old.</p>
579 Your friendly Clearcase Administrator
584 # to => 'Andrew@DeFaria.com',
586 subject => 'Old views',
596 @views = sort { $$a{ownerName} cmp $$b{ownerName} } @views;
599 my $currUser = $views [0]->{ownerName};
604 next unless $view{email};
606 if ($currUser ne $view{ownerName}) {
607 EmailUser $view{email}, @userViews if @userViews;
609 $currUser = $view{ownerName};
613 if ($view{age} > $opts{ageThreshold}) {
614 push @userViews, \%view
615 if !-f "$view{gpath}/ageless";
628 'usage' => sub { Usage },
629 'verbose' => sub { set_verbose },
630 'debug' => sub { set_debug },
637 ) or Usage "Invalid parameter";
639 # Get options from CGI
642 $opts{$_} = $CGIOpts{$_} for keys %CGIOpts;
647 verbose "$FindBin::Script v$VERSION";
649 $clearadm = Clearadm->new;
651 if ($opts{action} and $opts{action} eq 'generate') {
652 Generate $opts{region};
653 Stats \%total if $opts{verbose};
655 if ($opts{region} and ($opts{region} eq 'Clearcase not installed')) {
657 displayError $opts{region};
662 $opts{region} ||= $Clearcase::CC->region;
664 my @views = $clearadm->FindView(
670 if ($opts{action} and $opts{action} eq 'report') {
682 display FormatTable 'full', @views;
690 =head1 CONFIGURATION AND ENVIRONMENT
692 DEBUG: If set then $debug is set to this level.
694 VERBOSE: If set then $verbose is set to this level.
696 TRACE: If set then $trace is set to this level.
704 L<CGI::Carp|CGI::Carp>
706 L<Data::Dumper|Data::Dumper>
708 L<File::stat|File::stat>
712 L<Getopt::Long|Getopt::Long>
714 L<Time::localtime|Time::localtime>
716 =head2 ClearSCM Perl Modules
735 <a href="http://clearscm.com/php/scm_man.php?file=clearadm/lib/Clearadm.pm">Clearadm</a><br>
736 <a href="http://clearscm.com/php/scm_man.php?file=clearadm/lib/ClearadmWeb.pm">ClearadmWeb</a><br>
737 <a href="http://clearscm.com/php/scm_man.php?file=lib/Clearcase.pm">Clearcase</a><br>
738 <a href="http://clearscm.com/php/scm_man.php?file=lib/Clearcase/View.pm">Clearcase::View</a><br>
739 <a href="http://clearscm.com/php/scm_man.php?file=lib/Clearcase/Views.pm">Clearcase::Views</a><br>
740 <a href="http://clearscm.com/php/scm_man.php?file=lib/DateUtils.pm">DateUtils</a><br>
741 <a href="http://clearscm.com/php/scm_man.php?file=lib/Display.pm">Display</a><br>
742 <a href="http://clearscm.com/php/scm_man.php?file=lib/Mail.pm">Mail</a><br>
743 <a href="http://clearscm.com/php/scm_man.php?file=lib/Utils.pm">Utils</a><br>
744 <a href="http://clearscm.com/php/scm_man.php?file=clearadm/lib/User.pm">User</a><br>
749 =head1 BUGS AND LIMITATIONS
751 There are no known bugs in this script
753 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
755 =head1 LICENSE AND COPYRIGHT
757 Copyright (c) 2010, ClearSCM, Inc. All rights reserved.