/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2003-2008 University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.cloud.db;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.TreeSet;
import edu.umd.cs.findbugs.BugPattern;
import edu.umd.cs.findbugs.BugRanker;
import edu.umd.cs.findbugs.I18N;
import edu.umd.cs.findbugs.PluginLoader;
import edu.umd.cs.findbugs.cloud.AbstractCloud;
import edu.umd.cs.findbugs.cloud.Cloud.UserDesignation;
import edu.umd.cs.findbugs.util.FractionalMultiset;
import edu.umd.cs.findbugs.util.MergeMap;
import edu.umd.cs.findbugs.util.Multiset;
import edu.umd.cs.findbugs.util.Util;
/**
* @author pwilliam
*/
public class DBStats {
enum BUG_STATUS {ACCEPTED, ASSIGNED, FIXED, FIX_LATER, NEW,
VERIFIED, VERIFIER_ASSIGNED, WILL_NOT_FIX, DUPLICATE;
public static int score(String name) {
try {
BUG_STATUS value = valueOf(name);
return value.score();
} catch (RuntimeException e) {
return 0;
}
}
public static int stage(String name) {
try {
BUG_STATUS value = valueOf(name);
return value.stage();
} catch (RuntimeException e) {
return 0;
}
}
public int score() {
switch (this) {
case NEW:
return 0;
case ACCEPTED:
case DUPLICATE:
case WILL_NOT_FIX:
return 1;
case ASSIGNED:
case FIXED:
case FIX_LATER:
case VERIFIED:
case VERIFIER_ASSIGNED:
return 2;
default:
throw new IllegalStateException();
}
}
public int stage() {
switch (this) {
case NEW:
case DUPLICATE:
return 0;
case ASSIGNED:
return 1;
case WILL_NOT_FIX:
case ACCEPTED:
return 2;
case FIX_LATER:
return 3;
case FIXED:
return 4;
case VERIFIED:
case VERIFIER_ASSIGNED:
return 5;
default:
throw new IllegalStateException();
}
}
}
enum Rank { SCARIEST, SCARY, TROUBLING, OF_CONCERN, UNRANKED;
static Rank getRank(int rank) {
if (rank <= 4) return SCARIEST;
if (rank <= 9) return SCARY;
if (rank <= 14) return TROUBLING;
if (rank <= 20) return OF_CONCERN;
return UNRANKED;
}
}
static Timestamp bucketByHour(Timestamp t) {
Timestamp result = new Timestamp(t.getTime());
result.setSeconds(0);
result.setMinutes(0);
result.setNanos(0);
return result;
}
static class TimeSeries<K, V extends Comparable<? super V>> implements Comparable<TimeSeries<K,V>>{
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((k == null) ? 0 : k.hashCode());
result = prime * result + ((v == null) ? 0 : v.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof TimeSeries))
return false;
TimeSeries other = (TimeSeries) obj;
return Util.nullSafeEquals(this.k, other.k) && Util.nullSafeEquals(this.v, other.v);
}
final K k;
final int keyHash;
final V v;
public TimeSeries(K k, V v) {
this.k = k;
this.keyHash = System.identityHashCode(k);
this.v = v;
}
@Override
public String toString() {
return v + " " + k;
}
public int compareTo(TimeSeries<K, V> o) {
if (o == this) return 0;
int result = v.compareTo(o.v);
if (result != 0)
return result;
if (keyHash < o.keyHash)
return -1;
return 1;
}
}
public static void main(String args[]) throws Exception {
Map<String,String> officeLocation = new HashMap<String,String>();
URL u = PluginLoader.getCoreResource("offices.properties");
if (u != null) {
BufferedReader in = new BufferedReader(new InputStreamReader(u.openStream()));
while(true) {
String s = in.readLine();
if (s == null) break;
if (s.trim().length() == 0)
continue;
int x = s.indexOf(':');
if (x == -1)
continue;
String office = s.substring(0,x);
for(String person : s.substring(x+1).split(" "))
officeLocation.put(person, office);
}
in.close();
}
I18N i18n = I18N.instance();
// TODO: this this
DBCloud cloud = new DBCloud(null, null, new Properties());
cloud.initialize();
Connection c = cloud.getConnection();
Map<Integer,Rank> bugRank = new HashMap<Integer, Rank>();
Map<Integer,String> bugPattern = new HashMap<Integer, String>();
Map<String, Integer> detailedBugRank = new HashMap<String, Integer>();
PreparedStatement ps = c.prepareStatement("SELECT id, hash, bugPattern, priority FROM findbugs_issue");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
int col = 1;
int id = rs.getInt(col++);
String hash = rs.getString(col++);
String bugType = rs.getString(col++);
int priority = rs.getInt(col++);
BugPattern pattern = i18n.lookupBugPattern(bugType);
if (pattern != null) {
int rank = BugRanker.findRank(pattern, priority);
bugRank.put(id, Rank.getRank(rank));
detailedBugRank.put(hash, rank);
bugPattern.put(id, pattern.getType());
}
}
rs.close();
ps.close();
ps = c.prepareStatement("SELECT who, jvmLoadTime, findbugsLoadTime, analysisLoadTime, initialSyncTime, timestamp, numIssues"
+ " FROM findbugs_invocation");
MergeMap.MinMap <String, Timestamp> firstUse = new MergeMap.MinMap<String,Timestamp>();
MergeMap.MinMap <String, Timestamp> reviewers = new MergeMap.MinMap<String,Timestamp>();
MergeMap.MinMap <String, Timestamp> uniqueReviews = new MergeMap.MinMap<String,Timestamp>();
HashSet<String> participants = new HashSet<String>();
Multiset<String> invocations = new Multiset<String>();
Multiset<String> participantsPerOffice = new Multiset<String>(new TreeMap<String,Integer>());
rs = ps.executeQuery();
int invocationCount = 0;
long invocationTotal = 0;
long loadTotal = 0;
while (rs.next()) {
int col = 1;
String who = rs.getString(col++);
int jvmLoad = rs.getInt(col++);
int fbLoad = rs.getInt(col++);
int analysisLoad = rs.getInt(col++);
int dbSync = rs.getInt(col++);
Timestamp when = rs.getTimestamp(col++);
int numIssues = rs.getInt(col++);
invocationCount++;
invocationTotal += jvmLoad + fbLoad + analysisLoad + dbSync;
loadTotal += fbLoad + analysisLoad + dbSync;
firstUse.put(who, when);
if (numIssues > 3000)
invocations.add(who);
if (participants.add(who)) {
String office = officeLocation.get(who);
if (office == null)
office = "unknown";
participantsPerOffice.add(office);
}
}
rs.close();
ps.close();
ps = c.prepareStatement("SELECT id, issueId, who, designation, timestamp FROM findbugs_evaluation ORDER BY timestamp DESC");
rs = ps.executeQuery();
Multiset<String> issueReviewedBy = new Multiset<String>();
Multiset<String> allIssues = new Multiset<String>();
Multiset<String> scariestIssues = new Multiset<String>();
Multiset<String> scaryIssues = new Multiset<String>();
Multiset<String> troublingIssues = new Multiset<String>();
Multiset<Integer> scoreForIssue = new Multiset<Integer>();
Multiset<Integer> squareScoreForIssue = new Multiset<Integer>();
Multiset<Integer> reviewsForIssue = new Multiset<Integer>();
Multiset<Integer> scoredReviews = new Multiset<Integer>();
HashSet<String> issueReviews = new HashSet<String>();
HashSet<Integer> missingRank = new HashSet<Integer>();
while (rs.next()) {
int col = 1;
int id = rs.getInt(col++);
int issueId = rs.getInt(col++);
String who = rs.getString(col++);
String designation = rs.getString(col++);
UserDesignation d = UserDesignation.valueOf(designation);
designation = getDesignationTitle(i18n, d);
int score = d.score();
if (d != UserDesignation.OBSOLETE_CODE) {
scoreForIssue.add(issueId, score);
squareScoreForIssue.add(issueId, score*score);
scoredReviews.add(issueId);
}
reviewsForIssue.add(issueId);
Timestamp when = rs.getTimestamp(col++);
Rank rank = bugRank.get(issueId);
reviewers.put(who, when);
String issueReviewer = who+"-" + issueId;
if (issueReviews.add(issueReviewer)) {
uniqueReviews.put(issueReviewer, when);
allIssues.add(designation);
issueReviewedBy.add(who);
if (rank != null)
switch(rank) {
case SCARIEST:
scariestIssues.add(designation);
break;
case SCARY:
scaryIssues.add(designation);
break;
case TROUBLING:
troublingIssues.add(designation);
break;
}
else {
if (missingRank.add(id)) {
System.out.println("No rank for " + id);
}
}
}
}
rs.close();
ps.close();
PrintWriter scariestBugs = new PrintWriter("bugReportsForScariestIssues.csv");
scariestBugs.println("assignedTo,status,rank,note");
Multiset<String> bugStatus = new Multiset<String>();
HashSet<String> bugsSeen = new HashSet<String>();
Multiset<String> bugScore = new Multiset<String>();
FractionalMultiset<String> patternScore = new FractionalMultiset<String>();
Multiset<String> patternCount = new Multiset<String>();
FractionalMultiset<String> patternVariance = new FractionalMultiset<String>();
FractionalMultiset<Integer> issueVariance = new FractionalMultiset<Integer>();
FractionalMultiset<Integer> issueScore = new FractionalMultiset<Integer>();
Multiset<String> bugsFiled = new Multiset<String>();
ps = c.prepareStatement("SELECT bugReportId,hash,status, whoFiled,assignedTo, postmortem, timestamp FROM findbugs_bugreport ORDER BY timestamp DESC");
rs = ps.executeQuery();
while (rs.next()) {
int col = 1;
String id = rs.getString(col++);
String hash = rs.getString(col++);
String status = rs.getString(col++);
String who = rs.getString(col++);
String assignedTo = rs.getString(col++);
String postmortem = rs.getString(col++);
Timestamp when = rs.getTimestamp(col++);
if (!bugsSeen.add(id))
continue;
Integer rank = detailedBugRank.get(hash);
if (rank == null) {
System.out.println("Could not find hash " + hash + " for " +id);
}
if (assignedTo != null && !"NEW".equals(status)
&& (rank != null && rank <= 4 || postmortem != null)) {
if (postmortem != null)
scariestBugs.printf("%s,%s,%s,%d,POSTMORTEM%n", assignedTo, id, status, rank);
else
scariestBugs.printf("%s,%s,%s,%d%n", assignedTo, id, status, rank);
}
if (!id.equals(DBCloud.PENDING) && !id.equals(DBCloud.NONE)) {
bugStatus.add(status);
bugsFiled.add(who);
bugScore.add(who, BUG_STATUS.score(status));
}
}
rs.close();
ps.close();
c.close();
scariestBugs.close();
Multiset<String> overallEvaluation = new Multiset<String>();
for(Map.Entry<Integer,Integer> e : scoreForIssue.entrySet()) {
int value = e.getValue();
Integer issue = e.getKey();
int num = scoredReviews.getCount(issue);
if (num == 0)
continue;
double average = value / (double) num;
int score = (int) Math.round(average);
double square = squareScoreForIssue.getCount(issue) / (double) num;
double variance = square - average*average;
String pattern = bugPattern.get(issue);
patternCount.add(pattern);
patternScore.add(pattern, average);
patternVariance.add(pattern, variance);
issueVariance.add(issue, variance);
issueScore.add(issue, average);
// System.out.printf("%s %2d %2d%n", score, value, num);
overallEvaluation.add(
getDesignationTitle(i18n, getDesignationFromScore(score)));
}
patternScore.turnTotalIntoAverage(patternCount);
patternVariance.turnTotalIntoAverage(patternCount);
printPatterns("patternScore.csv", "average,variance,rank,count,pattern", patternScore, patternVariance, patternCount);
issueScore.turnTotalIntoAverage(reviewsForIssue);
issueVariance.turnTotalIntoAverage(reviewsForIssue);
PrintWriter out1 = new PrintWriter("issueVariance.csv");
out1.println("variance,average,count,key,pattern");
for(Map.Entry<Integer, Double> e1 : issueVariance.entriesInDecreasingOrder()) {
Integer key = e1.getKey();
int elementCount = reviewsForIssue.getCount(key);
Double v = e1.getValue();
if (elementCount >= 3 && v >= 0.5)
out1.printf("%3.1f,%3.1f,%d,%d,%s%n", v, issueScore.getValue(key), elementCount, key, bugPattern.get(key));
}
out1.close();
System.out.printf("%6d invocations%n", invocationCount);
System.out.printf("%6d invocations time (secs)%n", invocationTotal/invocationCount/1000);
System.out.printf("%6d load time (secs)%n", loadTotal/invocationCount/1000);
System.out.println();
printTimeSeries("users.csv", "Unique users", firstUse);
printTimeSeries("reviewers.csv", "Unique reviewers", reviewers);
printTimeSeries("reviews.csv", "Total reviews", uniqueReviews);
PrintWriter out = new PrintWriter("bug_status.csv");
out.println("Status,Number of bugs");
printMultiset(out, "Bug status", bugStatus);
out.close();
out = new PrintWriter("reviews_by_category.csv");
out.println("Category,Number of reviews");
printMultisetContents(out, "", allIssues);
out.close();
out = new PrintWriter("overall_review_of_issue.csv");
out.println("Category,Number of issues");
printMultisetContents(out, "", overallEvaluation);
out.close();
out = new PrintWriter("reviews_by_rank_and_category.csv");
out.println("Rank,Category,Number of reviews");
printMultisetContents(out, "Scariest,", scariestIssues);
printMultisetContents(out, "Scary,", scaryIssues);
printMultisetContents(out, "Troubling,", troublingIssues);
out.close();
out = new PrintWriter("bugs_filed.csv");
out.println("rank,bugs filed,who");
AbstractCloud.printLeaderBoard2(out, bugsFiled, 200, null, "%s,%s,%s\n", "participants per office");
out.close();
out = new PrintWriter("bug_score.csv");
out.println("rank,bug score,who");
AbstractCloud.printLeaderBoard2(out, bugScore, 200, null, "%s,%s,%s\n", "participants per office");
out.close();
out = new PrintWriter("most_participants_by_office.csv");
out.println("rank,participants,office");
AbstractCloud.printLeaderBoard2(out, participantsPerOffice, 100, null, "%s,%s,%s\n", "participants per office");
out.close();
out = new PrintWriter("most_issues_reviewed_individual.csv");
out.println("rank,reviews,reviewers");
AbstractCloud.printLeaderBoard2(out, issueReviewedBy, 10000, null, "%s,%s,%s\n", "num issues reviewed");
out.close();
}
private static void printPatterns(String filename, String header, FractionalMultiset<String> average, FractionalMultiset<String> variance, Multiset<String> count) throws FileNotFoundException {
I18N i18n = I18N.instance();
PrintWriter out = new PrintWriter(filename);
out.println(header);
for(Map.Entry<String, Double> e : average.entriesInDecreasingOrder()) {
String key = e.getKey();
BugPattern pattern = i18n.lookupBugPattern(key);
if (pattern != null)
out.printf("%1.1f,%1.1f,%d,%d,%s%n", e.getValue(), variance.getValue(key), BugRanker.findRank(pattern, 1), count.getCount(key), key);
}
out.close();
}
/**
* @param value
*/
private static UserDesignation getDesignationFromScore(int value) {
if (value <= -3)
return UserDesignation.BAD_ANALYSIS;
else switch(value) {
case -2:
return UserDesignation.NOT_A_BUG;
case -1:
return UserDesignation.MOSTLY_HARMLESS;
case 0:
return UserDesignation.NEEDS_STUDY;
case 1:
return UserDesignation.SHOULD_FIX;
default:
return UserDesignation.MUST_FIX;
}
}
/**
* @param i18n
* @param d
* @return
*/
private static String getDesignationTitle(I18N i18n, edu.umd.cs.findbugs.cloud.Cloud.UserDesignation d) {
String designation;
switch (d) {
case OBSOLETE_CODE:
designation= "obsolete code";
break;
case MUST_FIX:
designation= "Must fix";
break;
case SHOULD_FIX:
designation= "Should fix";
break;
default:
designation = i18n.getUserDesignation(d.name());
}
return designation;
}
/**
* @param out TODO
* @param allIssues
*/
private static void printMultiset(PrintWriter out, String title, Multiset<String> allIssues) {
printMultisetContents(out, "", allIssues);
}
/**
* @param allIssues
*/
private static void printMultisetContents(PrintWriter out, String prefix, Multiset<String> allIssues) {
for(Map.Entry<String, Integer> e : allIssues.entrySet())
out.printf("%s%s,%d%n", prefix, e.getKey(), e.getValue());
}
final static Date fixitStart = new Date("May 11, 2009");
private static void printTimeSeries(String filename, String title, MergeMap.MinMap<String, Timestamp> firstUse) throws FileNotFoundException {
PrintWriter out = new PrintWriter(filename);
out.println(title+",time,full time");
TreeSet<TimeSeries<String, Timestamp>> series = new TreeSet<TimeSeries<String, Timestamp>>();
for(Map.Entry<String, Timestamp> e : firstUse.entrySet()) {
series.add(new TimeSeries<String,Timestamp>(e.getKey(), e.getValue()));
}
Multiset<Timestamp> counter = new Multiset<Timestamp>(new TreeMap<Timestamp, Integer>());
for(TimeSeries<String, Timestamp> t : series) {
counter.add(bucketByHour(t.v));
}
int total = 0;
SimpleDateFormat format = new SimpleDateFormat("h a EEE");
SimpleDateFormat defaultFormat = new SimpleDateFormat();
for(Map.Entry<Timestamp, Integer> e : counter.entrySet()) {
Timestamp time = e.getKey();
total += e.getValue();
if (time.after(fixitStart))
out.printf("%d,%s,%s%n", total, format.format(time), defaultFormat.format(time));
}
out.close();
}
}