| 1 | /******************************************************************************* |
| 2 | * Copyright (c) 2012 Ericsson |
| 3 | * |
| 4 | * All rights reserved. This program and the accompanying materials are |
| 5 | * made available under the terms of the Eclipse Public License v1.0 which |
| 6 | * accompanies this distribution, and is available at |
| 7 | * http://www.eclipse.org/legal/epl-v10.html |
| 8 | * |
| 9 | * Contributors: |
| 10 | * Alexandre Montplaisir - Initial API and implementation |
| 11 | ******************************************************************************/ |
| 12 | |
| 13 | package org.eclipse.linuxtools.tmf.core.statistics; |
| 14 | |
| 15 | import java.io.File; |
| 16 | import java.util.HashMap; |
| 17 | import java.util.List; |
| 18 | import java.util.Map; |
| 19 | |
| 20 | import org.eclipse.core.resources.IResource; |
| 21 | import org.eclipse.core.runtime.CoreException; |
| 22 | import org.eclipse.linuxtools.tmf.core.TmfCommonConstants; |
| 23 | import org.eclipse.linuxtools.tmf.core.exceptions.AttributeNotFoundException; |
| 24 | import org.eclipse.linuxtools.tmf.core.exceptions.StateSystemDisposedException; |
| 25 | import org.eclipse.linuxtools.tmf.core.exceptions.StateValueTypeException; |
| 26 | import org.eclipse.linuxtools.tmf.core.exceptions.TimeRangeException; |
| 27 | import org.eclipse.linuxtools.tmf.core.exceptions.TmfTraceException; |
| 28 | import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval; |
| 29 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignal; |
| 30 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignalManager; |
| 31 | import org.eclipse.linuxtools.tmf.core.signal.TmfStatsUpdatedSignal; |
| 32 | import org.eclipse.linuxtools.tmf.core.statesystem.IStateChangeInput; |
| 33 | import org.eclipse.linuxtools.tmf.core.statesystem.ITmfStateSystem; |
| 34 | import org.eclipse.linuxtools.tmf.core.statesystem.StateSystemManager; |
| 35 | import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace; |
| 36 | |
| 37 | /** |
| 38 | * Implementation of ITmfStatistics which uses a state history for storing its |
| 39 | * information. |
| 40 | * |
| 41 | * It requires building the history first, but gives very fast response times |
| 42 | * when built : Queries are O(log n) wrt the size of the trace, and O(1) wrt to |
| 43 | * the size of the time interval selected. |
| 44 | * |
| 45 | * @author Alexandre Montplaisir |
| 46 | * @since 2.0 |
| 47 | */ |
| 48 | |
| 49 | public class TmfStateStatistics implements ITmfStatistics { |
| 50 | |
| 51 | /** ID for the statistics state system */ |
| 52 | public static final String STATE_ID = "org.eclipse.linuxtools.tmf.statistics"; //$NON-NLS-1$ |
| 53 | |
| 54 | /** Filename the "statistics state history" file will have */ |
| 55 | private static final String STATS_STATE_FILENAME = "statistics.ht"; //$NON-NLS-1$ |
| 56 | |
| 57 | private final ITmfTrace trace; |
| 58 | |
| 59 | /** |
| 60 | * The state system that's used to stored the statistics. It's hidden from |
| 61 | * the trace, so that it doesn't conflict with ITmfTrace.getStateSystem() |
| 62 | * (which is something else!) |
| 63 | */ |
| 64 | private final ITmfStateSystem stats; |
| 65 | |
| 66 | /** |
| 67 | * Empty constructor. The resulting TmfStatistics object will not be usable, |
| 68 | * but it might be needed for sub-classes. |
| 69 | */ |
| 70 | public TmfStateStatistics() { |
| 71 | stats = null; |
| 72 | trace = null; |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Constructor |
| 77 | * |
| 78 | * @param trace |
| 79 | * The trace for which we build these statistics |
| 80 | * @throws TmfTraceException |
| 81 | * If something went wrong trying to initialize the statistics |
| 82 | */ |
| 83 | public TmfStateStatistics(ITmfTrace trace) throws TmfTraceException { |
| 84 | /* Set up the path to the history tree file we'll use */ |
| 85 | this.trace = trace; |
| 86 | IResource resource = trace.getResource(); |
| 87 | String supplDirectory = null; |
| 88 | |
| 89 | try { |
| 90 | // get the directory where the history file will be stored. |
| 91 | supplDirectory = resource.getPersistentProperty(TmfCommonConstants.TRACE_SUPPLEMENTARY_FOLDER); |
| 92 | } catch (CoreException e) { |
| 93 | throw new TmfTraceException(e.toString(), e); |
| 94 | } |
| 95 | |
| 96 | final File htFile = new File(supplDirectory + File.separator + STATS_STATE_FILENAME); |
| 97 | final IStateChangeInput htInput = new StatsStateProvider(trace); |
| 98 | |
| 99 | this.stats = StateSystemManager.loadStateHistory(htFile, htInput, STATE_ID, false); |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * Manual constructor. This should be used if the trace's Resource is null |
| 104 | * (ie, for unit tests). It requires specifying the location of the history |
| 105 | * file manually. |
| 106 | * |
| 107 | * @param trace |
| 108 | * The trace for which we build these statistics |
| 109 | * @param historyFile |
| 110 | * The location of the state history file to build for the stats |
| 111 | * @throws TmfTraceException |
| 112 | * If the file could not be written to |
| 113 | */ |
| 114 | public TmfStateStatistics(ITmfTrace trace, File historyFile) throws TmfTraceException { |
| 115 | this.trace = trace; |
| 116 | final IStateChangeInput htInput = new StatsStateProvider(trace); |
| 117 | this.stats = StateSystemManager.loadStateHistory(historyFile, htInput, STATE_ID, true); |
| 118 | } |
| 119 | |
| 120 | // ------------------------------------------------------------------------ |
| 121 | // ITmfStatistics |
| 122 | // ------------------------------------------------------------------------ |
| 123 | |
| 124 | @Override |
| 125 | public void dispose() { |
| 126 | stats.dispose(); |
| 127 | } |
| 128 | |
| 129 | @Override |
| 130 | public void updateStats(final boolean isGlobal, final long start, |
| 131 | final long end) { |
| 132 | /* |
| 133 | * Since we are currently in a signal handler (ie, in the UI thread), |
| 134 | * and since state system queries can be arbitrarily long (O(log n) wrt |
| 135 | * the size of the trace), we will run those queries in a separate |
| 136 | * thread and update the statistics view out-of-band. |
| 137 | */ |
| 138 | Thread statsThread = new Thread("Statistics update") { //$NON-NLS-1$ |
| 139 | @Override |
| 140 | public void run() { |
| 141 | long total; |
| 142 | Map<String, Long> map; |
| 143 | |
| 144 | /* Wait until the history building completed */ |
| 145 | if (!stats.waitUntilBuilt()) { |
| 146 | return; |
| 147 | } |
| 148 | |
| 149 | /* Range should be valid for both global and time range queries */ |
| 150 | total = getEventsInRange(start, end); |
| 151 | map = getEventTypesInRange(start, end); |
| 152 | |
| 153 | /* Send the signal to notify the stats viewer to update its display */ |
| 154 | TmfSignal sig = new TmfStatsUpdatedSignal(this, trace, isGlobal, total, map); |
| 155 | TmfSignalManager.dispatchSignal(sig); |
| 156 | } |
| 157 | }; |
| 158 | statsThread.start(); |
| 159 | return; |
| 160 | |
| 161 | } |
| 162 | |
| 163 | @Override |
| 164 | public long getEventsTotal() { |
| 165 | /* We need the complete state history to be built to answer this. */ |
| 166 | stats.waitUntilBuilt(); |
| 167 | |
| 168 | long endTime = stats.getCurrentEndTime(); |
| 169 | int count = 0; |
| 170 | |
| 171 | try { |
| 172 | final int quark = stats.getQuarkAbsolute(Attributes.TOTAL); |
| 173 | count= stats.querySingleState(endTime, quark).getStateValue().unboxInt(); |
| 174 | |
| 175 | } catch (TimeRangeException e) { |
| 176 | /* Assume there is no events for that range */ |
| 177 | return 0; |
| 178 | } catch (AttributeNotFoundException e) { |
| 179 | e.printStackTrace(); |
| 180 | } catch (StateValueTypeException e) { |
| 181 | e.printStackTrace(); |
| 182 | } catch (StateSystemDisposedException e) { |
| 183 | e.printStackTrace(); |
| 184 | } |
| 185 | |
| 186 | return count; |
| 187 | } |
| 188 | |
| 189 | @Override |
| 190 | public Map<String, Long> getEventTypesTotal() { |
| 191 | /* We need the complete state history to be built to answer this. */ |
| 192 | stats.waitUntilBuilt(); |
| 193 | |
| 194 | Map<String, Long> map = new HashMap<String, Long>(); |
| 195 | long endTime = stats.getCurrentEndTime(); |
| 196 | |
| 197 | try { |
| 198 | /* Get the list of quarks, one for each even type in the database */ |
| 199 | int quark = stats.getQuarkAbsolute(Attributes.EVENT_TYPES); |
| 200 | List<Integer> quarks = stats.getSubAttributes(quark, false); |
| 201 | |
| 202 | /* Since we want the total we can look only at the end */ |
| 203 | List<ITmfStateInterval> endState = stats.queryFullState(endTime); |
| 204 | |
| 205 | String curEventName; |
| 206 | long eventCount; |
| 207 | for (int typeQuark : quarks) { |
| 208 | curEventName = stats.getAttributeName(typeQuark); |
| 209 | eventCount = endState.get(typeQuark).getStateValue().unboxInt(); |
| 210 | map.put(curEventName, eventCount); |
| 211 | } |
| 212 | |
| 213 | } catch (TimeRangeException e) { |
| 214 | /* Assume there is no events, nothing will be put in the map. */ |
| 215 | } catch (AttributeNotFoundException e) { |
| 216 | e.printStackTrace(); |
| 217 | } catch (StateValueTypeException e) { |
| 218 | e.printStackTrace(); |
| 219 | } catch (StateSystemDisposedException e) { |
| 220 | e.printStackTrace(); |
| 221 | } |
| 222 | return map; |
| 223 | } |
| 224 | |
| 225 | @Override |
| 226 | public long getEventsInRange(long start, long end) { |
| 227 | // FIXME Instead of waiting until the end, we could check the current |
| 228 | // end time, and answer as soon as possible... |
| 229 | stats.waitUntilBuilt(); |
| 230 | |
| 231 | int countAtStart = 0, countAtEnd = 0; |
| 232 | long startTime = checkStartTime(start); |
| 233 | long endTime = checkEndTime(end); |
| 234 | |
| 235 | try { |
| 236 | final int quark = stats.getQuarkAbsolute(Attributes.TOTAL); |
| 237 | if (startTime == stats.getStartTime()) { |
| 238 | countAtStart = 0; |
| 239 | } else { |
| 240 | /* State system works that way... */ |
| 241 | countAtStart = stats.querySingleState(startTime - 1, quark).getStateValue().unboxInt(); |
| 242 | } |
| 243 | countAtEnd = stats.querySingleState(endTime, quark).getStateValue().unboxInt(); |
| 244 | |
| 245 | } catch (TimeRangeException e) { |
| 246 | /* Assume there is no events for that range */ |
| 247 | return 0; |
| 248 | } catch (AttributeNotFoundException e) { |
| 249 | e.printStackTrace(); |
| 250 | } catch (StateValueTypeException e) { |
| 251 | e.printStackTrace(); |
| 252 | } catch (StateSystemDisposedException e) { |
| 253 | e.printStackTrace(); |
| 254 | } |
| 255 | |
| 256 | long total = countAtEnd - countAtStart; |
| 257 | return total; |
| 258 | } |
| 259 | |
| 260 | @Override |
| 261 | public Map<String, Long> getEventTypesInRange(long start, long end) { |
| 262 | // FIXME Instead of waiting until the end, we could check the current |
| 263 | // end time, and answer as soon as possible... |
| 264 | stats.waitUntilBuilt(); |
| 265 | |
| 266 | Map<String, Long> map = new HashMap<String, Long>(); |
| 267 | |
| 268 | /* Make sure the start/end times are within the state history, so we |
| 269 | * don't get TimeRange exceptions. |
| 270 | */ |
| 271 | long startTime = checkStartTime(start); |
| 272 | long endTime = checkEndTime(end); |
| 273 | |
| 274 | try { |
| 275 | /* Get the list of quarks, one for each even type in the database */ |
| 276 | int quark = stats.getQuarkAbsolute(Attributes.EVENT_TYPES); |
| 277 | List<Integer> quarks = stats.getSubAttributes(quark, false); |
| 278 | |
| 279 | List<ITmfStateInterval> endState = stats.queryFullState(endTime); |
| 280 | |
| 281 | String curEventName; |
| 282 | long countAtStart, countAtEnd, eventCount; |
| 283 | |
| 284 | if (startTime == stats.getStartTime()) { |
| 285 | /* Only use the values picked up at the end time */ |
| 286 | for (int typeQuark : quarks) { |
| 287 | curEventName = stats.getAttributeName(typeQuark); |
| 288 | eventCount = endState.get(typeQuark).getStateValue().unboxInt(); |
| 289 | if (eventCount == -1) { |
| 290 | eventCount = 0; |
| 291 | } |
| 292 | map.put(curEventName, eventCount); |
| 293 | } |
| 294 | } else { |
| 295 | /* |
| 296 | * Query the start time at -1, so the beginning of the interval |
| 297 | * is inclusive. |
| 298 | */ |
| 299 | List<ITmfStateInterval> startState = stats.queryFullState(startTime - 1); |
| 300 | for (int typeQuark : quarks) { |
| 301 | curEventName = stats.getAttributeName(typeQuark); |
| 302 | countAtStart = startState.get(typeQuark).getStateValue().unboxInt(); |
| 303 | countAtEnd = endState.get(typeQuark).getStateValue().unboxInt(); |
| 304 | |
| 305 | if (countAtStart == -1) { |
| 306 | countAtStart = 0; |
| 307 | } |
| 308 | if (countAtEnd == -1) { |
| 309 | countAtEnd = 0; |
| 310 | } |
| 311 | eventCount = countAtEnd - countAtStart; |
| 312 | map.put(curEventName, eventCount); |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | } catch (TimeRangeException e) { |
| 317 | /* Assume there is no events, nothing will be put in the map. */ |
| 318 | } catch (AttributeNotFoundException e) { |
| 319 | /* |
| 320 | * These other exception types would show a logic problem however, |
| 321 | * so they should not happen. |
| 322 | */ |
| 323 | e.printStackTrace(); |
| 324 | } catch (StateValueTypeException e) { |
| 325 | e.printStackTrace(); |
| 326 | } catch (StateSystemDisposedException e) { |
| 327 | e.printStackTrace(); |
| 328 | } |
| 329 | return map; |
| 330 | } |
| 331 | |
| 332 | private long checkStartTime(long initialStart) { |
| 333 | long start = initialStart; |
| 334 | if (start < stats.getStartTime()) { |
| 335 | return stats.getStartTime(); |
| 336 | } |
| 337 | return start; |
| 338 | } |
| 339 | |
| 340 | private long checkEndTime(long initialEnd) { |
| 341 | long end = initialEnd; |
| 342 | if (end > stats.getCurrentEndTime()) { |
| 343 | return stats.getCurrentEndTime(); |
| 344 | } |
| 345 | return end; |
| 346 | } |
| 347 | |
| 348 | |
| 349 | /** |
| 350 | * The attribute names that are used in the state provider |
| 351 | */ |
| 352 | public static class Attributes { |
| 353 | |
| 354 | /** Total nb of events */ |
| 355 | public static final String TOTAL = "total"; //$NON-NLS-1$ |
| 356 | |
| 357 | /** event_types */ |
| 358 | public static final String EVENT_TYPES = "event_types"; //$NON-NLS-1$< |
| 359 | } |
| 360 | } |