| /******************************************************************************* |
| * Copyright (c) 2004 - 2006 University Of British Columbia and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * University Of British Columbia - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.mylar.internal.monitor.reports.collectors; |
| |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.mylar.internal.core.util.DateUtil; |
| import org.eclipse.mylar.internal.core.util.MylarStatusHandler; |
| import org.eclipse.mylar.internal.monitor.reports.ReportGenerator; |
| import org.eclipse.mylar.internal.tasklist.ui.actions.TaskActivateAction; |
| import org.eclipse.mylar.internal.tasklist.ui.actions.TaskDeactivateAction; |
| import org.eclipse.mylar.provisional.core.InteractionEvent; |
| |
| /** |
| * Delagates to other collectors for additional info. |
| * |
| * @author Mik Kersten |
| */ |
| public class MylarUsageAnalysisCollector extends AbstractMylarUsageCollector { |
| |
| // public static final int BASELINE_EDITS_THRESHOLD = 400; |
| // private static final int MYLAR_EDITS_THRESHOLD = 1200; |
| |
| public static final int BASELINE_EDITS_THRESHOLD = 1000; |
| |
| private static final int MYLAR_EDITS_THRESHOLD = 3000; |
| |
| private static final int NUM_VIEWS_REPORTED = 5; |
| |
| private float summaryEditRatioDelta = 0; |
| |
| private final List<Integer> usersImproved = new ArrayList<Integer>(); |
| |
| private final List<Integer> usersDegraded = new ArrayList<Integer>(); |
| |
| private Map<Integer, Date> startDates = new HashMap<Integer, Date>(); |
| |
| private Map<Integer, Integer> numMylarActiveJavaEdits = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Date> endDates = new HashMap<Integer, Date>(); |
| |
| private Map<Integer, Integer> baselineSelections = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> baselineEdits = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> mylarInactiveSelections = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> mylarInactiveEdits = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> mylarSelections = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> mylarEdits = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> baselineCurrentNumSelectionsBeforeEdit = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> baselineTotalSelectionsBeforeEdit = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> baselineTotalEditsCounted = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> mylarCurrentNumSelectionsBeforeEdit = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> mylarTotalSelectionsBeforeEdit = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, Integer> mylarTotalEditsCounted = new HashMap<Integer, Integer>(); |
| |
| private Map<Integer, InteractionEvent> lastUserEvent = new HashMap<Integer, InteractionEvent>(); |
| |
| private Map<Integer, Long> timeMylarActive = new HashMap<Integer, Long>(); |
| |
| private Map<Integer, Long> timeMylarInactive = new HashMap<Integer, Long>(); |
| |
| private Map<Integer, Long> timeBaseline = new HashMap<Integer, Long>(); |
| |
| private MylarViewUsageCollector viewUsageCollector = new MylarViewUsageCollector(); |
| |
| public MylarUsageAnalysisCollector() { |
| viewUsageCollector.setMaxViewsToReport(NUM_VIEWS_REPORTED); |
| super.getDelegates().add(viewUsageCollector); |
| } |
| |
| public String getReportTitle() { |
| return "Mylar Usage"; |
| } |
| |
| public void consumeEvent(InteractionEvent event, int userId) { |
| super.consumeEvent(event, userId); |
| if (!startDates.containsKey(userId)) |
| startDates.put(userId, event.getDate()); |
| endDates.put(userId, event.getDate()); |
| |
| // Mylar is active |
| if (mylarUserIds.contains(userId) && !mylarInactiveUserIds.contains(userId)) { |
| accumulateDuration(event, userId, timeMylarActive); |
| if (isJavaEdit(event)) |
| incrementCount(userId, numMylarActiveJavaEdits); |
| if (isSelection(event)) { |
| incrementCount(userId, mylarSelections); |
| incrementCount(userId, mylarCurrentNumSelectionsBeforeEdit); |
| } else if (isEdit(event)) { |
| incrementCount(userId, mylarEdits); |
| |
| if (mylarCurrentNumSelectionsBeforeEdit.containsKey((userId))) { |
| int num = mylarCurrentNumSelectionsBeforeEdit.get(userId); |
| if (num > 0) { |
| incrementCount(userId, mylarTotalEditsCounted); |
| incrementCount(userId, mylarTotalSelectionsBeforeEdit, num); |
| mylarCurrentNumSelectionsBeforeEdit.put(userId, 0); |
| } |
| } |
| } |
| // Mylar is inactive |
| } else if (mylarInactiveUserIds.contains(userId)) { |
| accumulateDuration(event, userId, timeMylarInactive); |
| if (isSelection(event)) { |
| incrementCount(userId, mylarInactiveSelections); |
| } else if (isEdit(event)) { |
| incrementCount(userId, mylarInactiveEdits); |
| } |
| // Baseline |
| } else { |
| accumulateDuration(event, userId, timeBaseline); |
| if (isSelection(event)) { |
| incrementCount(userId, baselineSelections); |
| |
| incrementCount(userId, baselineCurrentNumSelectionsBeforeEdit); |
| } else if (isEdit(event)) { |
| incrementCount(userId, baselineEdits); |
| |
| if (baselineCurrentNumSelectionsBeforeEdit.containsKey((userId))) { |
| int num = baselineCurrentNumSelectionsBeforeEdit.get(userId); |
| if (num > 0) { |
| incrementCount(userId, baselineTotalEditsCounted); |
| incrementCount(userId, baselineTotalSelectionsBeforeEdit, num); |
| baselineCurrentNumSelectionsBeforeEdit.put(userId, 0); |
| } |
| } |
| } |
| } |
| } |
| |
| private void accumulateDuration(InteractionEvent event, int userId, Map<Integer, Long> timeAccumulator) { |
| // Restart accumulation if greater than 5 min has elapsed between events |
| if (lastUserEvent.containsKey(userId)) { |
| long elapsed = event.getDate().getTime() - lastUserEvent.get(userId).getDate().getTime(); |
| |
| if (elapsed < 5 * 60 * 1000) { |
| if (!timeAccumulator.containsKey(userId)) { |
| timeAccumulator.put(userId, new Long(0)); |
| } |
| timeAccumulator.put(userId, timeAccumulator.get(userId) + elapsed); |
| } |
| } |
| lastUserEvent.put(userId, event); |
| } |
| |
| public static boolean isEdit(InteractionEvent event) { |
| return event.getKind().equals(InteractionEvent.Kind.EDIT) |
| || (event.getKind().equals(InteractionEvent.Kind.SELECTION) && isSelectionInEditor(event)); |
| } |
| |
| public static boolean isSelection(InteractionEvent event) { |
| return event.getKind().equals(InteractionEvent.Kind.SELECTION) && !isSelectionInEditor(event); |
| } |
| |
| public static boolean isSelectionInEditor(InteractionEvent event) { |
| return event.getOriginId().contains("Editor") || event.getOriginId().contains("editor") |
| || event.getOriginId().contains("source"); |
| } |
| |
| public static boolean isJavaEdit(InteractionEvent event) { |
| return event.getKind().equals(InteractionEvent.Kind.EDIT) |
| && (event.getOriginId().contains("java") || event.getOriginId().contains("jdt.ui")); |
| } |
| |
| private void incrementCount(int userId, Map<Integer, Integer> map, int count) { |
| if (!map.containsKey(userId)) |
| map.put(userId, 0); |
| map.put(userId, map.get(userId) + count); |
| } |
| |
| private void incrementCount(int userId, Map<Integer, Integer> map) { |
| incrementCount(userId, map, 1); |
| } |
| |
| public List<String> getReport() { |
| usersImproved.clear(); |
| usersDegraded.clear(); |
| int acceptedUsers = 0; |
| int rejectedUsers = 0; |
| summaryEditRatioDelta = 0; |
| List<String> report = new ArrayList<String>(); |
| for (Iterator it = userIds.iterator(); it.hasNext();) { |
| int id = (Integer) it.next(); |
| if (acceptUser(id)) { |
| report.add("<h3>USER ID: " + id + " (from: " + getStartDate(id) + " to " + getEndDate(id) + ")</h3>"); |
| acceptedUsers++; |
| |
| float baselineRatio = getBaselineRatio(id); |
| float mylarInactiveRatio = getMylarInactiveRatio(id); |
| float mylarActiveRatio = getMylarRatio(id); |
| float combinedMylarRatio = mylarInactiveRatio + mylarActiveRatio; |
| |
| float ratioPercentage = (combinedMylarRatio - baselineRatio) / baselineRatio; |
| if (ratioPercentage > 0) { |
| usersImproved.add(id); |
| } else { |
| usersDegraded.add(id); |
| } |
| summaryEditRatioDelta += ratioPercentage; |
| String baselineVsMylarRatio = "Baseline vs. Mylar edit ratio: " + baselineRatio + ", mylar: " |
| + combinedMylarRatio + ", "; |
| String ratioChange = ReportGenerator.formatPercentage(100 * ratioPercentage); |
| baselineVsMylarRatio += " <b>change: " + ratioChange + "%</b>"; |
| report.add(baselineVsMylarRatio + "<br>"); |
| |
| report.add("<h4>Activity</h4>"); |
| float editsActive = getNumMylarEdits(id); |
| float editsInactive = getNumInactiveEdits(id); |
| report.add("Proportion Mylar active (by edits): <b>" |
| + ReportGenerator.formatPercentage(100 * ((editsActive) / (editsInactive + editsActive))) + "%</b><br>"); |
| |
| report.add("Elapsed time baseline: " + getTime(id, timeBaseline) + ", active: " |
| + getTime(id, timeMylarActive) + ", inactive: " + getTime(id, timeMylarInactive) + "<br>"); |
| |
| report.add("Selections baseline: " + getNumBaselineSelections(id) + ", Mylar active: " |
| + getNumMylarSelections(id) + ", inactive: " + getNumMylarInactiveSelections(id) + "<br>"); |
| report.add("Edits baseline: " + getNumBaselineEdits(id) + ", Mylar active: " + getNumMylarEdits(id) |
| + ", inactive: " + getNumInactiveEdits(id) + "<br>"); |
| |
| int numTaskActivations = commandUsageCollector.getCommands().getUserCount(id, TaskActivateAction.ID); |
| int numTaskDeactivations = commandUsageCollector.getCommands() |
| .getUserCount(id, TaskDeactivateAction.ID); |
| report.add("Task activations: " + numTaskActivations + ", "); |
| report.add("deactivations: " + numTaskDeactivations + "<br>"); |
| |
| report.addAll(viewUsageCollector.getSummary(id)); |
| report.add(ReportGenerator.SUMMARY_SEPARATOR); |
| } else { |
| rejectedUsers++; |
| } |
| } |
| report.add("<h3>Summary</h3>"); |
| String acceptedSummary = " (based on " + acceptedUsers + " accepted, " + rejectedUsers + " rejected users)"; |
| float percentage = summaryEditRatioDelta / (float) acceptedUsers; |
| String ratioChange = ReportGenerator.formatPercentage(100 * (percentage - 1)); |
| if (percentage >= 1) { |
| report.add("Overall edit ratio improved by: " + ratioChange + "% " + acceptedSummary + "<br>"); |
| } else { |
| report.add("Overall edit ratio degraded by: " + ratioChange + "% " + acceptedSummary + "<br>"); |
| } |
| report.add("degraded: " + usersDegraded.size() + ", improved: " + usersImproved.size() + "<br>"); |
| report.add(ReportGenerator.SUMMARY_SEPARATOR); |
| return report; |
| } |
| |
| public void exportAsCSVFile(String directory) { |
| FileWriter writer; |
| try { |
| writer = new FileWriter(directory + "/mylar-usage.csv"); |
| writer.write("userid, " + "ratio-baseline, ratio-mylar, " + "ratio-improvement, " |
| + "filtered-explorer, " + "filtered-outline, " + "filtered-problems, " |
| + "edits-active, " + "time-baseline, time-active, time-inactive, " |
| + "task-activations, task-deactivations, sel-interesting, sel-predicted, sel-decayed, sel-new, sel-unknown\n"); |
| // "filtered-explorer, filtered-outline, filtered-problems, "); |
| |
| for (Iterator it = userIds.iterator(); it.hasNext();) { |
| int userId = (Integer) it.next(); |
| |
| if (acceptUser(userId)) { |
| writer.write(userId + ", "); |
| float baselineRatio = getBaselineRatio(userId); |
| float mylarInactiveRatio = getMylarInactiveRatio(userId); |
| float mylarActiveRatio = getMylarRatio(userId); |
| float combinedMylarRatio = mylarInactiveRatio + mylarActiveRatio; |
| |
| writer.write(baselineRatio + ", "); |
| writer.write(combinedMylarRatio + ", "); |
| |
| float ratioPercentage = (combinedMylarRatio - baselineRatio) / baselineRatio; |
| writer.write(100 * ratioPercentage + ", "); |
| |
| Map<String, Integer> filteredViewSelections = viewUsageCollector.usersFilteredViewSelections.get(userId); |
| Map<String, Integer> normalViewSelections = viewUsageCollector.usersNormalViewSelections.get(userId); |
| |
| String[] views = new String[] { "org.eclipse.jdt.ui.PackageExplorer", "org.eclipse.ui.views.ContentOutline", "org.eclipse.ui.views.ProblemView"}; |
| for (int i = 0; i < views.length; i++) { |
| if (normalViewSelections.containsKey(views[i]) && filteredViewSelections.containsKey(views[i])) { |
| float normalSelections = normalViewSelections.get(views[i]); |
| float filteredSelections = filteredViewSelections.get(views[i]); |
| float ratio = filteredSelections / (normalSelections+filteredSelections); |
| // int unfilteredSelections = normalSelections - filteredSelections; |
| if (ratio >= 0.01) { |
| writer.write(ratio + ", "); |
| } else { |
| writer.write("0, "); |
| } |
| } else { |
| writer.write("0, "); |
| } |
| } |
| |
| float editsActive = getNumMylarEdits(userId); |
| float editsInactive = getNumInactiveEdits(userId); |
| writer.write(100 * ((editsActive) / (editsInactive + editsActive)) + ", "); |
| |
| writer.write(getTime(userId, timeBaseline) + ", "); |
| writer.write(getTime(userId, timeMylarActive) + ", "); |
| writer.write(getTime(userId, timeMylarInactive) + ", "); |
| |
| int numTaskActivations = commandUsageCollector.getCommands() |
| .getUserCount(userId, TaskActivateAction.ID); |
| int numTaskDeactivations = commandUsageCollector.getCommands().getUserCount(userId, |
| TaskDeactivateAction.ID); |
| writer.write(numTaskActivations + ", "); |
| writer.write(numTaskDeactivations + ", "); |
| |
| |
| int numNew = 0; |
| if (viewUsageCollector.usersNumNew.containsKey(userId)) |
| numNew = viewUsageCollector.usersNumNew.get(userId); |
| int numPredicted = 0; |
| if (viewUsageCollector.usersNumPredicted.containsKey(userId)) |
| numPredicted = viewUsageCollector.usersNumPredicted.get(userId); |
| int numInteresting = 0; |
| if (viewUsageCollector.usersNumDefault.containsKey(userId)) |
| numInteresting = viewUsageCollector.usersNumDefault.get(userId); |
| int numDecayed = 0; |
| if (viewUsageCollector.usersNumDecayed.containsKey(userId)) |
| numDecayed = viewUsageCollector.usersNumDecayed.get(userId); |
| int numUnknown = 0; |
| if (viewUsageCollector.usersNumUnknown.containsKey(userId)) |
| numUnknown = viewUsageCollector.usersNumUnknown.get(userId); |
| |
| // float numSelections = numNew + numPredicted + numInteresting + numDecayed + numUnknown; |
| // writer.write(numSelections + ", "); |
| writer.write(numInteresting + ", "); |
| writer.write(numPredicted + ", "); |
| writer.write(numDecayed + ", "); |
| writer.write(numNew + ", "); |
| writer.write(numUnknown + ", "); |
| |
| writer.write("\n"); |
| } |
| } |
| writer.close(); |
| } catch (IOException e) { |
| MylarStatusHandler.fail(e, "could not generate csv file", true); |
| } |
| } |
| |
| private String getTime(int id, Map<Integer, Long> timeMap) { |
| if (timeMap.containsKey(id)) { |
| long timeInSeconds = timeMap.get(id) / 1000; |
| long hours, minutes; |
| hours = timeInSeconds / 3600; |
| timeInSeconds = timeInSeconds - (hours * 3600); |
| minutes = timeInSeconds / 60; |
| timeInSeconds = timeInSeconds - (minutes * 60); |
| return hours + "." + minutes; |
| } else { |
| return "0"; |
| } |
| } |
| |
| public boolean acceptUser(int id) { |
| |
| if (!numMylarActiveJavaEdits.containsKey(id)) { |
| return false; |
| } else { |
| return getNumBaselineEdits(id) > BASELINE_EDITS_THRESHOLD && getNumMylarEdits(id) > MYLAR_EDITS_THRESHOLD; |
| } |
| } |
| |
| public String getStartDate(int id) { |
| Calendar start = Calendar.getInstance(); |
| start.setTime(startDates.get(id)); |
| return DateUtil.getFormattedDate(start); |
| } |
| |
| public String getEndDate(int id) { |
| Calendar end = Calendar.getInstance(); |
| end.setTime(endDates.get(id)); |
| return DateUtil.getFormattedDate(end); |
| } |
| |
| public int getNumBaselineSelections(int id) { |
| if (baselineSelections.containsKey(id)) { |
| return baselineSelections.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumBaselineEdits(int id) { |
| if (baselineEdits.containsKey(id)) { |
| return baselineEdits.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumMylarEdits(int id) { |
| if (mylarEdits.containsKey(id)) { |
| return mylarEdits.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumMylarInactiveEdits(int id) { |
| if (mylarInactiveEdits.containsKey(id)) { |
| return mylarInactiveEdits.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumInactiveEdits(int id) { |
| if (mylarInactiveEdits.containsKey(id)) { |
| return mylarInactiveEdits.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumMylarInactiveSelections(int id) { |
| if (mylarInactiveSelections.containsKey(id)) { |
| return mylarInactiveSelections.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumMylarSelections(int id) { |
| if (mylarSelections.containsKey(id)) { |
| return mylarSelections.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Public for testing. |
| */ |
| public float getBaselineRatio(int id) { |
| return getEditRatio(id, baselineEdits, baselineSelections); |
| } |
| |
| public float getMylarInactiveRatio(int id) { |
| return getEditRatio(id, mylarInactiveEdits, mylarInactiveSelections); |
| } |
| |
| /** |
| * Public for testing. |
| */ |
| public float getMylarRatio(int id) { |
| return getEditRatio(id, mylarEdits, mylarSelections); |
| } |
| |
| private float getEditRatio(int id, Map<Integer, Integer> edits, Map<Integer, Integer> selections) { |
| if (edits.containsKey(id) && selections.containsKey(id)) { |
| return (float) edits.get(id) / (float) selections.get(id); |
| } else { |
| return 0f; |
| } |
| } |
| } |
| |
| // int numIncrements = commandUsageCollector.getCommands().getUserCount(id, |
| // "org.eclipse.mylar.internal.ui.actions.InterestIncrementAction"); |
| // int numDecrements = commandUsageCollector.getCommands().getUserCount(id, |
| // "org.eclipse.mylar.internal.ui.actions.InterestDecrementAction"); |
| // report.add("Interest increments: " + numIncrements + ", decrements: " + |
| // numDecrements + "<br>"); |
| |
| // float inactivePercentage = (mylarActiveRatio-mylarInactiveRatio) / |
| // mylarInactiveRatio; |
| // String inactiveRatioChange = formatPercentage(100*(inactivePercentage)); |
| // mylarInactiveDelta += inactivePercentage; |
| // String inactiveVsActiveRatio = ""; |
| // inactiveVsActiveRatio += "Inactive vs. Active edit ratio: " + |
| // mylarInactiveRatio + ", mylar: " + mylarActiveRatio + ", "; |
| // inactiveVsActiveRatio += " <b>change: " + inactiveRatioChange + "%</b>"; |
| // report.add(inactiveVsActiveRatio + "<br>"); |
| |
| // if (baselineTotalSelectionsBeforeEdit.containsKey(id) && |
| // mylarTotalSelectionsBeforeEdit.containsKey(id)) { |
| // float baselineRuns = |
| // (float)baselineTotalSelectionsBeforeEdit.get(id) / |
| // (float)baselineTotalEditsCounted.get(id); |
| // |
| // float mylarRuns = |
| // (float)mylarTotalSelectionsBeforeEdit.get(id) / |
| // (float)mylarTotalEditsCounted.get(id); |
| // |
| // float runsPercentage = (mylarRuns-baselineRuns) / baselineRuns; |
| // String runsChange = formatPercentage(-100*runsPercentage); |
| // report.add("Avg baseline selections before edit: " + baselineRuns |
| // + " vs. mylar: " + mylarRuns |
| // + " <b>change: " + runsChange + "</b><br>");; |
| // } |
| |
| // String[] REJECTED_IDs = { |
| // "org.eclipse.mylar.java.ui.editor.MylarCompilationUnitEditor", |
| // "org.eclipse.jdt.ui.CompilationUnitEditor", |
| // "org.eclipse.jdt.ui.DefaultTextEditor", |
| // "org.eclipse.jdt.ui.ClassFileEditor" |
| // }; |
| |
| // String[] ACCEPTED_EDITORS = { |
| // "org.eclipse.mylar.java.ui.editor.MylarCompilationUnitEditor", |
| // "org.eclipse.jdt.ui.CompilationUnitEditor" |
| // }; |
| // String originId = event.getOriginId(); |
| // if (originId == null) return false; |
| |