0ad5fbd48d
Instead of hardcoding paths to the executables, use the version in the path. This allows the scripts to work on more systems, and allows the binary version to be changed more easily if needed. Signed-off-by: Martin Roth <martin@coreboot.org> Change-Id: Ifcc56aa21092cd3866eacb6a02d198110ec6051d Reviewed-on: https://review.coreboot.org/c/coreboot/+/48904 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Angel Pons <th3fanbus@gmail.com>
457 lines
17 KiB
Perl
Executable file
457 lines
17 KiB
Perl
Executable file
#!/usr/bin/env perl
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
package gerrit_stats;
|
|
|
|
# To install any needed modules install the cpanm app, and use it to install the required modules:
|
|
# sudo cpan App::cpanminus
|
|
# sudo /usr/local/bin/cpanm JSON::Util Net::OpenSSH DateTime Devel::Size
|
|
|
|
use strict;
|
|
use warnings;
|
|
use English qw( -no_match_vars );
|
|
use File::Find;
|
|
use File::Path;
|
|
use Getopt::Long;
|
|
use Getopt::Std;
|
|
use JSON::Util;
|
|
use Net::OpenSSH;
|
|
use Data::Dumper qw(Dumper);
|
|
use DateTime;
|
|
use Devel::Size qw(size total_size);
|
|
|
|
my $old_version;
|
|
my $new_version;
|
|
my $infodir="$ENV{'HOME'}/.commit_info/" . `git config -l | grep remote.origin.url | sed 's|.*@||' | sed 's|:.*||'`;
|
|
chomp($infodir);
|
|
my $URL_WITH_USER;
|
|
my $SKIP_GERRIT_CHECK;
|
|
my $print_commit_list = 1;
|
|
|
|
#disable print buffering
|
|
$OUTPUT_AUTOFLUSH = 1;
|
|
binmode STDOUT, ":utf8";
|
|
|
|
Main();
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Main
|
|
#-------------------------------------------------------------------------------
|
|
sub Main {
|
|
check_arguments();
|
|
|
|
my %submitters = ();
|
|
my %authors = ();
|
|
my %owners = ();
|
|
my %reviewers = ();
|
|
my %author_added = ();
|
|
my %author_removed = ();
|
|
my $total_added = 0;
|
|
my $total_removed = 0;
|
|
my $number_of_commits = 0;
|
|
my $number_of_submitters = 0;
|
|
my $submit_epoch = "";
|
|
my $first_submit_epoch = "";
|
|
if (!$URL_WITH_USER) {
|
|
get_user()
|
|
}
|
|
|
|
# make sure the versions exist
|
|
check_versions();
|
|
|
|
#fetch patches if needed. Get ids of first and last commits
|
|
my @commits = `git log --pretty=%h "$old_version..$new_version" 2>/dev/null`;
|
|
get_commits(@commits);
|
|
my $last_commit_id = $commits[0];
|
|
my $first_commit_id = $commits[@commits - 1];
|
|
chomp $last_commit_id;
|
|
chomp $first_commit_id;
|
|
|
|
print "Statistics from commit $first_commit_id to commit $last_commit_id\n";
|
|
print "Patch, Date, Owner, Author, Submitter, Inserted lines, Deleted lines, Subject, Reviewers\n";
|
|
|
|
#loop through all commits
|
|
for my $commit_id (@commits) {
|
|
$commit_id =~ s/^\s+|\s+$//g;
|
|
|
|
my $submitter = "";
|
|
my %patch_reviewers = ();
|
|
my $info;
|
|
my $owner;
|
|
my $author;
|
|
my $author_email;
|
|
my $inserted_lines = 0;
|
|
my $deleted_lines = 0;
|
|
my $subject;
|
|
|
|
$number_of_commits++;
|
|
print "\"$commit_id\", ";
|
|
|
|
#read the data file for the current commit
|
|
if (-f "$infodir/$commit_id" && -s "$infodir/$commit_id" > 20) {
|
|
open( my $HANDLE, "<", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
|
|
$info = <$HANDLE>;
|
|
close $HANDLE;
|
|
|
|
my $commit_info = JSON::Util->decode($info);
|
|
|
|
#get the easy data
|
|
$owner = $commit_info->{'owner'}{'name'};
|
|
if (! $owner) {
|
|
$owner = $commit_info->{'owner'}{'username'};
|
|
}
|
|
if (! $owner) {
|
|
$owner = "";
|
|
}
|
|
$author = $commit_info->{'currentPatchSet'}{'author'}{'name'};
|
|
$author_email = $commit_info->{'currentPatchSet'}{'author'}{'email'};
|
|
if (! $author) {
|
|
$author = $commit_info->{'currentPatchSet'}{'author'}{'username'};
|
|
}
|
|
|
|
$inserted_lines = $commit_info->{'currentPatchSet'}{'sizeInsertions'};
|
|
$deleted_lines = $commit_info->{'currentPatchSet'}{'sizeDeletions'};
|
|
$subject = $commit_info->{'subject'};
|
|
|
|
#get the patch's submitter
|
|
my $approvals = $commit_info->{'currentPatchSet'}{'approvals'};
|
|
for my $approval (@$approvals) {
|
|
if ($approval->{'type'} eq "SUBM") {
|
|
$submit_epoch = $approval->{'grantedOn'};
|
|
$submitter = $approval->{'by'}{'name'};
|
|
}
|
|
}
|
|
|
|
#get all the reviewers for all patch revisions
|
|
my $patchsets = $commit_info->{'patchSets'};
|
|
for my $patch (@$patchsets) {
|
|
if (! $author) {
|
|
$author = $patch->{'author'}{'name'};
|
|
}
|
|
my $approvals = $patch->{'approvals'};
|
|
for my $approval (@$approvals) {
|
|
|
|
if ( (! $submitter) && ($approval->{'type'} eq "SUBM")) {
|
|
$submit_epoch = $approval->{'grantedOn'};
|
|
$submitter = $approval->{'by'}{'name'};
|
|
}
|
|
|
|
if ($approval->{'type'} eq "Code-Review") {
|
|
my $patch_reviewer = $approval->{'by'}{'name'};
|
|
if ($patch_reviewer) {
|
|
if (exists $patch_reviewers{$patch_reviewer}) {
|
|
$patch_reviewers{$patch_reviewer}++;
|
|
} else {
|
|
$patch_reviewers{$patch_reviewer} = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
# get the info from git
|
|
my $logline = `git log --pretty="%ct@@@%s@@@%an@@@%aE@@@%cn" $commit_id^..$commit_id --`;
|
|
$logline =~ m/^(.*)@@@(.*)@@@(.*)@@@(.*)@@@(.*)\n/;
|
|
($submit_epoch, $subject, $author, $author_email, $submitter) = ($1, $2, $3, $4, $5);
|
|
$owner = $author;
|
|
$logline = `git log --pretty= --shortstat $commit_id^..$commit_id --`;
|
|
if ($logline =~ m/\s+(\d+)\s+insertion/) {
|
|
$inserted_lines = $1;
|
|
}
|
|
if ($logline =~ m/\s+(\d+)\s+deletion/) {
|
|
$deleted_lines = $1 * -1;
|
|
}
|
|
my @loglines = `git log $commit_id^..$commit_id -- | grep '\\sReviewed-by:'`;
|
|
for my $line (@loglines){
|
|
if ($line =~ m/.*:\s+(.*)\s</) {
|
|
my $patch_reviewer = $1;
|
|
if ($patch_reviewer) {
|
|
if (exists $patch_reviewers{$patch_reviewer}) {
|
|
$patch_reviewers{$patch_reviewer}++;
|
|
} else {
|
|
$patch_reviewers{$patch_reviewer} = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
# Not entirely certain why this is needed, but for a number of patches have been submitted
|
|
# the submit time in gerrit is set to April 9, 2015.
|
|
if ($submit_epoch == 1428586219){
|
|
my $logline = `git log --pretty="%ct" $commit_id^..$commit_id --`;
|
|
$logline =~ m/^(.*)\n/;
|
|
$submit_epoch = $1;
|
|
}
|
|
|
|
#add the count and owner to the submitter hash
|
|
if (exists $submitters{$submitter}) {
|
|
$submitters{$submitter}++;
|
|
} else {
|
|
$submitters{$submitter} = 1;
|
|
$number_of_submitters++;
|
|
}
|
|
|
|
#create a readable date
|
|
my $dt = DateTime->from_epoch(epoch => $submit_epoch);
|
|
$dt->set_time_zone( 'Europe/Paris' );
|
|
my $submit_time = $dt->strftime('%Y/%m/%d %H:%M:%S');
|
|
if (!$first_submit_epoch) {
|
|
$first_submit_epoch = $submit_epoch;
|
|
}
|
|
|
|
#create the list of reviewers to print
|
|
my $reviewerlist = "";
|
|
foreach my $reviewer (keys %patch_reviewers) {
|
|
if ($reviewerlist eq "") {
|
|
$reviewerlist = $reviewer;
|
|
} else {
|
|
$reviewerlist .= ", $reviewer";
|
|
}
|
|
|
|
if (exists $reviewers{$reviewer}) {
|
|
$reviewers{$reviewer}++;
|
|
} else {
|
|
$reviewers{$reviewer} = 1;
|
|
}
|
|
}
|
|
if (! $reviewerlist) {
|
|
$reviewerlist = "-"
|
|
}
|
|
|
|
if ($print_commit_list) {
|
|
print "$submit_time, $owner, $author, $submitter, $inserted_lines, $deleted_lines, \"$subject\", \"$reviewerlist\"\n";
|
|
} else {
|
|
print "$number_of_commits\n";
|
|
}
|
|
$total_added += $inserted_lines;
|
|
$total_removed += $deleted_lines;
|
|
if (exists $owners{$owner}) {
|
|
$owners{$owner}++;
|
|
} else {
|
|
$owners{$owner} = 1;
|
|
}
|
|
|
|
if (exists $authors{$author}{"num"}) {
|
|
$authors{$author}{"num"}++;
|
|
$author_added{$author} += $inserted_lines;
|
|
$author_removed{$author} += $deleted_lines;
|
|
$authors{$author}{"earliest_commit"}=$submit_time;
|
|
} else {
|
|
$authors{$author}{"num"} = 1;
|
|
$authors{$author}{"latest_commit"}=$submit_time;
|
|
$authors{$author}{"earliest_commit"}=$submit_time;
|
|
$author_added{$author} = $inserted_lines;
|
|
$author_removed{$author} = $deleted_lines;
|
|
}
|
|
if (! exists $authors{$author}{email} && $author_email) {
|
|
$authors{$author}{email} = "$author_email";
|
|
}
|
|
}
|
|
my $Days = ($first_submit_epoch - $submit_epoch) / 86400;
|
|
if (($first_submit_epoch - $submit_epoch) % 86400) {
|
|
$Days += 1;
|
|
}
|
|
|
|
print "- Total Commits: $number_of_commits\n";
|
|
printf "- Average Commits per day: %.2f\n", $number_of_commits / $Days;
|
|
print "- Total lines added: $total_added\n";
|
|
print "- Total lines removed: $total_removed\n";
|
|
print "- Total difference: " . ($total_added + $total_removed) . "\n\n";
|
|
|
|
print "=== Authors - Number of commits ===\n";
|
|
my $number_of_authors = 0;
|
|
foreach my $author (sort { $authors{$b}{num} <=> $authors{$a}{num} } (keys %authors) ) {
|
|
if (! exists $authors{$author}{"email"}) {
|
|
$authors{$author}{"email"} = "-";
|
|
}
|
|
printf "%-25s %5d %-40s (%2.2f%%) {%s / %s}\n",$author, $authors{$author}{"num"}, $authors{$author}{"email"}, $authors{$author}{"num"} / $number_of_commits * 100, $authors{$author}{"latest_commit"}, $authors{$author}{"earliest_commit"};
|
|
$number_of_authors++;
|
|
}
|
|
print "Total Authors: $number_of_authors\n\n";
|
|
|
|
print "=== Authors - Lines added ===\n";
|
|
foreach my $author (sort { $author_added{$b} <=> $author_added{$a} } (keys %author_added) ) {
|
|
if ($author_added{$author}) {
|
|
printf "%-25s %5d (%2.3f%%)\n",$author, $author_added{$author}, $author_added{$author} / $total_added * 100;
|
|
}
|
|
}
|
|
print "\n";
|
|
|
|
print "=== Authors - Lines removed ===\n";
|
|
foreach my $author (sort { $author_removed{$a} <=> $author_removed{$b} } (keys %author_removed) ) {
|
|
if ($author_removed{$author}) {
|
|
printf "%-25s %5d (%2.3f%%)\n",$author,$author_removed{$author} * -1, $author_removed{$author} / $total_removed * 100;
|
|
}
|
|
}
|
|
print "\n";
|
|
|
|
print "=== Reviewers - Number of patches reviewed ===\n";
|
|
my $number_of_reviewers = 0;
|
|
foreach my $reviewer (sort { $reviewers{$b} <=> $reviewers{$a} } (keys %reviewers) ) {
|
|
printf "%-25s %5d (%2.3f%%)\n",$reviewer, $reviewers{$reviewer}, $reviewers{$reviewer} / $number_of_commits * 100;
|
|
$number_of_reviewers++;
|
|
}
|
|
print "Total Reviewers: $number_of_reviewers\n\n";
|
|
|
|
print "=== Submitters - Number of patches submitted ===\n";
|
|
foreach my $submitter (sort { $submitters{$b} <=> $submitters{$a} } (keys %submitters) ) {
|
|
printf "%-25s %5d (%2.3f%%)\n",$submitter, $submitters{$submitter}, $submitters{$submitter} / $number_of_commits * 100;
|
|
}
|
|
print "Total Submitters: $number_of_submitters\n\n";
|
|
|
|
print "Commits, Ave, Added, Removed, Diff, Authors, Reviewers, Submitters\n";
|
|
printf "$number_of_commits, %.2f, $total_added, $total_removed, " . ($total_added + $total_removed) . ", $number_of_authors, $number_of_reviewers, $number_of_submitters\n", $number_of_commits / $Days;
|
|
}
|
|
|
|
#-------------------------------------------------------------------------------
|
|
#-------------------------------------------------------------------------------
|
|
sub check_versions {
|
|
`git cat-file -e $old_version^{commit} 2>/dev/null`;
|
|
if (${^CHILD_ERROR_NATIVE}){
|
|
print "Error: Old version ($old_version) does not exist.\n";
|
|
exit 1;
|
|
}
|
|
|
|
`git cat-file -e $new_version^{commit} 2>/dev/null`;
|
|
if (${^CHILD_ERROR_NATIVE}){
|
|
print "Error: New version ($new_version) does not exist.\n";
|
|
exit 1;
|
|
}
|
|
}
|
|
|
|
#-------------------------------------------------------------------------------
|
|
#-------------------------------------------------------------------------------
|
|
sub get_user {
|
|
my $url=`git config -l | grep remote.origin.url`;
|
|
|
|
if ($url =~ /.*url=ssh:\/\/(\w+@[a-zA-Z][a-zA-Z0-9\.]+:\d+)/)
|
|
{
|
|
$URL_WITH_USER = $1;
|
|
} else {
|
|
print "Error: Could not get a ssh url with a username from gitconfig.\n";
|
|
print " use the -u option to set a url.\n";
|
|
exit 1;
|
|
}
|
|
}
|
|
|
|
#-------------------------------------------------------------------------------
|
|
#-------------------------------------------------------------------------------
|
|
sub get_commits {
|
|
my @commits = @_;
|
|
my $submit_time = "";
|
|
if (defined $SKIP_GERRIT_CHECK) {
|
|
return;
|
|
}
|
|
my $ssh = Net::OpenSSH->new("$URL_WITH_USER", );
|
|
$ssh->error and die "Couldn't establish SSH connection to $URL_WITH_USER:". $ssh->error;
|
|
|
|
print "Using URL: ssh://$URL_WITH_USER\n";
|
|
|
|
if (! -d $infodir) {
|
|
mkpath($infodir)
|
|
}
|
|
|
|
for my $commit_id (@commits) {
|
|
$commit_id =~ s/^\s+|\s+$//g;
|
|
$submit_time = "";
|
|
my $gerrit_review;
|
|
|
|
# Quit if we've reeached the last coreboot commit supporting these queries
|
|
if ($commit_id =~ /^7309709/) {
|
|
last;
|
|
}
|
|
|
|
if (-f "$infodir/$commit_id") {
|
|
$gerrit_review = 1;
|
|
} else {
|
|
$gerrit_review = `git log $commit_id^..$commit_id | grep '\\sReviewed-on:\\s'`;
|
|
}
|
|
|
|
if ($gerrit_review && $commit_id && (! -f "$infodir/$commit_id") ) {
|
|
print "Downloading $commit_id";
|
|
my @info = $ssh->capture("gerrit query --format=JSON --comments --files --current-patch-set --all-approvals --submit-records --dependencies commit:$commit_id");
|
|
$ssh->error and die "remote ls command failed: " . $ssh->error;
|
|
|
|
my $commit_info = JSON::Util->decode($info[0]);
|
|
my $rowcount = $commit_info->{'rowCount'};
|
|
if (defined $rowcount && ($rowcount eq "0")) {
|
|
print " - no gerrit commit for that id.\n";
|
|
open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
|
|
print $HANDLE "No gerrit commit";
|
|
close $HANDLE;
|
|
next;
|
|
}
|
|
my $approvals = $commit_info->{'currentPatchSet'}{'approvals'};
|
|
|
|
for my $approval (@$approvals) {
|
|
if ($approval->{'type'} eq "SUBM") {
|
|
$submit_time = $approval->{'grantedOn'}
|
|
}
|
|
}
|
|
my $dt="";
|
|
if ($submit_time) {
|
|
$dt = DateTime->from_epoch(epoch => $submit_time);
|
|
} else {
|
|
print " - no submit time for that id.\n";
|
|
open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
|
|
print $HANDLE "No submit time";
|
|
close $HANDLE;
|
|
|
|
next;
|
|
}
|
|
|
|
open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
|
|
print $HANDLE $info[0];
|
|
close $HANDLE;
|
|
|
|
$dt->set_time_zone( 'Europe/Paris' );
|
|
print " - submit time: " . $dt->strftime('%Y/%m/%d %H:%M:%S') . "\n";
|
|
} elsif ($commit_id && (! -f "$infodir/$commit_id")) {
|
|
print "No gerrit commit for $commit_id\n";
|
|
open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
|
|
print $HANDLE "No gerrit commit";
|
|
close $HANDLE;
|
|
}
|
|
}
|
|
print "\n";
|
|
}
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# check_arguments parse the command line arguments
|
|
#-------------------------------------------------------------------------------
|
|
sub check_arguments {
|
|
my $show_usage = 0;
|
|
GetOptions(
|
|
'help|?' => sub { usage() },
|
|
'url|u=s' => \$URL_WITH_USER,
|
|
'skip|s' => \$SKIP_GERRIT_CHECK,
|
|
);
|
|
# strip ssh:// from url if passed in.
|
|
if (defined $URL_WITH_USER) {
|
|
$URL_WITH_USER =~ s|ssh://||;
|
|
}
|
|
if (@ARGV) {
|
|
($old_version, $new_version) = @ARGV;
|
|
} else {
|
|
usage();
|
|
}
|
|
}
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# usage - Print the arguments for the user
|
|
#-------------------------------------------------------------------------------
|
|
sub usage {
|
|
print "gerrit_stats <options> [Old version] [New version]\n";
|
|
print "Old version should be a tag (4.1), a branch (origin/4.1), or a commit id\n";
|
|
print "New version can be 'HEAD' a branch (origin/master) a tag (4.2), or a commit id\n";
|
|
print " Options:\n";
|
|
print " u | url [url] url with username.\n";
|
|
print "Example: \"$0 -u Gaumless\@review.coreboot.org:29418 origin/4.1 4.2\"\n";
|
|
exit(0);
|
|
}
|
|
|
|
1;
|