ckp_increment_dates.pl #1

  • //
  • guest/
  • robert_cowham/
  • perforce/
  • utils/
  • journal_tool/
  • ckp_increment_dates.pl
  • View
  • Commits
  • Open Download .zip Download (12 KB)
#!/usr/bin/perl -w

# Copyright (c) 2007, Perforce Software, Inc.  All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#
# See PerlDoc in footer of script
use POSIX qw( strftime );
use Time::Local;

package ckp_increment_dates;

use P4::Journal;

my $delta_seconds;
my $report_mode; # Set if in report mode
my $new_start_date; # Set if doing a squash
my $old_start_date;
my $date_factor;
my %date_map = ();  # Record previously mapped dates

# When in report mode
my %table_min_dates = (); # Min/max dates per table
my %table_max_dates = (); # Min/max dates per table
my %table_total_dates = ();
my %table_count = ();

our @ISA = qw( P4::Journal );

sub new( $$ ) {
    my $class = shift;
    my $output = shift;

    my $i;

    my $self = new P4::Journal;
    bless( $self, $class );

    open OUTPUT, ">$output" or
        die "Could not write to \'" . $output . "\':\n" . $!;

    return $self;
}

# A list of all date fields indexed by table name.
my %FIELDMAP = ( 
    'db.change' => [ 'date','access','update' ],
    'db.changex' => [ 'date','access','update' ],
    'db.configh' => [ 'date' ],
    'db.domain' => [ 'updateDate','accessDate' ],
    'db.fix' => [ 'date' ],
    'db.fixrev' => [ 'date' ],
    'db.graphindex' => [ 'date' ],
    'db.have' => [ 'time' ],
    'db.have.pt' => [ 'time' ],
    'db.have.rp' => [ 'time' ],
    'db.jnlack' => [ 'lastUpdate' ],
    'db.job' => [ 'xdate' ],
    'db.monitor' => [ 'startDate' ],
    'db.property' => [ 'date' ],
    'db.protect' => [ 'update' ],
    'db.pubkey' => [ 'update' ],
    'db.refhist' => [ 'date' ],
    'db.remote' => [ 'update', 'access' ],
    'db.repo' => [ 'created', 'pushed' ],
    'db.rev' => [ 'date', 'modTime' ],
    'db.revbx' => [ 'date', 'modTime' ],
    'db.revdx' => [ 'date', 'modTime' ],
    'db.revhx' => [ 'date', 'modTime' ],
    'db.revpx' => [ 'date', 'modTime' ],
    'db.revsh' => [ 'date', 'modTime' ],
    'db.revstg' => [ 'date', 'modTime' ],
    'db.revsx' => [ 'date', 'modTime' ],
    'db.revtx' => [ 'date', 'modTime' ],
    'db.revux' => [ 'date', 'modTime' ],
    'db.sendq' => [ 'date', 'modTime' ],
    'db.sendq.pt' => [ 'modtime', 'date' ],
    'db.storage' => [ 'date' ],
    'db.storageg' => [ 'date' ],
    'db.storagesh' => [ 'date' ],
    'db.stream' => [ 'preview' ],
    'db.ticket' => [ 'updateDate' ],
    'db.ticket.rp' => [ 'updateDate' ],
    'db.upgrades' => [ 'startdate', 'enddate' ],
    'db.upgrades.rp' => [ 'startdate', 'enddate' ],
    'db.user' => [ 'updateDate', 'accessDate', 'endDate', 'passDate', 'passExpire', 'attempts' ],
    'db.user.rp' => [ 'updateDate', 'accessDate', 'endDate', 'passDate', 'passExpire', 'attempts' ],
    'db.working' => [ 'modTime' ],
    'db.workingg' => [ 'modTime' ],
    'db.workingx' => [ 'modTime' ],
    'pdb.lbr' => [ 'date', 'modTime' ],
    'rdb.lbr' => [ 'date', 'modTime' ],
    );
# Rev tables with times that need to be updated together - so if same time occurs in any of these then 
# need to ensure the new value is the same
my %REV_TABLES = ('db.change' => 1, 'db.changex' => 1, 'db.rev' => 1, 'db.revbx' => 1, 'db.revdx' => 1, 'db.revhx' => 1,
    'db.revpx' => 1, 'db.revsh' => 1, 'db.revstg' => 1, 'db.revsx' => 1, 'db.revtx' => 1, 'db.revux' => 1);

# db.bodtext is a spec field which is user defined. The first entry contains the spec definition.
# Ideally we would parse this, but for now we just hard code the field IDs which specify dates
# For example with a spec definition like this:
# @pv@ 1 @db.bodtext@ @job@ 0 0 @Job;code:101;opt:required;rq;len:32;;Status;code:102;type:select;opt:required;rq;len:10;pre:open;val:Open/In_Progress/Closed_Fixed;;Created_by;code:103;opt:once;ro;len:32;pre:$user;;Date_created;code:104;type:date;opt:once;ro;len:20;pre:$now;;Description;code:105;type:text;opt:required;rq;pre:$blank;;Date_modified;code:130;type:date;opt:always;ro;len:20;pre:$now;;Assigned;code:120;opt:required;rq;len:32;pre:$user;;@ 
# We can see that the fields of type 'date' are 104 (Date_created) and 130 (Date_modified)
my %BODTEXT_DATE_FIELDS = (104 => 1, 130 => 1);

# Map from old date range to new date range, returning cached val if appropriate - to avoid minor
# discrepancies.
sub map_date( $$ ) {
    my $tableName = shift;
    my $val = shift;
    my $result = int((($val - $old_start_date) * $date_factor + $new_start_date));
    if (exists($REV_TABLES{$tableName})) {
        if (exists($date_map{$val})) {
            $result = $date_map{$val};
        } else {
            $date_map{$val} = $result;
        }
    }
    return $result;
}

sub ParseRecord( $ ) {
    my $self = shift;
    my $rec = shift;

    my $op;
    my $jver;
    my $dbName;
    my $remainder;

    if ( $rec->Raw() ) {
        ($op, $jver, $dbName, $remainder) = split " ", $rec->Raw(), 4;
        SWITCH: {
            if( !defined $dbName ) { last SWITCH; }
            if( !defined $rec->Raw() ) { last SWITCH; }

            # Special processing for job fields as user defined spec - see comment where BODTEXT_DATE_FIELDS is declared
            if( $dbName eq "\@db.bodtext@" ) {
                my $attr = $rec->FetchField( 'attr' );
                if( exists($BODTEXT_DATE_FIELDS{$attr}) ) {
                    my $table = 'db.bodtext';
                    my $val = $rec->FetchField( 'text' );
                    if ( $report_mode ) {
                        $table_count{$table}++;
                        $table_total_dates{$table} += $val;
                        $table_min_dates{$table} = $val if (!exists($table_min_dates{$table}) || $table_min_dates{$table} gt $val);
                        $table_max_dates{$table} = $val if (!exists($table_max_dates{$table}) || $table_max_dates{$table} lt $val);
                    } elsif ($new_start_date ne 0) {
                        # If we don't add a space the P4::Journal treats as an integer and doesn't quote it!
                        my $new_val = sprintf( " %012d", map_date($table, $val) );
                        $rec->SetField( 'text', $new_val );
                    } else {
                        my $new_val = sprintf( " %012d", $val + $delta_seconds );
                        $rec->SetField( 'text', $new_val );
                    }
                }
                last SWITCH;
            }

            foreach my $table ( keys %FIELDMAP ) {
                if( $dbName eq "\@$table@" ) {
                    foreach my $fName ( @{$FIELDMAP{ $table }} ) {
                        my $val = $rec->FetchField( $fName );
                        if( defined($val) && $val ne 0 ) {
                            if ( $report_mode ) {
                                $table_count{$table}++;
                                $table_total_dates{$table} += $val;
                                $table_min_dates{$table} = $val if (!exists($table_min_dates{$table}) || $table_min_dates{$table} gt $val);
                                $table_max_dates{$table} = $val if (!exists($table_max_dates{$table}) || $table_max_dates{$table} lt $val);
                            } elsif ($new_start_date ne 0) {
                                $rec->SetField( $fName, map_date($table, $val) );
                            } else {
                                $rec->SetField( $fName, $val + $delta_seconds );
                            }
                        }
                    }
                    last SWITCH;
                }
            }
        }
    }
    printf OUTPUT "%s\n", $rec->Raw();
}

sub DESTROY 
{
    close OUTPUT;
}

package main;

use Getopt::Long 'HelpMessage';
$report_mode = 0;
GetOptions( 
    'delta=i' => \(my $delta=1),
    'units=s' => \(my $units='y'),
    'old_start_date=s' => \(my $old_startstr=""),
    'new_start_date=s' => \(my $new_startstr=""),
    'report'  => \($report_mode),
    'help'    =>   sub { HelpMessage(0) },
) or HelpMessage(1);

my $output = shift;
if (!defined $output) { die "You must supply an output file name.\n"; }
if ($units !~ /y|m|w|d/) {
    die "Units must be one of: y,m,w,d";
}
if ((($new_startstr ne "") && ($old_startstr eq "")) || (($new_startstr eq "") && ($old_startstr ne ""))) {
    die "Need to specify both --min_date and --new_start"
}

$new_start_date = 0;
$old_start_date = 0;
if ($new_startstr ne "") {

    if ($new_startstr =~ /(\d{4})\/(\d{1,2})\/(\d{1,2})/) {
        my $year = $1;
        my $month = $2;
        my $day = $3;
        $new_start_date = timelocal(0, 0, 0, $day, $month - 1, $year);
    } else {
        die "Can't parse $new_startstr as YYYY/mm/dd";
    }
    if ($old_startstr =~ /(\d{4})\/(\d{1,2})\/(\d{1,2})/) {
        my $year = $1;
        my $month = $2;
        my $day = $3;
        $old_start_date = timelocal(0, 0, 0, $day, $month - 1, $year);
    } else {
        die "Can't parse $old_startstr as YYYY/mm/dd";
    }
    my $time_now = timelocal(gmtime());
    $date_factor = ($time_now - $new_start_date) / ($time_now - $old_start_date);
}

if ($units eq "y") {
    $delta_seconds = $delta * 365 * 24 * 60 * 60;
} elsif ($units eq "m") {
    $delta_seconds = $delta * 30 * 24 * 60 * 60;
} elsif ($units eq "w") {
    $delta_seconds = $delta * 7 * 24 * 60 * 60;
} else {
    $delta_seconds = $delta * 24 * 60 * 60;
}

my $ckp = new ckp_increment_dates( $output );

$ckp->Parse;

if ($report_mode) {
    for (keys %table_count) {
        printf "Count Table date %s: %d\n", $_, $table_count{$_};
    }
    for (keys %table_count) {
        my $avg = $table_total_dates{$_} / $table_count{$_};
        printf "Average Table date %s: %d %s\n", $_, $avg, 
            POSIX::strftime("%Y-%m-%d %H:%M:%S", gmtime($avg));
    }
    for (keys %table_min_dates) {
        printf "Min (non-zero) Table date %s: %d %s\n", $_, $table_min_dates{$_},
            POSIX::strftime("%Y-%m-%d %H:%M:%S", gmtime($table_min_dates{$_}));
    }
    for (keys %table_max_dates) {
        printf "Max (non-zero) Table date %s: %d %s\n", $_, $table_max_dates{$_},
            POSIX::strftime("%Y-%m-%d %H:%M:%S", gmtime($table_max_dates{$_}));;
    }
}

=head1 NAME

ckp_increment_dates - increment dates in a checkpoint

=head1 SYNOPSIS

  ckp_increment_dates.pl [ --delta <no units> --units [y/m/w/d] | --report | --start <yyyy/mm/dd> ] <output-file>

  --delta,-d        Specify delta as an integer value (default=1)
  --units,-u        Specify units: y=year, m=month, w=week, d=day, (default=y)
  --report,-r       Report on dates per table (mutually exclus)
  --old_start_date  Specifies a YYYY/MM/DD date - as old as any date in the file
  --new_start_date  Specifies new YYYY/MM/DD to use as earliest date for increment and squash all dates 
                    into the time period between then and now
  --help,-h         Print this help
  <output-file>     Specify output file (or '-' for stdout)

Increment all dates in a checkpoint by a fixed delta, or squash into a new range.
Only relevant date fields on certain records are updated.

The P4::Journal module must be installed as this script heavily uses that
module. Be careful about running this script on large checkpoints - may take a while!

Examples:

  Increment an uncompressed checkpoint by a delta of 1 year and 
  save the results to an uncompressed checkpoint:

    cat customer.ckp | ckp_increment_dates.pl --delta 1 --units y incremented.ckp

  Increment a compressed checkpoint, squashing the dates into a new time period, and saving them to a compressed checkpoint:

    cat customer.ckp.gz | gunzip | ckp_increment_dates.pl --start 2020/01/01 - | gzip > incremented.ckp.gz

=cut
# Change User Description Committed
#1 32178 Robert Cowham journal_tool