| /******************************************************************************* |
| * Copyright (c) 2004, 2008 Tasktop Technologies 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: |
| * Tasktop Technologies - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.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.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.mylyn.commons.core.DateUtil; |
| import org.eclipse.mylyn.commons.core.StatusHandler; |
| import org.eclipse.mylyn.internal.monitor.reports.MonitorReportsPlugin; |
| import org.eclipse.mylyn.internal.monitor.usage.ReportGenerator; |
| import org.eclipse.mylyn.internal.tasks.ui.actions.TaskActivateAction; |
| import org.eclipse.mylyn.internal.tasks.ui.actions.TaskDeactivateAction; |
| import org.eclipse.mylyn.monitor.core.InteractionEvent; |
| |
| /** |
| * Delegates to other collectors for additional info. |
| * |
| * @author Mik Kersten |
| */ |
| public class FocusedUiUsageAnalysisCollector extends AbstractMylynUsageCollector { |
| |
| public static final int BASELINE_EDITS_THRESHOLD = 1000; |
| |
| private static final int MYLYN_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 final Map<Integer, Date> startDates = new HashMap<Integer, Date>(); |
| |
| private final Map<Integer, Integer> numMylynActiveJavaEdits = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Date> endDates = new HashMap<Integer, Date>(); |
| |
| private final Map<Integer, Integer> baselineSelections = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> baselineEdits = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> mylynInactiveSelections = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> mylynInactiveEdits = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> mylynSelections = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> mylynEdits = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> baselineCurrentNumSelectionsBeforeEdit = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> baselineTotalSelectionsBeforeEdit = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> baselineTotalEditsCounted = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> mylynCurrentNumSelectionsBeforeEdit = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> mylynTotalSelectionsBeforeEdit = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, Integer> mylynTotalEditsCounted = new HashMap<Integer, Integer>(); |
| |
| private final Map<Integer, InteractionEvent> lastUserEvent = new HashMap<Integer, InteractionEvent>(); |
| |
| private final Map<Integer, Long> timeMylynActive = new HashMap<Integer, Long>(); |
| |
| private final Map<Integer, Long> timeMylynInactive = new HashMap<Integer, Long>(); |
| |
| private final Map<Integer, Long> timeBaseline = new HashMap<Integer, Long>(); |
| |
| private final FocusedUiViewUsageCollector viewUsageCollector = new FocusedUiViewUsageCollector(); |
| |
| public FocusedUiUsageAnalysisCollector() { |
| viewUsageCollector.setMaxViewsToReport(NUM_VIEWS_REPORTED); |
| super.getDelegates().add(viewUsageCollector); |
| } |
| |
| @Override |
| public String getReportTitle() { |
| return "Mylyn Usage"; |
| } |
| |
| @Override |
| 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()); |
| |
| // Mylyn is active |
| if (mylynUserIds.contains(userId) && !mylynInactiveUserIds.contains(userId)) { |
| accumulateDuration(event, userId, timeMylynActive); |
| if (isJavaEdit(event)) { |
| incrementCount(userId, numMylynActiveJavaEdits); |
| } |
| if (isSelection(event)) { |
| incrementCount(userId, mylynSelections); |
| incrementCount(userId, mylynCurrentNumSelectionsBeforeEdit); |
| } else if (isEdit(event)) { |
| incrementCount(userId, mylynEdits); |
| |
| if (mylynCurrentNumSelectionsBeforeEdit.containsKey((userId))) { |
| int num = mylynCurrentNumSelectionsBeforeEdit.get(userId); |
| if (num > 0) { |
| incrementCount(userId, mylynTotalEditsCounted); |
| incrementCount(userId, mylynTotalSelectionsBeforeEdit, num); |
| mylynCurrentNumSelectionsBeforeEdit.put(userId, 0); |
| } |
| } |
| } |
| // Mylyn is inactive |
| } else if (mylynInactiveUserIds.contains(userId)) { |
| accumulateDuration(event, userId, timeMylynInactive); |
| if (isSelection(event)) { |
| incrementCount(userId, mylynInactiveSelections); |
| } else if (isEdit(event)) { |
| incrementCount(userId, mylynInactiveEdits); |
| } |
| // 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); |
| } |
| |
| @Override |
| public List<String> getReport() { |
| usersImproved.clear(); |
| usersDegraded.clear(); |
| int acceptedUsers = 0; |
| int rejectedUsers = 0; |
| summaryEditRatioDelta = 0; |
| List<String> report = new ArrayList<String>(); |
| for (int id : userIds) { |
| if (acceptUser(id)) { |
| report.add("<h3>USER ID: " + id + " (from: " + getStartDate(id) + " to " + getEndDate(id) + ")</h3>"); |
| acceptedUsers++; |
| |
| float baselineRatio = getBaselineRatio(id); |
| float mylynInactiveRatio = getMylynInactiveRatio(id); |
| float mylynActiveRatio = getMylynRatio(id); |
| float combinedMylynRatio = mylynInactiveRatio + mylynActiveRatio; |
| |
| float ratioPercentage = (combinedMylynRatio - baselineRatio) / baselineRatio; |
| if (ratioPercentage > 0) { |
| usersImproved.add(id); |
| } else { |
| usersDegraded.add(id); |
| } |
| summaryEditRatioDelta += ratioPercentage; |
| String baselineVsMylynRatio = "Baseline vs. Mylyn edit ratio: " + baselineRatio + ", mylyn: " |
| + combinedMylynRatio + ", "; |
| String ratioChange = ReportGenerator.formatPercentage(100 * ratioPercentage); |
| baselineVsMylynRatio += " <b>change: " + ratioChange + "%</b>"; |
| report.add(baselineVsMylynRatio + "<br>"); |
| |
| report.add("<h4>Activity</h4>"); |
| float editsActive = getNumMylynEdits(id); |
| float editsInactive = getNumInactiveEdits(id); |
| report.add("Proportion Mylyn active (by edits): <b>" |
| + ReportGenerator.formatPercentage(100 * ((editsActive) / (editsInactive + editsActive))) |
| + "%</b><br>"); |
| |
| report.add("Elapsed time baseline: " + getTime(id, timeBaseline) + ", active: " |
| + getTime(id, timeMylynActive) + ", inactive: " + getTime(id, timeMylynInactive) + "<br>"); |
| |
| report.add("Selections baseline: " + getNumBaselineSelections(id) + ", Mylyn active: " |
| + getNumMylynSelections(id) + ", inactive: " + getNumMylynInactiveSelections(id) + "<br>"); |
| report.add("Edits baseline: " + getNumBaselineEdits(id) + ", Mylyn active: " + getNumMylynEdits(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>"); |
| |
| int numIncrement = commandUsageCollector.getCommands().getUserCount(id, |
| "org.eclipse.mylyn.ui.interest.increment"); |
| int numDecrement = commandUsageCollector.getCommands().getUserCount(id, |
| "org.eclipse.mylyn.ui.interest.decrement"); |
| report.add("Interest increments: " + numIncrement + ", "); |
| report.add("Interest decrements: " + numDecrement + "<br>"); |
| |
| report.addAll(viewUsageCollector.getSummary(id, true)); |
| report.add(ReportGenerator.SUMMARY_SEPARATOR); |
| } else { |
| rejectedUsers++; |
| } |
| } |
| report.add("<h3>Summary</h3>"); |
| String acceptedSummary = " (based on " + acceptedUsers + " accepted, " + rejectedUsers + " rejected users)"; |
| float percentage = summaryEditRatioDelta / 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; |
| } |
| |
| @Override |
| public void exportAsCSVFile(String directory) { |
| FileWriter writer; |
| try { |
| writer = new FileWriter(directory + "/mylyn-usage.csv"); |
| writer.write("userid, " |
| + "ratio-baseline, ratio-mylyn, " |
| + "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 (int userId : userIds) { |
| if (acceptUser(userId)) { |
| writer.write(userId + ", "); |
| float baselineRatio = getBaselineRatio(userId); |
| float mylynInactiveRatio = getMylynInactiveRatio(userId); |
| float mylynActiveRatio = getMylynRatio(userId); |
| float combinedMylynRatio = mylynInactiveRatio + mylynActiveRatio; |
| |
| writer.write(baselineRatio + ", "); |
| writer.write(combinedMylynRatio + ", "); |
| |
| float ratioPercentage = (combinedMylynRatio - baselineRatio) / baselineRatio; |
| writer.write(100 * ratioPercentage + ", "); |
| |
| Map<String, Integer> filteredViewSelections = viewUsageCollector.usersFilteredViewSelections.get(userId); |
| Map<String, Integer> normalViewSelections = viewUsageCollector.getUsersNormalViewSelections().get( |
| userId); |
| |
| String[] views = new String[] { "org.eclipse.jdt.ui.PackageExplorer", |
| "org.eclipse.ui.views.ContentOutline", "org.eclipse.ui.views.ProblemView" }; |
| for (String view : views) { |
| if (normalViewSelections.containsKey(view) && filteredViewSelections.containsKey(view)) { |
| float normalSelections = normalViewSelections.get(view); |
| float filteredSelections = filteredViewSelections.get(view); |
| 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 = getNumMylynEdits(userId); |
| float editsInactive = getNumInactiveEdits(userId); |
| writer.write(100 * ((editsActive) / (editsInactive + editsActive)) + ", "); |
| |
| writer.write(getTime(userId, timeBaseline) + ", "); |
| writer.write(getTime(userId, timeMylynActive) + ", "); |
| writer.write(getTime(userId, timeMylynInactive) + ", "); |
| |
| 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) { |
| StatusHandler.log(new Status(IStatus.ERROR, MonitorReportsPlugin.ID_PLUGIN, "Could not generate csv file", |
| e)); |
| } |
| } |
| |
| 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) { |
| // XXX: delete |
| // int[] ACCEPTED = { |
| // 1922, |
| // 970, |
| // 1650, |
| // 1548, |
| // 1565, |
| // 1752, |
| // 2194, |
| // 2364, |
| // 1735, |
| // 936, |
| // 1803, |
| // 2007, |
| // 1208, |
| // 1684, |
| // 919, |
| // 2041, |
| // 1174 |
| // }; |
| // for (int i : ACCEPTED) { |
| // if (i == id) return true; |
| // } |
| // return false; |
| if (!numMylynActiveJavaEdits.containsKey(id)) { |
| return false; |
| } else { |
| return getNumBaselineEdits(id) > BASELINE_EDITS_THRESHOLD && getNumMylynEdits(id) > MYLYN_EDITS_THRESHOLD; |
| } |
| } |
| |
| public String getStartDate(int id) { |
| Calendar start = Calendar.getInstance(); |
| start.setTime(startDates.get(id)); |
| return DateUtil.getIsoFormattedDate(start); |
| } |
| |
| public String getEndDate(int id) { |
| Calendar end = Calendar.getInstance(); |
| end.setTime(endDates.get(id)); |
| return DateUtil.getIsoFormattedDate(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 getNumMylynEdits(int id) { |
| if (mylynEdits.containsKey(id)) { |
| return mylynEdits.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumMylynInactiveEdits(int id) { |
| if (mylynInactiveEdits.containsKey(id)) { |
| return mylynInactiveEdits.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumInactiveEdits(int id) { |
| if (mylynInactiveEdits.containsKey(id)) { |
| return mylynInactiveEdits.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumMylynInactiveSelections(int id) { |
| if (mylynInactiveSelections.containsKey(id)) { |
| return mylynInactiveSelections.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| public int getNumMylynSelections(int id) { |
| if (mylynSelections.containsKey(id)) { |
| return mylynSelections.get(id); |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Public for testing. |
| */ |
| public float getBaselineRatio(int id) { |
| return getEditRatio(id, baselineEdits, baselineSelections); |
| } |
| |
| public float getMylynInactiveRatio(int id) { |
| return getEditRatio(id, mylynInactiveEdits, mylynInactiveSelections); |
| } |
| |
| /** |
| * Public for testing. |
| */ |
| public float getMylynRatio(int id) { |
| return getEditRatio(id, mylynEdits, mylynSelections); |
| } |
| |
| 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; |
| } |
| } |
| } |