| /******************************************************************************* |
| * Copyright (c) 2004, 2009 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.usage; |
| |
| import java.io.File; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.IJobChangeListener; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.mylyn.commons.core.StatusHandler; |
| import org.eclipse.mylyn.internal.monitor.core.collection.IUsageCollector; |
| import org.eclipse.mylyn.internal.monitor.core.collection.IUsageScanner; |
| import org.eclipse.mylyn.internal.monitor.core.collection.InteractionEventComparator; |
| import org.eclipse.mylyn.internal.monitor.core.collection.InteractionEventSummary; |
| import org.eclipse.mylyn.internal.monitor.core.collection.InteractionEventUtil; |
| import org.eclipse.mylyn.monitor.core.InteractionEvent; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.progress.IProgressService; |
| |
| /** |
| * Used for generating reports of user activity. |
| * |
| * @author Mik Kersten |
| * @since 3.0 |
| */ |
| public class ReportGenerator { |
| |
| public static final String SUMMARY_SEPARATOR = "<hr><br>"; //$NON-NLS-1$ |
| |
| private final InteractionEventLogger logger; |
| |
| private UsageStatisticsSummary lastParsedSummary = null; |
| |
| private final Set<Integer> userIds = new HashSet<Integer>(); |
| |
| private final List<IUsageCollector> collectors; |
| |
| private List<IUsageScanner> scanners; |
| |
| private Map<Integer, Map<String, SortedSet<InteractionEvent>>> allUserEvents; |
| |
| private boolean saveAllUserEvents = false; |
| |
| private IJobChangeListener listener = null; |
| |
| private boolean forceSyncForTesting = false; |
| |
| public ReportGenerator(InteractionEventLogger logger, IUsageCollector collector, boolean saveAllUserEvents) { |
| this(logger, collector); |
| this.saveAllUserEvents = saveAllUserEvents; |
| } |
| |
| public ReportGenerator(InteractionEventLogger logger, List<IUsageCollector> collectors, boolean saveAllUserEvents) { |
| this(logger, collectors); |
| this.saveAllUserEvents = saveAllUserEvents; |
| } |
| |
| public ReportGenerator(InteractionEventLogger logger, IUsageCollector collector) { |
| List<IUsageCollector> collectors = new ArrayList<IUsageCollector>(); |
| collectors.add(collector); |
| this.logger = logger; |
| this.collectors = collectors; |
| } |
| |
| public ReportGenerator(InteractionEventLogger logger, List<IUsageCollector> collectors) { |
| this.logger = logger; |
| this.collectors = collectors; |
| } |
| |
| public ReportGenerator(InteractionEventLogger logger, List<IUsageCollector> collectors, |
| List<IUsageScanner> scanners, IJobChangeListener listener) { |
| this(logger, collectors); |
| this.scanners = scanners; |
| } |
| |
| public ReportGenerator(InteractionEventLogger logger, IUsageCollector collector, IJobChangeListener listener) { |
| this(logger, collector); |
| this.listener = listener; |
| } |
| |
| public ReportGenerator(InteractionEventLogger logger, List<IUsageCollector> collectors, |
| IJobChangeListener listener, boolean forceSyncForTesting) { |
| this(logger, collectors); |
| this.listener = listener; |
| this.forceSyncForTesting = forceSyncForTesting; |
| } |
| |
| public void setScanners(List<IUsageScanner> scanners) { |
| this.scanners = scanners; |
| } |
| |
| // XXX clean up code and methods |
| public void getStatisticsFromInteractionHistory(File source, IJobChangeListener listener) { |
| List<File> sources = new ArrayList<File>(); |
| sources.add(source); |
| getStatisticsFromInteractionHistories(sources, listener); |
| } |
| |
| public void getStatisticsFromInteractionHistories(List<File> sources, IProgressMonitor monitor) { |
| final GenerateStatisticsJob job = new GenerateStatisticsJob(this, sources); |
| job.run(monitor); |
| } |
| |
| public void getStatisticsFromInteractionHistories(List<File> sources, IJobChangeListener jobChangeListener) { |
| |
| final GenerateStatisticsJob job = new GenerateStatisticsJob(this, sources); |
| if (jobChangeListener == null) { |
| IProgressService service = PlatformUI.getWorkbench().getProgressService(); |
| try { |
| service.run(true, false, new IRunnableWithProgress() { |
| |
| public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { |
| job.run(monitor); |
| } |
| |
| }); |
| } catch (InvocationTargetException e) { |
| StatusHandler.log(new Status(IStatus.ERROR, UiUsageMonitorPlugin.ID_PLUGIN, e.getMessage(), e)); |
| } catch (InterruptedException e) { |
| StatusHandler.log(new Status(IStatus.ERROR, UiUsageMonitorPlugin.ID_PLUGIN, e.getMessage(), e)); |
| } |
| |
| } else { |
| |
| if (jobChangeListener != null) { |
| job.addJobChangeListener(jobChangeListener); |
| } |
| if (this.listener != null) { |
| job.addJobChangeListener(this.listener); |
| } |
| job.setPriority(Job.LONG); |
| if (forceSyncForTesting) { |
| job.run(new NullProgressMonitor()); |
| } else { |
| job.schedule(); |
| } |
| } |
| |
| } |
| |
| public UsageStatisticsSummary getLastParsedSummary() { |
| return lastParsedSummary; |
| } |
| |
| /** |
| * Assuming the file naming convention of <phase>-<version>-usage-<userID>-<date and time>.zip |
| */ |
| private int getUserId(File source) { |
| String userIDText = source.getName(); |
| int userId = -1; |
| String prefix = "-usage-"; //$NON-NLS-1$ |
| |
| if (source.getName().indexOf(prefix) >= 0) { |
| try { |
| userIDText = userIDText.substring(userIDText.indexOf(prefix) + prefix.length(), userIDText.length()); |
| userIDText = userIDText.substring(0, userIDText.indexOf("-")); //$NON-NLS-1$ |
| userId = Integer.valueOf(userIDText); |
| } catch (Throwable t) { |
| StatusHandler.log(new Status(IStatus.ERROR, UiUsageMonitorPlugin.ID_PLUGIN, |
| "Could not parse user ID from source file", t)); //$NON-NLS-1$ |
| } |
| } |
| |
| return userId; |
| } |
| |
| private String getPhase(File source) { |
| String userIDText = source.getName(); |
| String phase = "unknown"; //$NON-NLS-1$ |
| String terminator = "-"; //$NON-NLS-1$ |
| |
| if (source.getName().indexOf(terminator) >= 0) { |
| try { |
| phase = userIDText.substring(0, userIDText.indexOf(terminator) - 1); |
| } catch (Throwable t) { |
| StatusHandler.log(new Status(IStatus.ERROR, UiUsageMonitorPlugin.ID_PLUGIN, |
| "Could not parse user ID from source file", t)); //$NON-NLS-1$ |
| } |
| } |
| return phase; |
| } |
| |
| class GenerateStatisticsJob extends Job { |
| |
| private final ReportGenerator generator; |
| |
| private final List<File> sources; |
| |
| public GenerateStatisticsJob(ReportGenerator generator, List<File> sources) { |
| super(Messages.ReportGenerator_Generate_Statistics_Job); |
| this.generator = generator; |
| this.sources = sources; |
| } |
| |
| @Override |
| public IStatus run(IProgressMonitor monitor) { |
| |
| if (saveAllUserEvents) { |
| allUserEvents = new HashMap<Integer, Map<String, SortedSet<InteractionEvent>>>(); |
| } |
| |
| UsageStatisticsSummary statistics = new UsageStatisticsSummary(); |
| Map<Integer, Map<String, InteractionEventSummary>> summaryMap = new HashMap<Integer, Map<String, InteractionEventSummary>>(); |
| |
| Map<Integer, List<File>> filesPerUser = new HashMap<Integer, List<File>>(); |
| try { |
| |
| // Go through the files to determine which users we have to |
| // process (by user id) |
| for (File source : sources) { |
| int userId = getUserId(source); |
| userIds.add(userId); |
| List<File> filesForUser = null; |
| if (filesPerUser.containsKey(userId)) { |
| filesForUser = filesPerUser.get(userId); |
| } |
| if (filesForUser == null) { |
| filesForUser = new ArrayList<File>(); |
| } |
| filesForUser.add(source); |
| filesPerUser.put(userId, filesForUser); |
| } |
| } catch (Throwable t) { |
| StatusHandler.log(new Status(IStatus.ERROR, UiUsageMonitorPlugin.ID_PLUGIN, |
| "Could not generate usage report", t)); //$NON-NLS-1$ |
| } |
| |
| try { |
| // There are three processing events per user |
| monitor.beginTask(Messages.ReportGenerator_Mylyn_Usage_Summary_Generation, userIds.size() * 3); |
| |
| // Process the files for each user |
| for (Integer aUser : filesPerUser.keySet()) { |
| Map<String, SortedSet<InteractionEvent>> userEvents = new HashMap<String, SortedSet<InteractionEvent>>(); |
| |
| for (File aFile : filesPerUser.get(aUser)) { |
| String phase = getPhase(aFile); |
| |
| // orderedEvents must be a set because the |
| // monitor-log.xml file contains some duplicate |
| // events and we want to be sure that we ignore the |
| // duplicates in the reporting. Sets |
| // cannot contain duplicates, so orderedEvents will only |
| // accept the unique events. |
| SortedSet<InteractionEvent> orderedEvents; |
| if (userEvents.get(phase) == null) { |
| orderedEvents = new TreeSet<InteractionEvent>(new InteractionEventComparator()); |
| orderedEvents.addAll(this.generator.logger.getHistoryFromFile(aFile)); |
| } else { |
| orderedEvents = userEvents.get(phase); |
| orderedEvents.addAll(this.generator.logger.getHistoryFromFile(aFile)); |
| } |
| userEvents.put(phase, orderedEvents); |
| } |
| monitor.worked(1); |
| |
| // If there are scanners registered, give each event to each |
| // scanner in turn |
| if (this.generator.scanners != null && this.generator.scanners.size() > 0) { |
| |
| for (Map.Entry<String, SortedSet<InteractionEvent>> eventsPerPhase : userEvents.entrySet()) { |
| // String phaseToProcess = eventsPerPhase.getKey(); |
| SortedSet<InteractionEvent> events = eventsPerPhase.getValue(); |
| |
| for (InteractionEvent event : events) { |
| for (IUsageScanner scanner : this.generator.scanners) { |
| scanner.scanEvent(event, aUser); |
| } |
| } |
| } |
| } |
| monitor.worked(1); |
| |
| if (allUserEvents != null) { |
| allUserEvents.put(aUser, userEvents); |
| } |
| |
| // Consume all events |
| for (Map.Entry<String, SortedSet<InteractionEvent>> eventsPerPhase : userEvents.entrySet()) { |
| // String phaseToProcess = eventsPerPhase.getKey(); |
| SortedSet<InteractionEvent> events = eventsPerPhase.getValue(); |
| |
| for (InteractionEvent event : events) { |
| |
| if (event.getKind().isUserEvent()) { // TODO: |
| // some |
| // collectors |
| // may want |
| // non-user |
| // events |
| for (IUsageCollector collector : this.generator.collectors) { |
| collector.consumeEvent(event, aUser); |
| } |
| } |
| createUsageTableData(summaryMap, event, aUser); |
| } |
| } |
| monitor.worked(1); |
| } |
| |
| for (IUsageCollector collector : this.generator.collectors) { |
| statistics.add(collector); |
| } |
| |
| // Flatten the summaries for the command usage table |
| List<InteractionEventSummary> flattenedSummaries = new ArrayList<InteractionEventSummary>(); |
| Map<String, InteractionEventSummary> combinedUserSummary = new HashMap<String, InteractionEventSummary>(); |
| |
| // Go through the summary for each user and combine the |
| // information into a table sorted by the command |
| for (Map<String, InteractionEventSummary> userSummary : summaryMap.values()) { |
| for (InteractionEventSummary aSummary : userSummary.values()) { |
| if (!combinedUserSummary.containsKey(aSummary.getName())) { |
| combinedUserSummary.put(aSummary.getName(), new InteractionEventSummary(aSummary)); |
| } else { |
| // Could be simplified; but written this way for |
| // clarity |
| InteractionEventSummary combinedSummary = combinedUserSummary.get(aSummary.getName()); |
| combinedSummary.combine(aSummary); |
| combinedUserSummary.put(aSummary.getName(), combinedSummary); |
| } |
| } |
| |
| } |
| |
| flattenedSummaries.addAll(combinedUserSummary.values()); |
| |
| statistics.setSingleSummaries(flattenedSummaries); |
| this.generator.lastParsedSummary = statistics; |
| monitor.done(); |
| |
| } catch (Throwable t) { |
| StatusHandler.log(new Status(IStatus.ERROR, UiUsageMonitorPlugin.ID_PLUGIN, |
| "Could not generate usage report", t)); //$NON-NLS-1$ |
| } |
| |
| return Status.OK_STATUS; |
| } |
| |
| private void createUsageTableData(Map<Integer, Map<String, InteractionEventSummary>> summaryMap, |
| InteractionEvent event, int userId) { |
| Map<String, InteractionEventSummary> usersSummary = summaryMap.get(userId); |
| if (usersSummary == null) { |
| usersSummary = new HashMap<String, InteractionEventSummary>(); |
| summaryMap.put(userId, usersSummary); |
| } |
| |
| InteractionEventSummary summary = usersSummary.get(getIdentifier(event)); |
| if (summary == null) { |
| summary = new InteractionEventSummary(event.getKind().toString(), |
| InteractionEventUtil.getCleanOriginId(event), 0); |
| usersSummary.put(getIdentifier(event), summary); |
| } |
| summary.setUsageCount(summary.getUsageCount() + 1); |
| summary.setInterestContribution(summary.getInterestContribution() + event.getInterestContribution()); |
| summary.setDelta(event.getDelta()); |
| summary.addUserId(userId); |
| } |
| |
| public String getIdentifier(InteractionEvent event) { |
| return event.getKind().toString() + ':' + InteractionEventUtil.getCleanOriginId(event); |
| } |
| |
| } |
| |
| public static String formatPercentage(float percentage) { |
| String percentageString = "" + percentage; //$NON-NLS-1$ |
| int indexOf2ndDecimal = percentageString.indexOf('.') + 3; |
| if (indexOf2ndDecimal <= percentageString.length()) { |
| percentageString = percentageString.substring(0, indexOf2ndDecimal); |
| } |
| return percentageString; |
| } |
| |
| public List<IUsageCollector> getCollectors() { |
| return collectors; |
| } |
| |
| public Map<Integer, Map<String, SortedSet<InteractionEvent>>> getAllUsers() { |
| return allUserEvents; |
| } |
| |
| public void forceSyncForTesting(boolean syncForTesting) { |
| this.forceSyncForTesting = syncForTesting; |
| |
| } |
| } |