###############################################################################
-# Copyright (c) 2015 EfficiOS Inc. and others
+# Copyright (c) 2015, 2016 EfficiOS Inc. and others
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
###############################################################################
-# Copyright (c) 2015 EfficiOS Inc. and others
+# Copyright (c) 2015, 2016 EfficiOS Inc. and others
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
/*******************************************************************************
- * Copyright (c) 2015 EfficiOS Inc. and others
+ * Copyright (c) 2015, 2016 EfficiOS Inc. and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
/*******************************************************************************
- * Copyright (c) 2015 EfficiOS Inc. and others
+ * Copyright (c) 2015, 2016 EfficiOS Inc. and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core;
+
+/**
+ * Strings used in the LAMI v1.0 protocol.
+ *
+ * The full spec is documented <a href=
+ * "https://github.com/lttng/lami-spec/blob/d6129206184988b3fd7cccb76deace4a69c1443e/lami.md">here</a>.
+ *
+ * @author Alexandre Montplaisir
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+@SuppressWarnings({ "javadoc", "nls" })
+public interface LamiStrings {
+
+ /* Metadata elements */
+ String MI_VERSION = "mi-version";
+ String TITLE = "title";
+ String UNIT = "unit";
+ String TABLE_CLASSES = "table-classes";
+ String COLUMN_DESCRIPTIONS = "column-descriptions";
+ String RESULTS = "results";
+ String TIME_RANGE = "time-range";
+ String CLASS = "class";
+ String DATA = "data";
+ String INHERIT = "inherit";
+
+ /* Data types */
+ String VALUE = "value";
+ String ID = "id";
+ String FD = "fd";
+ String NAME = "name";
+ String PATH = "path";
+
+ /* Time ranges */
+ String BEGIN = "begin";
+ String END = "end";
+
+ /* Process info */
+ String PID = "pid";
+ String TID = "tid";
+
+ /* IRQ stuff */
+ String NR = "nr";
+ String HARD = "hard";
+
+ /* Version object */
+ String MAJOR = "major";
+ String MINOR = "minor";
+ String PATCH = "patch";
+ String EXTRA = "extra";
+
+ /* Data classes */
+ String DATA_CLASS_UNKNOWN = "unknown";
+ String DATA_CLASS_RATIO = "ratio";
+ String DATA_CLASS_TIMESTAMP = "timestamp";
+ String DATA_CLASS_TIME_RANGE = "time-range";
+ String DATA_CLASS_DURATION = "duration";
+ String DATA_CLASS_SIZE = "size";
+ String DATA_CLASS_BITRATE = "bitrate";
+ String DATA_CLASS_SYSCALL = "syscall";
+ String DATA_CLASS_PROCESS = "process";
+ String DATA_CLASS_PATH = "path";
+ String DATA_CLASS_FD = "fd";
+ String DATA_CLASS_IRQ = "irq";
+ String DATA_CLASS_CPU = "cpu";
+ String DATA_CLASS_DISK = "disk";
+ String DATA_CLASS_PART = "part";
+ String DATA_CLASS_NETIF = "netif";
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Michael Jeanson
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiDuration;
+
+/**
+ * Aspect for a time range duration
+ *
+ * @author Michael Jeanson
+ */
+public class LamiDurationAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param colName
+ * Column name
+ * @param colIndex
+ * Column index
+ */
+ public LamiDurationAspect(String colName, int colIndex) {
+ super(colName, "ns"); //$NON-NLS-1$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return true;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeDuration() {
+ return true;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiDuration) {
+ LamiDuration duration = (LamiDuration) data;
+ return String.valueOf(duration.getValue());
+ }
+ return data.toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(@NonNull LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiDuration) {
+ LamiDuration range = (LamiDuration) data;
+ return Double.valueOf(range.getValue());
+ }
+ return null;
+ }
+
+ @Override
+ public @NonNull Comparator<@NonNull LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ Double dO1 = resolveDouble(o1);
+ Double dO2 = resolveDouble(o2);
+ if (dO1 == null || dO2 == null) {
+ return 0;
+ }
+
+ return dO1.compareTo(dO2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+
+/**
+ * Null aspect for LAMI tables, normally printing nothing in the corresponding
+ * cells. Should be access using {@link #INSTANCE}.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiEmptyAspect extends LamiTableEntryAspect {
+
+ /** Singleton instance */
+ public static final LamiEmptyAspect INSTANCE = new LamiEmptyAspect();
+
+ private LamiEmptyAspect() {
+ super("", null); //$NON-NLS-1$
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return false;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ return null;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(@NonNull LamiTableEntry entry) {
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> 0;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+
+/**
+ * Base class for LAMI table aspects.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiGenericAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+ private final boolean fIsContinuous;
+ private final boolean fIsTimeStamp;
+
+ /**
+ * Constructor
+ *
+ * @param aspectName
+ * Name of the aspect (name of the column in the UI)
+ * @param units
+ * The units of this column
+ * @param colIndex
+ * Index of this column
+ * @param isContinuous
+ * If the contents of this column are numbers or not
+ * @param isTimeStamp
+ * If the contents of this column are numerical timestamp or not
+ */
+ public LamiGenericAspect(String aspectName, @Nullable String units, int colIndex, boolean isContinuous, boolean isTimeStamp) {
+ super(aspectName, units);
+ fColIndex = colIndex;
+ fIsContinuous = isContinuous;
+ fIsTimeStamp = isTimeStamp;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return fIsContinuous;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return fIsTimeStamp;
+ }
+
+ @Override
+ public @Nullable String resolveString(@NonNull LamiTableEntry entry) {
+ return entry.getValue(fColIndex).toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(@NonNull LamiTableEntry entry) {
+ if (fIsContinuous) {
+ try {
+ if (entry.getValue(fColIndex).toString() != null) {
+ return Double.parseDouble(entry.getValue(fColIndex).toString());
+ }
+ } catch (NumberFormatException e) {
+ // Fallback to default value below
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ if (isContinuous()) {
+ return (o1, o2) -> {
+ Double dO1 = resolveDouble(o1);
+ Double dO2 = resolveDouble(o2);
+ if (dO1 == null || dO2 == null) {
+ return 0;
+ }
+
+ return dO1.compareTo(dO2);
+ };
+ }
+
+ /* Use regular string comparison */
+ return (o1, o2) -> {
+ String s1 = resolveString(o1);
+ String s2 = resolveString(o2);
+
+ if (s1 == null || s2 == null) {
+ return 0;
+ }
+
+ return s1.compareTo(s2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiIRQ;
+
+/**
+ * Aspect for the IRQ handler names.
+ *
+ * This resolves the interrupt handler name, (like i915) from a given table
+ * entry.
+ *
+ * @author Philippe Proulx
+ */
+public class LamiIRQNameAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param colName
+ * Column name
+ * @param colIndex
+ * Column index
+ */
+ public LamiIRQNameAspect(String colName, int colIndex) {
+ super(colName + " (" + Messages.LamiAspect_Name +')', null); //$NON-NLS-1$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiIRQ) {
+ return ((LamiIRQ) data).getName();
+ }
+ /* Could be null, unknown, etc. */
+ return data.toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(LamiTableEntry entry) {
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ String s1 = resolveString(o1);
+ String s2 = resolveString(o2);
+
+ if (s1 == null || s2 == null) {
+ return 0;
+ }
+
+ return s1.compareTo(s2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiIRQ;
+
+/**
+ * Aspect for the IRQ numbers.
+ *
+ * This resolves the IRQ number for a given table, so 0|timer would return 0.
+ *
+ * @author Philippe Proulx
+ */
+public class LamiIRQNumberAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param colName
+ * Column name
+ * @param colIndex
+ * Column index
+ */
+ public LamiIRQNumberAspect(String colName, int colIndex) {
+ super(colName + " (#)", null); //$NON-NLS-1$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiIRQ) {
+ return String.valueOf(((LamiIRQ) data).getNumber());
+ }
+ /* Could be null, unknown, etc. */
+ return data.toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiIRQ) {
+ return Double.valueOf(((LamiIRQ) data).getNumber());
+ }
+
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ Double dO1 = resolveDouble(o1);
+ Double dO2 = resolveDouble(o2);
+ if (dO1 == null || dO2 == null) {
+ return 0;
+ }
+
+ return dO1.compareTo(dO2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiIRQ;
+
+/**
+ * Aspect for IRQ type, indicating if it is a hardware IRQ or software IRQ.
+ *
+ * @author Philippe Proulx
+ */
+public class LamiIRQTypeAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param colName
+ * Column name
+ * @param colIndex
+ * Column index
+ */
+ public LamiIRQTypeAspect(String colName, int colIndex) {
+ super(colName + " (" + Messages.LamiAspect_Type +')', null); //$NON-NLS-1$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiIRQ) {
+ LamiIRQ irq = (LamiIRQ) data;
+
+ switch (irq.getType()) {
+ case HARD:
+ return Messages.LamiIRQTypeAspect_HardwareIRQ;
+
+ case SOFT:
+ return Messages.LamiIRQTypeAspect_SoftIRQ;
+
+ default:
+ return "?"; //$NON-NLS-1$
+ }
+ }
+ /* Could be null, unknown, etc. */
+ return data.toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(LamiTableEntry entry) {
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ String s1 = resolveString(o1);
+ String s2 = resolveString(o2);
+
+ if (s1 == null || s2 == null) {
+ return 0;
+ }
+
+ return s1.compareTo(s2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData.DataType;
+
+/**
+ * Aspect for LAMI mixed types.
+ *
+ * The data in this column can contain any class of data.
+ *
+ * @author Philippe Proulx
+ */
+public class LamiMixedAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param colName
+ * Column name
+ * @param colIndex
+ * Column index
+ */
+ public LamiMixedAspect(String colName, int colIndex) {
+ super(colName, null);
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ Class<? extends LamiData> cls = data.getClass();
+
+ DataType dataType = DataType.fromClass(cls);
+
+ if (dataType == null) {
+ return data.toString();
+ }
+
+ String str = data.toString();
+
+ if (dataType.getUnits() != null) {
+ str += " " + dataType.getUnits(); //$NON-NLS-1$
+ }
+
+ return str;
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(LamiTableEntry entry) {
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ String s1 = resolveString(o1);
+ String s2 = resolveString(o2);
+
+ if (s1 == null || s2 == null) {
+ return 0;
+ }
+
+ return s1.compareTo(s2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiProcess;
+
+/**
+ * Aspect for process names
+ *
+ * @author Philippe Proulx
+ */
+public class LamiProcessNameAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param colName
+ * Column name
+ * @param colIndex
+ * Column index
+ */
+ public LamiProcessNameAspect(String colName, int colIndex) {
+ super(colName + " (" + Messages.LamiAspect_Name +')', null); //$NON-NLS-1$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiProcess) {
+ return ((LamiProcess) data).getName();
+ }
+ /* Could be null, unknown, etc. */
+ return data.toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(LamiTableEntry entry) {
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ String s1 = resolveString(o1);
+ String s2 = resolveString(o2);
+
+ if (s1 == null || s2 == null) {
+ return 0;
+ }
+
+ return s1.compareTo(s2);
+ };
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiProcess;
+
+/**
+ * Aspect for process PID
+ *
+ * @author Philippe Proulx
+ */
+public class LamiProcessPIDAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param colName
+ * Column name
+ * @param colIndex
+ * Column index
+ */
+ public LamiProcessPIDAspect(String colName, int colIndex) {
+ super(colName + " (PID)", null); //$NON-NLS-1$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiProcess) {
+ Long pid = ((LamiProcess) data).getPID();
+
+ if (pid == null) {
+ return null;
+ }
+
+ return pid.toString();
+ }
+ /* Could be null, unknown, etc. */
+ return data.toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiProcess) {
+ Long pid = ((LamiProcess) data).getPID();
+
+ if (pid == null) {
+ return null;
+ }
+
+ return Double.valueOf(pid);
+ }
+
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ Double dO1 = resolveDouble(o1);
+ Double dO2 = resolveDouble(o2);
+ if (dO1 == null || dO2 == null) {
+ return 0;
+ }
+
+ return dO1.compareTo(dO2);
+ };
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiProcess;
+
+/**
+ * Aspect for process TID
+ *
+ * @author Philippe Proulx
+ */
+public class LamiProcessTIDAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param colName
+ * Column name
+ * @param colIndex
+ * Column index
+ */
+ public LamiProcessTIDAspect(String colName, int colIndex) {
+ super(colName + " (TID)", null); //$NON-NLS-1$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiProcess) {
+ Long tid = ((LamiProcess) data).getTID();
+
+ if (tid == null) {
+ return null;
+ }
+
+ return tid.toString();
+ }
+ /* Could be null, unknown, etc. */
+ return data.toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiProcess) {
+ Long tid = ((LamiProcess) data).getTID();
+
+ if (tid == null) {
+ return null;
+ }
+
+ return Double.valueOf(tid);
+ }
+
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ Double dO1 = resolveDouble(o1);
+ Double dO2 = resolveDouble(o2);
+ if (dO1 == null || dO2 == null) {
+ return 0;
+ }
+
+ return dO1.compareTo(dO2);
+ };
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+
+/**
+ * Aspect for LAMI table entries, which normally correspond to one "row"
+ * of JSON output.
+ *
+ * It is not the same as a "Event aspect" used for trace events, but it is
+ * heavily inspired from it.
+ *
+ * @author Alexandre Montplaisir
+ * @see org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect
+ */
+public abstract class LamiTableEntryAspect {
+
+ private final String fName;
+ private final @Nullable String fUnits;
+
+ /**
+ * Constructor
+ *
+ * @param name
+ * Aspect name, will be used as column name in the UI
+ * @param units
+ * The units of the value in this column
+ */
+ protected LamiTableEntryAspect(String name, @Nullable String units) {
+ fUnits = units;
+ fName = name;
+ }
+
+ /**
+ * Get the label of this aspect.
+ *
+ * The label is composed of the name followed by the units in parentheses.
+ *
+ * @return The label
+ */
+ public String getLabel() {
+ if (getUnits() == null) {
+ return getName();
+ }
+ return (getName() + " (" + getUnits() + ')'); //$NON-NLS-1$
+ }
+
+ /**
+ * Get the name of this aspect
+ *
+ * @return The name
+ */
+ public String getName() {
+ return fName;
+ }
+
+ /**
+ * Get the units of this aspect.
+ *
+ * @return The units
+ */
+ public @Nullable String getUnits() {
+ return fUnits;
+ }
+
+ /**
+ * Indicate if this aspect is numerical or not. This is used, among other
+ * things, to align the text in the table cells.
+ *
+ * @return If this aspect is numerical or not
+ */
+ public abstract boolean isContinuous();
+
+
+ /**
+ * Indicate if this aspect represent timestamp or not. This can be used in chart
+ * for axis labeling etc.
+ * @return If this aspect represent a timestamp or not
+ */
+ public abstract boolean isTimeStamp();
+
+ /**
+ * Indicate if this aspect represent a time duration or not. This can be used in
+ * chart for axis labeling etc.
+ * @return If this aspect represent a time duration or not
+ */
+ public boolean isTimeDuration() {
+ return false;
+ }
+
+ /**
+ * Resolve this aspect for the given entry.
+ *
+ * @param entry
+ * The table row
+ * @return The string to display for the given cell
+ */
+ public abstract @Nullable String resolveString(LamiTableEntry entry);
+
+ /**
+ * Resolve this aspect double representation for the given entry
+ *
+ * Returned value does not matter if isNumerical() is false.
+ *
+ * @param entry
+ * The table row
+ * @return The double value for the given cell
+ */
+ public abstract @Nullable Double resolveDouble(LamiTableEntry entry);
+
+ /**
+ * Get the comparator that should be used to compare this entry (or table
+ * row) with the other rows in the table. This will be passed on to the
+ * table's content provider.
+ *
+ * @return The entry comparator
+ */
+ public abstract Comparator<LamiTableEntry> getComparator();
+
+ /**
+ * Check if an aspect have the same properties.
+ *
+ * FIXME:Might want to compare the units if necessary.
+ *
+ * @param aspect
+ * The aspect to compare to
+ * @return If all aspect's properties are equal
+ */
+ public boolean arePropertiesEqual(LamiTableEntryAspect aspect) {
+ boolean timestamp = (this.isTimeStamp() == aspect.isTimeStamp());
+ boolean numerical = (this.isContinuous() == aspect.isContinuous());
+ return (timestamp && numerical);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestampFormat;
+
+/**
+ * Aspect for beginning timestamp of a timerange
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiTimeRangeBeginAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param timeRangeName
+ * Name of the time range
+ * @param colIndex
+ * Column index
+ */
+ public LamiTimeRangeBeginAspect(String timeRangeName, int colIndex) {
+ super(timeRangeName + " (" + Messages.LamiAspect_TimeRangeBegin + ')', null); //$NON-NLS-1$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return true;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return true;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiTimeRange) {
+ LamiTimeRange range = (LamiTimeRange) data;
+ return TmfTimestampFormat.getDefaulTimeFormat().format(range.getStart());
+ }
+ /* Could be null, unknown, etc. */
+ return data.toString();
+ }
+
+
+
+ @Override
+ public @Nullable Double resolveDouble(@NonNull LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiTimeRange) {
+ LamiTimeRange range = (LamiTimeRange) data;
+ return Double.valueOf(range.getStart());
+ }
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ Double dO1 = resolveDouble(o1);
+ Double dO2 = resolveDouble(o2);
+ if (dO1 == null || dO2 == null) {
+ return 0;
+ }
+
+ return dO1.compareTo(dO2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange;
+
+/**
+ * Aspect for a time range duration
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiTimeRangeDurationAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param timeRangeName
+ * Name of the time range
+ * @param colIndex
+ * Column index
+ */
+ public LamiTimeRangeDurationAspect(String timeRangeName, int colIndex) {
+ super(timeRangeName + " (" + Messages.LamiAspect_TimeRangeDuration + ')', "ns"); //$NON-NLS-1$ //$NON-NLS-2$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return true;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeDuration() {
+ return true;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiTimeRange) {
+ LamiTimeRange range = (LamiTimeRange) data;
+ return String.valueOf(range.getDuration());
+ }
+ return data.toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(@NonNull LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiTimeRange) {
+ LamiTimeRange range = (LamiTimeRange) data;
+ return Double.valueOf(range.getDuration());
+ }
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ Double dO1 = resolveDouble(o1);
+ Double dO2 = resolveDouble(o2);
+ if (dO1 == null || dO2 == null) {
+ return 0;
+ }
+
+ return dO1.compareTo(dO2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestampFormat;
+
+/**
+ * Aspect for a time range duration
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiTimeRangeEndAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param timeRangeName
+ * Name of the time range
+ * @param colIndex
+ * Column index
+ */
+ public LamiTimeRangeEndAspect(String timeRangeName, int colIndex) {
+ super(timeRangeName + " (" + Messages.LamiAspect_TimeRangeEnd + ')', null); //$NON-NLS-1$
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return true;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiTimeRange) {
+ LamiTimeRange range = (LamiTimeRange) data;
+ return TmfTimestampFormat.getDefaulTimeFormat().format(range.getEnd());
+ }
+ return data.toString();
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return true;
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(@NonNull LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiTimeRange) {
+ LamiTimeRange range = (LamiTimeRange) data;
+ return Double.valueOf(range.getEnd());
+ }
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ Double dO1 = resolveDouble(o1);
+ Double dO2 = resolveDouble(o2);
+ if (dO1 == null || dO2 == null) {
+ return 0;
+ }
+
+ return dO1.compareTo(dO2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiInteger;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestampFormat;
+
+/**
+ * Aspect for timestamps
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiTimestampAspect extends LamiTableEntryAspect {
+
+ private final int fColIndex;
+
+ /**
+ * Constructor
+ *
+ * @param timestampName
+ * Name of the timestamp
+ * @param colIndex
+ * Column index
+ */
+ public LamiTimestampAspect(String timestampName, int colIndex) {
+ super(timestampName, null);
+ fColIndex = colIndex;
+ }
+
+ @Override
+ public boolean isContinuous() {
+ return true;
+ }
+
+ @Override
+ public boolean isTimeStamp() {
+ return true;
+ }
+
+ @Override
+ public @Nullable String resolveString(LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiInteger) {
+ LamiInteger range = (LamiInteger) data;
+ return TmfTimestampFormat.getDefaulTimeFormat().format(range.getValue());
+ }
+ return data.toString();
+ }
+
+ @Override
+ public @Nullable Double resolveDouble(@NonNull LamiTableEntry entry) {
+ LamiData data = entry.getValue(fColIndex);
+ if (data instanceof LamiInteger) {
+ LamiInteger range = (LamiInteger) data;
+ return Double.valueOf(range.getValue());
+ }
+ return null;
+ }
+
+ @Override
+ public Comparator<LamiTableEntry> getComparator() {
+ return (o1, o2) -> {
+ Double dO1 = resolveDouble(o1);
+ Double dO2 = resolveDouble(o2);
+ if (dO1 == null || dO2 == null) {
+ return 0;
+ }
+
+ return dO1.compareTo(dO2);
+ };
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String LamiAspect_Name;
+ public static String LamiAspect_Type;
+
+ public static String LamiAspect_TimeRangeBegin;
+ public static String LamiAspect_TimeRangeDuration;
+ public static String LamiAspect_TimeRangeEnd;
+
+ public static String LamiIRQTypeAspect_HardwareIRQ;
+ public static String LamiIRQTypeAspect_SoftIRQ;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2016 EfficiOS Inc. 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
+###############################################################################
+
+LamiAspect_Name = name
+LamiAspect_Type = type
+
+LamiAspect_TimeRangeBegin = begin
+LamiAspect_TimeRangeDuration = duration
+LamiAspect_TimeRangeEnd = end
+
+LamiIRQTypeAspect_HardwareIRQ = Hard
+LamiIRQTypeAspect_SoftIRQ = Soft
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNullContents;
+import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.function.Predicate;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.analysis.lami.core.Activator;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.LamiStrings;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiGenericAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiIRQNameAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiIRQNumberAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiIRQTypeAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiMixedAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiProcessNameAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiProcessPIDAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiProcessTIDAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiDurationAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiEmptyAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTimeRangeBeginAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTimeRangeDurationAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTimeRangeEndAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTimestampAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData.DataType;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange;
+import org.eclipse.tracecompass.tmf.core.analysis.ondemand.IOnDemandAnalysis;
+import org.eclipse.tracecompass.tmf.core.analysis.ondemand.OnDemandAnalysisException;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Base class for analysis modules that call external scripts implementing the
+ * LAMI protocol.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiAnalysis implements IOnDemandAnalysis {
+
+ /** Maximum major version of the LAMI protocol we support */
+ private static final int MAX_SUPPORTED_MAJOR_VERSION = 1;
+
+ private static final String DOUBLE_QUOTES = "\""; //$NON-NLS-1$
+
+ /* Flags passed to the analysis scripts */
+ private static final String METADATA_FLAG = "--metadata"; //$NON-NLS-1$
+ private static final String PROGRESS_FLAG = "--output-progress"; //$NON-NLS-1$
+ private static final String BEGIN_FLAG = "--begin"; //$NON-NLS-1$
+ private static final String END_FLAG = "--end"; //$NON-NLS-1$
+
+ /** Log message for commands being run */
+ private static final String RUNNING_MESSAGE = "Running command:"; //$NON-NLS-1$
+
+ private final List<String> fScriptCommand;
+
+ /**
+ * The LAMI analysis is considered initialized after we have read the
+ * script's --metadata once. This will assign the fields below.
+ */
+ private boolean fInitialized = false;
+
+ private boolean fIsAvailable;
+ private final String fName;
+ private final boolean fIsUserDefined;
+ private final Predicate<ITmfTrace> fAppliesTo;
+
+ /* Data defined by the analysis's metadata */
+ private @Nullable String fAnalysisTitle;
+ private @Nullable Map<String, LamiTableClass> fTableClasses;
+ private boolean fUseProgressOutput;
+
+ /**
+ * Constructor. To be called by implementing classes.
+ *
+ * @param name
+ * Name of this analysis
+ * @param isUserDefined
+ * {@code true} if this is a user-defined analysis
+ * @param appliesTo
+ * Predicate to use to check whether or not this analysis applies
+ * to a given trace
+ * @param args
+ * Analysis arguments, including the executable name (first
+ * argument)
+ */
+ public LamiAnalysis(String name, boolean isUserDefined, Predicate<ITmfTrace> appliesTo,
+ List<String> args) {
+ fScriptCommand = ImmutableList.copyOf(args);
+ fName = name;
+ fIsUserDefined = isUserDefined;
+ fAppliesTo = appliesTo;
+ }
+
+ /**
+ * Map of pre-defined charts, for every table class names.
+ *
+ * If a table class is not in this map then it means that table has no
+ * predefined charts.
+ *
+ * @return The chart models, per table class names
+ */
+ protected Multimap<String, LamiChartModel> getPredefinedCharts() {
+ return ImmutableMultimap.of();
+ }
+
+ @Override
+ public boolean appliesTo(ITmfTrace trace) {
+ return fAppliesTo.test(trace);
+ }
+
+ @Override
+ public boolean canExecute(ITmfTrace trace) {
+ initialize();
+ return fIsAvailable;
+ }
+
+ private synchronized void initialize() {
+ if (fInitialized) {
+ return;
+ }
+
+ /* Do the analysis's initialization */
+
+ /* Check if the script's expected executable is on the PATH */
+ String executable = fScriptCommand.get(0);
+ boolean exists = Stream.of(System.getenv("PATH").split(checkNotNull(Pattern.quote(File.pathSeparator)))) //$NON-NLS-1$
+ .map(Paths::get)
+ .anyMatch(path -> Files.exists(path.resolve(executable)));
+ if (!exists) {
+ /* Script is not found */
+ fIsAvailable = false;
+ fInitialized = true;
+ return;
+ }
+
+ fIsAvailable = checkMetadata();
+ fInitialized = true;
+ }
+
+ private boolean checkMetadata() {
+ /*
+ * The initialize() phase of the analysis will be used to check the
+ * script's metadata. Actual runs of the script will use the execute()
+ * method below.
+ */
+ List<String> command = ImmutableList.<@NonNull String> builder()
+ .addAll(fScriptCommand).add(METADATA_FLAG).build();
+
+ Activator.instance().logInfo(RUNNING_MESSAGE + ' ' + command.toString());
+
+ String output = getOutputFromCommand(command);
+ if (output == null || output.isEmpty()) {
+ return false;
+ }
+
+ /*
+ *
+ * Metadata should look this this:
+ *
+ * {
+ * "version": [1, 5, 2, "dev"],
+ * "title": "I/O latency statistics",
+ * "authors": [
+ * "Julien Desfossez",
+ * "Antoine Busque"
+ * ],
+ * "description": "Provides statistics about the latency involved in various I/O operations.",
+ * "url": "https://github.com/lttng/lttng-analyses",
+ * "tags": [
+ * "io",
+ * "stats",
+ * "linux-kernel",
+ * "lttng-analyses"
+ * ],
+ * "table-classes": {
+ * "syscall-latency": {
+ * "title": "System calls latency statistics",
+ * "column-descriptions": [
+ * {"title": "System call", "type": "syscall"},
+ * {"title": "Count", "type": "int", "unit": "operations"},
+ * {"title": "Minimum duration", "type": "duration"},
+ * {"title": "Average duration", "type": "duration"},
+ * {"title": "Maximum duration", "type": "duration"},
+ * {"title": "Standard deviation", "type": "duration"}
+ * ]
+ * },
+ * "disk-latency": {
+ * "title": "Disk latency statistics",
+ * "column-descriptions": [
+ * {"title": "Disk name", "type": "disk"},
+ * {"title": "Count", "type": "int", "unit": "operations"},
+ * {"title": "Minimum duration", "type": "duration"},
+ * {"title": "Average duration", "type": "duration"},
+ * {"title": "Maximum duration", "type": "duration"},
+ * {"title": "Standard deviation", "type": "duration"}
+ * ]
+ * }
+ * }
+ * }
+ *
+ */
+
+ try {
+ JSONObject obj = new JSONObject(output);
+ fAnalysisTitle = obj.getString(LamiStrings.TITLE);
+
+ /* Very early scripts may not contain the "mi-version" */
+ JSONObject miVersion = obj.optJSONObject(LamiStrings.MI_VERSION);
+ if (miVersion == null) {
+ /* Before version 0.1 */
+ fUseProgressOutput = false;
+ } else {
+ int majorVersion = miVersion.getInt(LamiStrings.MAJOR);
+ if (majorVersion <= MAX_SUPPORTED_MAJOR_VERSION) {
+ fUseProgressOutput = true;
+ } else {
+ /* Unknown version, we do not support it */
+ return false;
+ }
+ }
+
+ JSONObject tableClasses = obj.getJSONObject(LamiStrings.TABLE_CLASSES);
+ @NonNull String[] tableClassNames = checkNotNullContents(JSONObject.getNames(tableClasses));
+
+ ImmutableMap.Builder<String, LamiTableClass> tablesBuilder = ImmutableMap.builder();
+ for (String tableClassName : tableClassNames) {
+ JSONObject tableClass = tableClasses.getJSONObject(tableClassName);
+
+ final String tableTitle = checkNotNull(tableClass.getString(LamiStrings.TITLE));
+ @NonNull JSONArray columnDescriptions = checkNotNull(tableClass.getJSONArray(LamiStrings.COLUMN_DESCRIPTIONS));
+
+ List<LamiTableEntryAspect> aspects = getAspectsFromColumnDescriptions(columnDescriptions);
+ Collection<LamiChartModel> chartModels = getPredefinedCharts().get(tableClassName);
+
+ tablesBuilder.put(tableClassName, new LamiTableClass(tableClassName, tableTitle, aspects, chartModels));
+ }
+
+ try {
+ fTableClasses = tablesBuilder.build();
+ } catch (IllegalArgumentException e) {
+ /*
+ * This is thrown if there are duplicate keys in the map
+ * builder.
+ */
+ throw new JSONException("Duplicate table class entry in " + fAnalysisTitle); //$NON-NLS-1$
+ }
+
+ } catch (JSONException e) {
+ /* Error in the parsing of the JSON, script is broken? */
+ Activator.instance().logError(e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ private static List<LamiTableEntryAspect> getAspectsFromColumnDescriptions(JSONArray columnDescriptions) throws JSONException {
+ ImmutableList.Builder<LamiTableEntryAspect> aspectsBuilder = new ImmutableList.Builder<>();
+ for (int j = 0; j < columnDescriptions.length(); j++) {
+ JSONObject column = columnDescriptions.getJSONObject(j);
+ DataType columnDataType;
+ String columnClass = column.optString(LamiStrings.CLASS, null);
+
+ if (columnClass == null) {
+ columnDataType = DataType.MIXED;
+ } else {
+ columnDataType = getDataTypeFromString(columnClass);
+ }
+
+ String columnTitle = column.optString(LamiStrings.TITLE, null);
+
+ if (columnTitle == null) {
+ columnTitle = String.format("%s #%d", columnDataType.getTitle(), j + 1); //$NON-NLS-1$
+ }
+
+ final int colIndex = j;
+ switch (columnDataType) {
+ case TIME_RANGE:
+ /*
+ * We will add 3 aspects, to represent the start, end and
+ * duration of this time range.
+ */
+ aspectsBuilder.add(new LamiTimeRangeBeginAspect(columnTitle, colIndex));
+ aspectsBuilder.add(new LamiTimeRangeEndAspect(columnTitle, colIndex));
+ aspectsBuilder.add(new LamiTimeRangeDurationAspect(columnTitle, colIndex));
+ break;
+
+ case TIMESTAMP:
+ aspectsBuilder.add(new LamiTimestampAspect(columnTitle, colIndex));
+ break;
+
+ case PROCESS:
+ aspectsBuilder.add(new LamiProcessNameAspect(columnTitle, colIndex));
+ aspectsBuilder.add(new LamiProcessPIDAspect(columnTitle, colIndex));
+ aspectsBuilder.add(new LamiProcessTIDAspect(columnTitle, colIndex));
+ break;
+
+ case IRQ:
+ aspectsBuilder.add(new LamiIRQTypeAspect(columnTitle, colIndex));
+ aspectsBuilder.add(new LamiIRQNameAspect(columnTitle, colIndex));
+ aspectsBuilder.add(new LamiIRQNumberAspect(columnTitle, colIndex));
+ break;
+
+ case DURATION:
+ aspectsBuilder.add(new LamiDurationAspect(columnTitle, colIndex));
+ break;
+
+ case MIXED:
+ aspectsBuilder.add(new LamiMixedAspect(columnTitle, colIndex));
+ break;
+
+ // $CASES-OMITTED$
+ default:
+ String units = column.optString(LamiStrings.UNIT, null);
+
+ if (units == null) {
+ units = columnDataType.getUnits();
+ }
+
+ /* We will add only one aspect representing the element */
+ LamiTableEntryAspect aspect = new LamiGenericAspect(columnTitle,
+ units, colIndex, columnDataType.isContinuous(), false);
+ aspectsBuilder.add(aspect);
+ break;
+ }
+ }
+ /*
+ * SWT quirk : we need an empty column at the end or else the last data
+ * column will clamp to the right edge of the view if it is
+ * right-aligned.
+ */
+ aspectsBuilder.add(LamiEmptyAspect.INSTANCE);
+
+ return aspectsBuilder.build();
+ }
+
+ private static DataType getDataTypeFromString(String value) throws JSONException {
+ try {
+ return DataType.fromString(value);
+ } catch (IllegalArgumentException e) {
+ throw new JSONException("Unrecognized data type: " + value); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Get the title of this analysis, as read from the script's metadata.
+ *
+ * @return The analysis title. Should not be null after the initialization
+ * completed successfully.
+ */
+ public @Nullable String getAnalysisTitle() {
+ return fAnalysisTitle;
+ }
+
+ /**
+ * Get the result table classes defined by this analysis, as read from the
+ * script's metadata.
+ *
+ * @return The analysis' result table classes. Should not be null after the
+ * execution completed successfully.
+ */
+ public @Nullable Map<String, LamiTableClass> getTableClasses() {
+ return fTableClasses;
+ }
+
+ /**
+ * Print the full command that will be run when calling {@link #execute},
+ * with the exception of the 'extraParams' that will be passed to execute().
+ *
+ * This can be used to display the command in the UI before it is actually
+ * run.
+ *
+ * @param trace
+ * The trace on which to run the analysis
+ * @param range
+ * The time range to specify. Null will not specify a time range,
+ * which means the whole trace will be taken.
+ * @return The command as a single, space-separated string
+ */
+ public String getFullCommandAsString(ITmfTrace trace, @Nullable TmfTimeRange range) {
+ String tracePath = checkNotNull(trace.getPath());
+
+ ImmutableList.Builder<String> builder = getBaseCommand(range);
+ /*
+ * We can add double-quotes around the trace path, which could contain
+ * spaces, so that the resulting command can be easily copy-pasted into
+ * a shell.
+ */
+ builder.add(DOUBLE_QUOTES + tracePath + DOUBLE_QUOTES);
+ List<String> list = builder.build();
+ String ret = list.stream().collect(Collectors.joining(" ")); //$NON-NLS-1$
+ return checkNotNull(ret);
+ }
+
+ /**
+ * Get the base part of the command that will be executed to run this
+ * analysis, supplying the given time range. Base part meaning:
+ *
+ * <pre>
+ * [script executable] [statically-defined parameters] [--begin/--end (if applicable)]
+ * </pre>
+ *
+ * Note that it does not include the path to the trace, that is to be added
+ * separately.
+ *
+ * @param range
+ * The time range that will be passed
+ * @return The elements of the command
+ */
+ private ImmutableList.Builder<String> getBaseCommand(@Nullable TmfTimeRange range) {
+ long begin = 0;
+ long end = 0;
+ if (range != null) {
+ begin = range.getStartTime().getValue();
+ end = range.getEndTime().getValue();
+ }
+
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.addAll(fScriptCommand);
+
+ if (fUseProgressOutput) {
+ builder.add(PROGRESS_FLAG);
+ }
+
+ if (range != null) {
+ builder.add(BEGIN_FLAG).add(String.valueOf(begin));
+ builder.add(END_FLAG).add(String.valueOf(end));
+ }
+ return builder;
+ }
+
+ /**
+ * Call the currently defined LAMI script with the given arguments.
+ *
+ * @param timeRange
+ * The time range. Null for the whole trace.
+ * @param monitor
+ * The progress monitor used to report progress
+ * @return The script's output, formatted into {@link LamiTableEntry}'s.
+ * @throws OnDemandAnalysisException
+ * If execution did not terminate normally
+ */
+ @Override
+ public List<LamiResultTable> execute(ITmfTrace trace, @Nullable TmfTimeRange timeRange,
+ String extraParams, IProgressMonitor monitor) throws OnDemandAnalysisException {
+ /* Should have been called already, but in case it was not */
+ initialize();
+
+ final @NonNull String tracePath = checkNotNull(trace.getPath());
+ final @NonNull String[] splitParams = extraParams.trim().split(" "); //$NON-NLS-1$
+
+ ImmutableList.Builder<String> builder = getBaseCommand(timeRange);
+
+ if (!extraParams.trim().equals("")) { //$NON-NLS-1$
+ builder.addAll(Arrays.asList(splitParams));
+ }
+ builder.add(tracePath);
+ List<String> command = builder.build();
+
+ Activator.instance().logInfo(RUNNING_MESSAGE + ' ' + command.toString());
+ String output = getResultsFromCommand(command, monitor);
+
+ if (output.isEmpty()) {
+ throw new OnDemandAnalysisException(Messages.LamiAnalysis_NoResults);
+ }
+
+ /*
+ * {
+ * "results": [
+ * {
+ * "time-range": {
+ * "type": "time-range",
+ * "begin": 1444334398154194201,
+ * "end": 1444334425194487548
+ * },
+ * "class": "syscall-latency",
+ * "data": [
+ * [
+ * {"type": "syscall", "name": "open"},
+ * 45,
+ * {"type": "duration", "value": 5562},
+ * {"type": "duration", "value": 13835},
+ * {"type": "duration", "value": 77683},
+ * {"type": "duration", "value": 15263}
+ * ],
+ * [
+ * {"type": "syscall", "name": "read"},
+ * 109,
+ * {"type": "duration", "value": 316},
+ * {"type": "duration", "value": 5774},
+ * {"type": "duration", "value": 62569},
+ * {"type": "duration", "value": 9277}
+ * ]
+ * ]
+ * },
+ * {
+ * "time-range": {
+ * "type": "time-range",
+ * "begin": 1444334425194487549,
+ * "end": 1444334425254887190
+ * },
+ * "class": "syscall-latency",
+ * "data": [
+ * [
+ * {"type": "syscall", "name": "open"},
+ * 45,
+ * {"type": "duration", "value": 1578},
+ * {"type": "duration", "value": 16648},
+ * {"type": "duration", "value": 15444},
+ * {"type": "duration", "value": 68540}
+ * ],
+ * [
+ * {"type": "syscall", "name": "read"},
+ * 109,
+ * {"type": "duration", "value": 78},
+ * {"type": "duration", "value": 1948},
+ * {"type": "duration", "value": 11184},
+ * {"type": "duration", "value": 94670}
+ * ]
+ * ]
+ * }
+ * ]
+ * }
+ *
+ */
+
+ ImmutableList.Builder<LamiResultTable> resultsBuilder = new ImmutableList.Builder<>();
+
+ try {
+ JSONObject obj = new JSONObject(output);
+ JSONArray results = obj.getJSONArray(LamiStrings.RESULTS);
+
+ if (results.length() == 0) {
+ /*
+ * No results were reported. This may be normal, but warn the
+ * user why a report won't be created.
+ */
+ throw new OnDemandAnalysisException(Messages.LamiAnalysis_NoResults);
+ }
+
+ for (int i = 0; i < results.length(); i++) {
+ JSONObject result = results.getJSONObject(i);
+
+ /* Parse the time-range */
+ JSONObject trObject = result.getJSONObject(LamiStrings.TIME_RANGE);
+ long start = trObject.getLong(LamiStrings.BEGIN);
+ long end = trObject.getLong(LamiStrings.END);
+ LamiTimeRange tr = new LamiTimeRange(start, end);
+
+ /* Parse the table's class */
+ LamiTableClass tableClass;
+ JSONObject tableClassObject = result.optJSONObject(LamiStrings.CLASS);
+ if (tableClassObject == null) {
+ /*
+ * "class" is just a standard string, indicating we use a
+ * metadata-defined table class as-is
+ */
+ @NonNull String tableClassName = checkNotNull(result.getString(LamiStrings.CLASS));
+ tableClass = getTableClassFromName(tableClassName);
+
+ // FIXME Rest will become more generic eventually in the LAMI format.
+ } else if (tableClassObject.has(LamiStrings.INHERIT)) {
+ /*
+ * Dynamic title: We reuse an existing table class but
+ * override the title.
+ */
+ String baseTableName = checkNotNull(tableClassObject.getString(LamiStrings.INHERIT));
+ LamiTableClass baseTableClass = getTableClassFromName(baseTableName);
+ String newTitle = checkNotNull(tableClassObject.getString(LamiStrings.TITLE));
+
+ tableClass = new LamiTableClass(baseTableClass, newTitle);
+ } else {
+ /*
+ * Dynamic column descriptions: we implement a new table
+ * class entirely.
+ */
+ String title = checkNotNull(tableClassObject.getString(LamiStrings.TITLE));
+ JSONArray columnDescriptions = checkNotNull(tableClassObject.getJSONArray(LamiStrings.COLUMN_DESCRIPTIONS));
+ List<LamiTableEntryAspect> aspects = getAspectsFromColumnDescriptions(columnDescriptions);
+
+ tableClass = new LamiTableClass(nullToEmptyString(Messages.LamiAnalysis_DefaultDynamicTableName), title, aspects, Collections.EMPTY_SET);
+ }
+
+ /* Parse the "data", which is the array of rows */
+ JSONArray data = result.getJSONArray(LamiStrings.DATA);
+ ImmutableList.Builder<LamiTableEntry> dataBuilder = new ImmutableList.Builder<>();
+
+ for (int j = 0; j < data.length(); j++) {
+ /* A row is an array of cells */
+ JSONArray row = data.getJSONArray(j);
+ ImmutableList.Builder<LamiData> rowBuilder = ImmutableList.builder();
+
+ for (int k = 0; k < row.length(); k++) {
+ Object cellObject = checkNotNull(row.get(k));
+ LamiData cellValue = LamiData.createFromObject(cellObject);
+ rowBuilder.add(cellValue);
+
+ }
+ dataBuilder.add(new LamiTableEntry(rowBuilder.build()));
+ }
+ resultsBuilder.add(new LamiResultTable(tr, tableClass, dataBuilder.build()));
+ }
+
+ } catch (JSONException e) {
+ /* Error parsing the output */
+ Activator.instance().logError(nullToEmptyString(e.getMessage()));
+ return Collections.EMPTY_LIST;
+ }
+
+ return resultsBuilder.build();
+ }
+
+ private LamiTableClass getTableClassFromName(String tableClassName) throws JSONException {
+ Map<String, LamiTableClass> map = checkNotNull(fTableClasses);
+ LamiTableClass tableClass = map.get(tableClassName);
+ if (tableClass == null) {
+ throw new JSONException("Table class " + tableClassName + //$NON-NLS-1$
+ " was not declared in the metadata"); //$NON-NLS-1$
+ }
+ return tableClass;
+ }
+
+ /**
+ * Get the output of an external command, used for getting the metadata.
+ * Cannot be cancelled, and will not report errors, simply returns null if
+ * the process ended abnormally.
+ */
+ private static @Nullable String getOutputFromCommand(List<String> command) {
+ try {
+ ProcessBuilder builder = new ProcessBuilder(command);
+ builder.redirectErrorStream(true);
+
+ Process p = builder.start();
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
+ int ret = p.waitFor();
+ String output = br.lines().collect(Collectors.joining());
+
+ return (ret == 0 ? output : null);
+ }
+ } catch (IOException | InterruptedException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get the results of invoking the specified command.
+ *
+ * The result should start with '{"results":...', as specified by the
+ * LAMI JSON protocol. The JSON itself may be split over multiple lines.
+ *
+ * @param command
+ * The command to run (program and its arguments)
+ * @return The analysis results
+ */
+ private static String getResultsFromCommand(List<String> command, IProgressMonitor monitor)
+ throws OnDemandAnalysisException {
+
+ final int scale = 1000;
+ double workedSoFar = 0.0;
+
+ ProcessCanceller cancellerRunnable = null;
+ Thread cancellerThread = null;
+
+ try {
+ monitor.beginTask(Messages.LamiAnalysis_MainTaskName, scale);
+
+ ProcessBuilder builder = new ProcessBuilder(command);
+ builder.redirectErrorStream(false);
+
+ Process p = checkNotNull(builder.start());
+
+ cancellerRunnable = new ProcessCanceller(p, monitor);
+ cancellerThread = new Thread(cancellerRunnable);
+ cancellerThread.start();
+
+ List<String> results = new ArrayList<>();
+
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
+ String line = in.readLine();
+ while (line != null && !line.matches("\\s*\\{.*")) { //$NON-NLS-1$
+ /*
+ * This is a line indicating progress, it has the form:
+ *
+ * 0.123 3000 of 5000 events processed
+ *
+ * The first part indicates the estimated fraction (out of
+ * 1.0) of work done. The second part is status text.
+ */
+
+ // Trim the line first to make sure the first character is
+ // significant
+ line = line.trim();
+
+ // Split at the first space
+ String[] elems = line.split(" ", 2); //$NON-NLS-1$
+
+ if (elems[0].matches("\\d.*")) { //$NON-NLS-1$
+ // It looks like we have a progress indication
+ try {
+ // Try parsing the number
+ double cumulativeWork = Double.parseDouble(elems[0]) * scale;
+ double workedThisLoop = cumulativeWork - workedSoFar;
+
+ // We're going backwards? Do not update the
+ // monitor's value
+ if (workedThisLoop > 0) {
+ monitor.internalWorked(workedThisLoop);
+ workedSoFar = cumulativeWork;
+ }
+
+ // There is a message: update the monitor's task name
+ if (elems.length >= 2) {
+ monitor.setTaskName(elems[1].trim());
+ }
+ } catch (NumberFormatException e) {
+ // Continue reading progress lines anyway
+ }
+ }
+
+ line = in.readLine();
+ }
+ while (line != null) {
+ /*
+ * We have seen the first line containing a '{', this is our
+ * JSON output!
+ */
+ results.add(line);
+ line = in.readLine();
+ }
+ }
+ int ret = p.waitFor();
+
+ if (monitor.isCanceled()) {
+ /* We were interrupted by the canceller thread. */
+ throw new OnDemandAnalysisException(null);
+ }
+
+ if (ret != 0) {
+ /*
+ * Something went wrong running the external script. We will
+ * gather the stderr and report it to the user.
+ */
+ BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+ String stdErrOutput = br.lines().collect(Collectors.joining("\n")); //$NON-NLS-1$
+ throw new OnDemandAnalysisException(stdErrOutput);
+ }
+
+ /* External script ended successfully, all is fine! */
+ String resultsStr = results.stream().collect(Collectors.joining());
+ return checkNotNull(resultsStr);
+
+ } catch (IOException | InterruptedException e) {
+ throw new OnDemandAnalysisException(Messages.LamiAnalysis_ExecutionInterrupted);
+
+ } finally {
+ if (cancellerRunnable != null) {
+ cancellerRunnable.setFinished();
+ }
+ if (cancellerThread != null) {
+ try {
+ cancellerThread.join();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ monitor.done();
+ }
+ }
+
+ private static class ProcessCanceller implements Runnable {
+
+ private final Process fProcess;
+ private final IProgressMonitor fMonitor;
+
+ private boolean fIsFinished = false;
+
+ public ProcessCanceller(Process process, IProgressMonitor monitor) {
+ fProcess = process;
+ fMonitor = monitor;
+ }
+
+ public void setFinished() {
+ fIsFinished = true;
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (!fIsFinished) {
+ Thread.sleep(500);
+ if (fMonitor.isCanceled()) {
+ fProcess.destroy();
+ return;
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+
+ }
+
+ @Override
+ public @NonNull String getName() {
+ return fName;
+ }
+
+ @Override
+ public boolean isUserDefined() {
+ return fIsUserDefined;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import java.util.List;
+
+import org.eclipse.tracecompass.tmf.core.analysis.ondemand.IOnDemandAnalysisReport;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Report generated by LAMI analyses.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiAnalysisReport implements IOnDemandAnalysisReport {
+
+ private final String fReportName;
+ private final List<LamiResultTable> fTables;
+
+ /**
+ * Constructor
+ *
+ * @param reportName
+ * Name of the report (to be shown in the UI)
+ * @param tables
+ * The result tables that are part of this report
+ */
+ public LamiAnalysisReport(String reportName, List<LamiResultTable> tables) {
+ fReportName = reportName;
+ fTables = ImmutableList.copyOf(tables);
+ }
+
+ @Override
+ public String getName() {
+ return fReportName;
+ }
+
+ /**
+ * Get the result tables of this report
+ *
+ * @return The result tables
+ */
+ public List<LamiResultTable> getTables() {
+ return fTables;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * UI Model of a LAMI chart. This object should contain all the information
+ * needed to create a chart in the GUI, independently of the actual chart
+ * implementation.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiChartModel {
+
+ /**
+ * Supported types of charts
+ */
+ public enum ChartType {
+ /** Bar chart */
+ BAR_CHART("Bar"), //$NON-NLS-1$
+
+ /** XY scatter chart */
+ XY_SCATTER("Scatter"), //$NON-NLS-1$
+
+ /**
+ * Pie chart
+ * FIXME NYI
+ */
+ PIE_CHART("Pie"); //$NON-NLS-1$
+
+ private final String fText;
+
+ private ChartType(final String text) {
+ fText = text;
+ }
+
+ @Override
+ public String toString() {
+ return fText;
+ }
+ }
+
+ private final ChartType fType;
+ private final String fName;
+ private final List<String> fXSeriesColumns;
+ private final List<String> fYSeriesColumns;
+ private final boolean fXAxisIsLog;
+ private final boolean fYAxisIsLog;
+
+
+ /**
+ * Constructor
+ *
+ * @param type
+ * The type of chart
+ * @param name
+ * The name of the chart
+ * @param xSeriesColumn
+ * The title of column used for the X axis
+ * @param ySeriesColumns
+ * The titles of the columns used for the series
+ * @param xAxisIsLog
+ * If the X-axis is log scale or not
+ * @param yAxisIsLog
+ * If the Y-axis is log scale or not
+ */
+ public LamiChartModel(ChartType type, String name, List<String> xSeriesColumn, List<String> ySeriesColumns,
+ boolean xAxisIsLog, boolean yAxisIsLog) {
+ fType = type;
+ fName = name;
+ fXSeriesColumns = ImmutableList.copyOf(xSeriesColumn);
+ fYSeriesColumns = ImmutableList.copyOf(ySeriesColumns);
+ fXAxisIsLog = xAxisIsLog;
+ fYAxisIsLog = yAxisIsLog;
+ }
+
+ /**
+ * Get the chart type.
+ *
+ * @return The chart type
+ */
+ public ChartType getChartType() {
+ return fType;
+ }
+
+ /**
+ * Get the chart's name.
+ *
+ * @return The chart name
+ */
+ public String getName() {
+ return fName;
+ }
+
+ /**
+ * Get the names of the columns used for the X part of a series.
+ *
+ * @return The columns used for the X-axis
+ */
+ public List<String> getXSeriesColumns() {
+ return fXSeriesColumns;
+ }
+
+ /**
+ * Get the names of the columns used for the Y part of a series.
+ *
+ * @return The columns used for the series
+ */
+ public List<String> getYSeriesColumns() {
+ return fYSeriesColumns;
+ }
+
+ /**
+ * Return if the X-axis should use a log scale.
+ *
+ * @return If the X-axis is log scale
+ */
+ public boolean xAxisIsLog() {
+ return fXAxisIsLog;
+ }
+
+ /**
+ * Return if the Y-axis should use a log scale.
+ *
+ * @return If Y-axis is log scale
+ */
+ public boolean yAxisIsLog() {
+ return fYAxisIsLog;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Jonathan Rajotte-Julien
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.util.Map.Entry;
+
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.common.collect.BiMap;
+
+/**
+ * Format label based on a given Map<String, Integer>
+ *
+ * @author Jonathan Rajotte-Julien
+ */
+public class LamiLabelFormat extends Format {
+
+ private static final long serialVersionUID = 4939553034329681316L;
+
+ private static final String SWTCHART_EMPTY_LABEL = " "; //$NON-NLS-1$
+ private static final String UNKNOWN_REPRESENTATION = "?"; //$NON-NLS-1$
+ private final BiMap<@Nullable String, Integer> fMap;
+
+ /**
+ * Constructor
+ *
+ * @param map
+ * Map of indices to labels
+ */
+ public LamiLabelFormat(BiMap<@Nullable String, Integer> map) {
+ super();
+ fMap = map;
+ }
+
+ @Override
+ public @Nullable StringBuffer format(@Nullable Object obj, @Nullable StringBuffer toAppendTo, @Nullable FieldPosition pos) {
+ if (obj == null || toAppendTo == null) {
+ return new StringBuffer(SWTCHART_EMPTY_LABEL);
+ }
+
+ Double doubleObj = (Double) obj;
+
+ /*
+ * Return a string buffer with a space in it since SWT does not like to
+ * draw empty strings.
+ */
+ if ((doubleObj % 1 != 0) || !fMap.containsValue((doubleObj.intValue()))) {
+ return new StringBuffer(SWTCHART_EMPTY_LABEL);
+ }
+
+ for (Entry<@Nullable String, Integer> entry : fMap.entrySet()) {
+ /*
+ * FIXME: Find if the elements are the same, based on their double
+ * value, because SWTChart uses double values so we do the same
+ * check. The loss of precision could lead to false positives.
+ */
+ if (Double.compare(entry.getValue().doubleValue(), doubleObj.doubleValue()) == 0) {
+ if (entry.getKey() == null) {
+ return new StringBuffer(UNKNOWN_REPRESENTATION);
+ }
+ return toAppendTo.append(entry.getKey());
+ }
+ }
+ return new StringBuffer(SWTCHART_EMPTY_LABEL);
+ }
+
+ @Override
+ public @Nullable Object parseObject(@Nullable String source, @Nullable ParsePosition pos) {
+ return fMap.get(source);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import java.util.List;
+
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Class holding the results contained in one table outputted by a LAMI analysis.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiResultTable {
+
+ private final LamiTimeRange fTimeRange;
+ private final LamiTableClass fTableClass;
+ private final List<LamiTableEntry> fEntries;
+
+ /**
+ * Construct a new table from its components.
+ *
+ * @param timeRange
+ * The time range represented by this table. Some analyses
+ * separate tables by time range "buckets".
+ * @param tableClass
+ * The class of this table
+ * @param entries
+ * The list of entries, or rows, contained in this table
+ */
+ public LamiResultTable(LamiTimeRange timeRange, LamiTableClass tableClass,
+ Iterable<LamiTableEntry> entries) {
+ fTimeRange = timeRange;
+ fTableClass = tableClass;
+ fEntries = ImmutableList.copyOf(entries);
+ }
+
+ /**
+ * Get the time range of this table.
+ *
+ * @return The time range
+ */
+ public LamiTimeRange getTimeRange() {
+ return fTimeRange;
+ }
+
+ /**
+ * Get the class of this table.
+ *
+ * @return The table class
+ */
+ public LamiTableClass getTableClass() {
+ return fTableClass;
+ }
+
+ /**
+ * Get the list of entries contained in this table.
+ *
+ * @return The table entries
+ */
+ public List<LamiTableEntry> getEntries() {
+ return fEntries;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * The model of a table element in a LAMI analysis script output.
+ *
+ * Contains all the required information to build the actual UI table layout.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiTableClass {
+
+ private final String fTableClassName;
+ private final String fTableTitle;
+ private final List<LamiTableEntryAspect> fAspects;
+ private final Collection<LamiChartModel> fPredefinedViews;
+
+ /**
+ * Standard constructor. Build a new table class by specifying all
+ * parameters.
+ *
+ * @param tableClassName
+ * The name of the table's class
+ * @param tableTitle
+ * The title of this table
+ * @param columnAspects
+ * The list of aspects representing the columsn of this table
+ * @param predefinedViews
+ * The pre-defined views of this analysis. Viewers will be
+ * created for these views by default.
+ */
+ public LamiTableClass(String tableClassName, String tableTitle,
+ List<LamiTableEntryAspect> columnAspects, Collection<LamiChartModel> predefinedViews) {
+ fTableClassName = tableClassName;
+ fTableTitle = tableTitle;
+ fAspects = checkNotNull(ImmutableList.copyOf(columnAspects));
+ fPredefinedViews = ImmutableList.copyOf(predefinedViews);
+ }
+
+ /**
+ * "Extension" constructor. Use an existing table class but override the
+ * table name.
+ *
+ * @param baseClass
+ * The base table class
+ * @param replacementTitle
+ * The new title to use instead
+ */
+ public LamiTableClass(LamiTableClass baseClass, String replacementTitle) {
+ fTableClassName = Messages.LamiAnalysis_ExtendedTableNamePrefix + ' ' + baseClass.fTableClassName;
+ fTableTitle = replacementTitle;
+ fAspects = baseClass.fAspects; // We know it's an immutable list
+ fPredefinedViews = baseClass.fPredefinedViews; // idem
+ }
+
+ /**
+ * Get the name of the table's class.
+ *
+ * @return The table class name
+ */
+ public String getTableClassName() {
+ return fTableClassName;
+ }
+
+ /**
+ * Get the title of this table.
+ *
+ * @return The table title
+ */
+ public String getTableTitle() {
+ return fTableTitle;
+ }
+
+ /**
+ * Get the aspects of this table's columns.
+ *
+ * @return The table aspects
+ */
+ public List<LamiTableEntryAspect> getAspects() {
+ return fAspects;
+ }
+ /**
+ * Get the pre-defined views of this table.
+ *
+ * @return The predefined views
+ */
+ public Collection<LamiChartModel> getPredefinedViews() {
+ return fPredefinedViews;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimestamp;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Entry of a LAMI output. Usually corresponds to one row in a JSON LAMI table
+ * output.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiTableEntry {
+
+ private final List<LamiData> fValues;
+
+ /**
+ * Constructor
+ *
+ * @param values Values contained in this row
+ */
+ public LamiTableEntry(List<LamiData> values) {
+ fValues = checkNotNull(ImmutableList.copyOf(values));
+ }
+
+ /**
+ * Get the value at a given index
+ *
+ * @param index
+ * Index to look at
+ * @return The value at this index
+ */
+ public LamiData getValue(int index) {
+ return fValues.get(index);
+ }
+
+ /**
+ * Get the time range represented by this row.
+ *
+ * If more than one exists, one of them (usually the first) is returned.
+ *
+ * If there are no time ranges in this row, null is returned.
+ *
+ * @return The time range of this row
+ */
+ public @Nullable LamiTimeRange getCorrespondingTimeRange() {
+ /*
+ * If there is one or more time range(s) in the values, return the first
+ * one we find directly.
+ */
+ Optional<LamiTimeRange> oTimerange = fValues.stream()
+ .filter(data -> (data instanceof LamiTimeRange))
+ .<@NonNull LamiTimeRange> map(data -> (LamiTimeRange) data)
+ .findFirst();
+ if (oTimerange.isPresent()) {
+ return oTimerange.get();
+ }
+
+ /* Look for individual timestamps instead */
+ List<LamiTimestamp> timestamps = fValues.stream()
+ .filter(data -> (data instanceof LamiTimestamp))
+ .<@NonNull LamiTimestamp> map(data -> (LamiTimestamp) data)
+ .collect(Collectors.toList());
+
+ if (timestamps.size() > 1) {
+ /* We can try using the first two timestamps to create a range (making sure it's valid) */
+ long first = timestamps.get(0).getValue();
+ long second = timestamps.get(1).getValue();
+ if (second >= first) {
+ return new LamiTimeRange(first, second);
+ }
+ }
+
+ if (!timestamps.isEmpty()) {
+ /* If there is only one timestamp, use it to create a punctual range */
+ long ts = timestamps.get(0).getValue();
+ return new LamiTimeRange(ts, ts);
+ }
+
+ /* Didn't find any timestamp we can't use */
+ return null;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc. 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestampFormat;
+
+/**
+ * Formatter for time stamps
+ */
+public class LamiTimeStampFormat extends SimpleDateFormat {
+
+ private static final long serialVersionUID = 4285447886537779762L;
+
+ private final TmfTimestampFormat fFormat;
+
+ // ------------------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------------------
+
+ /**
+ * The default constructor (uses the default time format)
+ */
+ public LamiTimeStampFormat() {
+ fFormat = TmfTimestampFormat.getDefaulTimeFormat();
+ }
+
+ /**
+ * The normal constructor
+ *
+ * @param pattern the format pattern
+ */
+ public LamiTimeStampFormat(String pattern) {
+ fFormat = new TmfTimestampFormat(pattern);
+ }
+
+ // ------------------------------------------------------------------------
+ // Operations
+ // ------------------------------------------------------------------------
+
+ @Override
+ public StringBuffer format(@Nullable Date date, @Nullable StringBuffer toAppendTo,
+ @Nullable FieldPosition fieldPosition) {
+ if (date != null && toAppendTo != null) {
+ long time = date.getTime();
+ toAppendTo.append(fFormat.format(time));
+ return toAppendTo;
+ }
+ return new StringBuffer();
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Jonathan Rajotte-Julien
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+
+/**
+ * Description of a series to use in LAMI charts.
+ *
+ * Contains the aspects to use on the X and Y axes (one per axis).
+ *
+ * @author Jonathan Rajotte-Julien
+ */
+public class LamiXYSeriesDescription {
+
+ private final LamiTableEntryAspect fXAspect;
+ private final LamiTableEntryAspect fYAspect;
+
+ /**
+ * Constructor
+ *
+ * @param xAspect
+ * The aspect to use on the X axis
+ * @param yAspect
+ * The aspect to use on the Y axis
+ */
+ public LamiXYSeriesDescription(LamiTableEntryAspect xAspect, LamiTableEntryAspect yAspect) {
+ fXAspect = xAspect;
+ fYAspect = yAspect;
+ }
+
+ /**
+ * Get the aspect corresponding to the X axis.
+ *
+ * @return The X-axis aspect
+ */
+ public LamiTableEntryAspect getXAspect() {
+ return fXAspect;
+ }
+
+ /**
+ * Get the aspect corresponding to the Y axis.
+ *
+ * @return The Y-axis aspect
+ */
+ public LamiTableEntryAspect getYAspect() {
+ return fYAspect;
+ }
+
+
+ @Override
+ public String toString() {
+ return "x:" + fXAspect.getLabel() + " y:" + fYAspect.getLabel(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fXAspect, fYAspect);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ LamiXYSeriesDescription other = (LamiXYSeriesDescription) obj;
+ if (!fXAspect.equals(other.fXAspect)) {
+ return false;
+ }
+ if (!fYAspect.equals(other.fYAspect)) {
+ return false;
+ }
+ return true;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String LamiAnalysis_DefaultDynamicTableName;
+
+ public static String LamiAnalysis_MainTaskName;
+ public static String LamiAnalysis_NoResults;
+ public static String LamiAnalysis_ExecutionInterrupted;
+
+ public static String LamiAnalysis_ExtendedTableNamePrefix;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2015, 2016 EfficiOS Inc. 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
+###############################################################################
+
+LamiAnalysis_DefaultDynamicTableName = Dynamic Table
+
+LamiAnalysis_MainTaskName = Invoking external analysis script
+LamiAnalysis_NoResults = No results were returned.
+LamiAnalysis_ExecutionInterrupted = Execution was interrupted.
+
+LamiAnalysis_ExtendedTableNamePrefix = Extended
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+class LamiBitrate extends LamiInteger {
+ public LamiBitrate(long value) {
+ super(value);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+class LamiBoolean extends LamiData {
+
+ private static final LamiBoolean TRUE = new LamiBoolean(true);
+ private static final LamiBoolean FALSE = new LamiBoolean(false);
+
+ public static LamiBoolean instance(boolean value) {
+ return (value ? TRUE : FALSE);
+ }
+
+ private final boolean fValue;
+
+ private LamiBoolean(boolean value) {
+ fValue = value;
+ }
+
+ public boolean getValue() {
+ return fValue;
+ }
+
+ @Override
+ public @Nullable String toString() {
+ return (fValue ?
+ nullToEmptyString(Messages.LamiBoolean_Yes) :
+ nullToEmptyString(Messages.LamiBoolean_No));
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+class LamiCPU extends LamiInteger {
+ public LamiCPU(long value) {
+ super(value);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc. 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.LamiStrings;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Base class for data types allowed in LAMI analysis scripts JSON output.
+ *
+ * @author Alexandre Montplaisir
+ * @author Philippe Proulx
+ */
+public abstract class LamiData {
+
+ /**
+ * Enum of all the valid data types
+ */
+ @SuppressWarnings("javadoc")
+ public enum DataType {
+
+ /* Generic JSON types */
+ STRING("string", "Value", false, null, LamiString.class), //$NON-NLS-1$ //$NON-NLS-2$
+ INT("int", "Value", true, null, LamiInteger.class), //$NON-NLS-1$ //$NON-NLS-2$
+ FLOAT("float", "Value", true, null, LamiNumber.class), //$NON-NLS-1$ //$NON-NLS-2$
+ NUMBER("number", "Value", true, null, LamiNumber.class), //$NON-NLS-1$ //$NON-NLS-2$
+ BOOL("bool", "Value", false, null, LamiBoolean.class), //$NON-NLS-1$ //$NON-NLS-2$
+
+ /* Lami-specific data types */
+ RATIO("ratio", "Ratio", true, "%", LamiRatio.class), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ TIMESTAMP("timestamp", "Timestamp", true, "ns", LamiTimestamp.class), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ TIME_RANGE("time-range", "Time range", true, null, LamiTimeRange.class), //$NON-NLS-1$ //$NON-NLS-2$
+ DURATION("duration", "Duration", true, "ns", LamiDuration.class), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ SIZE("size", "Size", true, Messages.LamiData_UnitBytes, LamiSize.class), //$NON-NLS-1$ //$NON-NLS-2$
+ BITRATE("bitrate", "Bitrate", true, Messages.LamiData_UnitBitsPerSecond, LamiBitrate.class), //$NON-NLS-1$ //$NON-NLS-2$
+ SYSCALL("syscall", "System call", false, null, LamiSystemCall.class), //$NON-NLS-1$ //$NON-NLS-2$
+ PROCESS("process", "Process", false, null, LamiProcess.class), //$NON-NLS-1$ //$NON-NLS-2$
+ PATH("path", "Path", false, null, LamiPath.class), //$NON-NLS-1$ //$NON-NLS-2$
+ FD("fd", "File descriptor", false, null, LamiFileDescriptor.class), //$NON-NLS-1$ //$NON-NLS-2$
+ IRQ("irq", "IRQ", false, null, LamiIRQ.class), //$NON-NLS-1$ //$NON-NLS-2$
+ CPU("cpu", "CPU", false, null, LamiCPU.class), //$NON-NLS-1$ //$NON-NLS-2$
+ DISK("disk", "Disk", false, null, LamiDisk.class), //$NON-NLS-1$ //$NON-NLS-2$
+ PART("part", "Disk partition", false, null, LamiDiskPartition.class), //$NON-NLS-1$ //$NON-NLS-2$
+ NETIF("netif", "Network interface", false, null, LamiNetworkInterface.class), //$NON-NLS-1$ //$NON-NLS-2$
+ UNKNOWN("unknown", "Value", false, null, LamiUnknown.class), //$NON-NLS-1$ //$NON-NLS-2$
+ MIXED("mixed", "Value", false, null, null); //$NON-NLS-1$ //$NON-NLS-2$
+
+ private final String fName;
+ private final String fTitle;
+ private final boolean fIsContinuous;
+ private final @Nullable String fUnits;
+ private final @Nullable Class<?> fClass;
+
+ private DataType(String name, String title, boolean isContinous, @Nullable String units, @Nullable Class<?> cls) {
+ fName = name;
+ fTitle = title;
+ fIsContinuous = isContinous;
+ fUnits = units;
+ fClass = cls;
+ }
+
+ /**
+ * Indicates if this data type represents a continuous numerical value.
+ *
+ * For example, time or bitrates are continuous values, but CPU or IRQ
+ * numbers are not (you can't have CPU 1.5!)
+ *
+ * @return If this aspect is continuous
+ */
+ public boolean isContinuous() {
+ return fIsContinuous;
+ }
+
+ /**
+ * Get the units of this data type, if any.
+ *
+ * @return The units, or <code>null</code> if there are no units
+ */
+ public @Nullable String getUnits() {
+ return fUnits;
+ }
+
+ /**
+ * The default title for columns containing these units.
+ *
+ * @return The data type's column title
+ */
+ public String getTitle() {
+ return fTitle;
+ }
+
+ /**
+ * Get the data type from its JSON string representation.
+ *
+ * @param value
+ * The string
+ * @return The corresponding data type
+ */
+ public static DataType fromString(String value) {
+ for (DataType type : DataType.values()) {
+ if (type.fName.equals(value)) {
+ return type;
+ }
+ }
+ throw new IllegalArgumentException("Unrecognized type: " + value);
+ }
+
+ /**
+ * Get the date type enum element from its implementation Class.
+ *
+ * @param cls
+ * The data type class
+ * @return The data type
+ */
+ public static @Nullable DataType fromClass(Class<? extends LamiData> cls) {
+ for (DataType type : DataType.values()) {
+ if (cls.equals(type.fClass)) {
+ return type;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ @Override
+ public abstract @Nullable String toString();
+
+ // ------------------------------------------------------------------------
+ // Convenience methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Convenience method to get the "value" field from a JSON object. Many LAMI
+ * types have a "value" field.
+ *
+ * @param obj
+ * The JSON object
+ * @return The read value
+ * @throws JSONException
+ * If the object does not actually have a "value" field.
+ */
+ private static final long getJSONObjectLongValue(JSONObject obj) throws JSONException {
+ return obj.getLong(LamiStrings.VALUE);
+ }
+
+ /**
+ * Convenience method to get the "name" field from a JSON object. Many LAMI
+ * types have a "nam" field.
+ *
+ * @param obj
+ * The JSON object
+ * @return The read name
+ * @throws JSONException
+ * If the object does not actually have a "name" field.
+ */
+ private static final String getJSONObjectStringName(JSONObject obj) throws JSONException {
+ return checkNotNull(obj.getString(LamiStrings.NAME));
+ }
+
+ // ------------------------------------------------------------------------
+ // "Factory" methods and helpers
+ // ------------------------------------------------------------------------
+
+ /**
+ * Factory method to build a new LamiData object from either a
+ * {@link JSONObject} or a standard Java {@link Object} representing a
+ * primitive type.
+ *
+ * @param obj
+ * The source object
+ * @return The corresponding LamiData object
+ * @throws JSONException
+ * If the object type is not supported
+ */
+ public static LamiData createFromObject(Object obj) throws JSONException {
+ if (obj instanceof JSONObject) {
+ return createFromJsonObject((JSONObject) obj);
+ } else if (obj.equals(JSONObject.NULL)) {
+ return LamiEmpty.INSTANCE;
+ } else {
+ return createFromPrimitiveObject(obj);
+ }
+ }
+
+ @FunctionalInterface
+ private static interface CheckedJSONExceptionFunction<T, R> {
+ R apply(T t) throws JSONException;
+ }
+
+ /**
+ * Map returning the Functions to build new LAMI objects for JSON primitive
+ * types
+ */
+ private static final Map<Class<?>, Function<Object, LamiData>> PRIMITIVE_TYPE_GENERATOR;
+ static {
+ ImmutableMap.Builder<Class<?>, Function<Object, LamiData>> primitiveTypeGenBuilder = ImmutableMap.builder();
+ primitiveTypeGenBuilder.put(Boolean.class, (o) -> LamiBoolean.instance((Boolean) o));
+ primitiveTypeGenBuilder.put(Integer.class, (o) -> new LamiInteger(((Integer) o).longValue()));
+ primitiveTypeGenBuilder.put(Long.class, (o) -> new LamiInteger((Long) o));
+ primitiveTypeGenBuilder.put(Double.class, (o) -> new LamiNumber((Double) o));
+ primitiveTypeGenBuilder.put(String.class, (o) -> new LamiString((String) o));
+ PRIMITIVE_TYPE_GENERATOR = primitiveTypeGenBuilder.build();
+ }
+
+ /**
+ * Map returning the Functions to build new LAMI objects for LAMI-specific
+ * types
+ */
+ private static final Map<String, CheckedJSONExceptionFunction<JSONObject, LamiData>> COMPLEX_TYPE_GENERATOR;
+ static {
+ ImmutableMap.Builder<String, CheckedJSONExceptionFunction<JSONObject, LamiData>> complexTypeGenBuilder = ImmutableMap.builder();
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_BITRATE, (obj) -> new LamiBitrate(getJSONObjectLongValue(obj)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_CPU, (obj) -> new LamiCPU(obj.getLong(LamiStrings.ID)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_DISK, (obj) -> new LamiDisk(getJSONObjectStringName(obj)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_DURATION, (obj) -> new LamiDuration(getJSONObjectLongValue(obj)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_PART, (obj) -> new LamiDiskPartition(getJSONObjectStringName(obj)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_FD, (obj) -> new LamiFileDescriptor(obj.getLong(LamiStrings.FD)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_NETIF, (obj) -> new LamiNetworkInterface(getJSONObjectStringName(obj)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_PATH, (obj) -> new LamiPath(checkNotNull(obj.getString(LamiStrings.PATH))));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_PROCESS, (obj) -> {
+ String name = obj.optString(LamiStrings.NAME);
+ Long pid = (obj.has(LamiStrings.PID) ? obj.getLong(LamiStrings.PID) : null);
+ Long tid = (obj.has(LamiStrings.TID) ? obj.getLong(LamiStrings.TID) : null);
+
+ return new LamiProcess(name, pid, tid);
+ });
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_RATIO, (obj) -> new LamiRatio(obj.getDouble(LamiStrings.VALUE)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_IRQ, (obj) -> {
+ LamiIRQ.Type irqType = LamiIRQ.Type.HARD;
+
+ if (obj.has(LamiStrings.HARD)) {
+ boolean isHardIrq = obj.getBoolean(LamiStrings.HARD);
+ irqType = (isHardIrq ? LamiIRQ.Type.HARD : LamiIRQ.Type.SOFT);
+ }
+
+ int nr = obj.getInt(LamiStrings.NR);
+ String name = obj.optString(LamiStrings.NAME);
+
+ return new LamiIRQ(irqType, nr, name);
+ });
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_SIZE, (obj) -> new LamiSize(getJSONObjectLongValue(obj)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_SYSCALL, (obj) -> new LamiSystemCall(getJSONObjectStringName(obj)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_TIME_RANGE, (obj) -> {
+ long begin = obj.getLong(LamiStrings.BEGIN);
+ long end = obj.getLong(LamiStrings.END);
+
+ return new LamiTimeRange(begin, end);
+ });
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_TIMESTAMP, (obj) -> new LamiTimestamp(getJSONObjectLongValue(obj)));
+ complexTypeGenBuilder.put(LamiStrings.DATA_CLASS_UNKNOWN, (obj) -> LamiUnknown.INSTANCE);
+ COMPLEX_TYPE_GENERATOR = complexTypeGenBuilder.build();
+ }
+
+ /**
+ * Create a new LamiData for a primitive type (Integer, String, etc.)
+ *
+ * @param obj
+ * The source object
+ * @return A new corresponding LamiData object
+ * @throws JSONException
+ * If the object type is not supported
+ */
+ private static LamiData createFromPrimitiveObject(Object obj) throws JSONException {
+ Function<Object, LamiData> func = PRIMITIVE_TYPE_GENERATOR.get(obj.getClass());
+ if (func == null) {
+ throw new JSONException("Unhandled type: " + obj.toString() + " of type " + obj.getClass().toString()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ /* We never return null in the implementations */
+ return checkNotNull(func.apply(obj));
+ }
+
+ /**
+ * Create a new LamiData for a LAMI-specific type from a {@link JSONObject}.
+ *
+ * @param obj
+ * The source object
+ * @return A new corresponding LamiData object
+ * @throws JSONException
+ * If the object type is not supported
+ */
+ private static LamiData createFromJsonObject(JSONObject obj) throws JSONException {
+ String dataClass = obj.optString(LamiStrings.CLASS);
+
+ if (dataClass == null) {
+ throw new JSONException("Cannot find data class"); //$NON-NLS-1$
+ }
+
+ CheckedJSONExceptionFunction<JSONObject, LamiData> func = COMPLEX_TYPE_GENERATOR.get(dataClass);
+
+ if (func == null) {
+ throw new JSONException(String.format("Unsupported data class \"%s\"", dataClass)); //$NON-NLS-1$
+ }
+
+ return func.apply(obj);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+/**
+ * Lami 'disk' value.
+ *
+ * A disk can be "sda". It may contain partitions.
+ *
+ * @author Philippe Proulx
+ */
+class LamiDisk extends LamiString {
+
+ public LamiDisk(String value) {
+ super(value);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+/**
+ * Lami 'disk partition' value.
+ *
+ * A disk partition is something like "sda2".
+ *
+ * @author Philippe Proulx
+ */
+class LamiDiskPartition extends LamiString {
+
+ public LamiDiskPartition(String value) {
+ super(value);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+/**
+ * Duration data element
+ *
+ * @author Philippe Proulx
+ */
+public class LamiDuration extends LamiInteger {
+
+ /**
+ * Constructor
+ *
+ * @param value
+ * The duration value (as a long)
+ */
+ public LamiDuration(long value) {
+ super(value);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+class LamiEmpty extends LamiData {
+
+ public static final LamiEmpty INSTANCE = new LamiEmpty();
+
+ private LamiEmpty() {}
+
+ @Override
+ public @Nullable String toString() {
+ return null;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+class LamiFileDescriptor extends LamiInteger {
+ public LamiFileDescriptor(long value) {
+ super(value);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Lami IRQ data type.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiIRQ extends LamiData {
+
+ /**
+ * IRQ type
+ */
+ public enum Type {
+ /** Hardware IRQ */
+ HARD,
+ /** Software IRQ */
+ SOFT
+ }
+
+ private final Type fType;
+ private final int fNumber;
+ private final @Nullable String fName;
+
+ /**
+ * Constructor
+ *
+ * @param irqType
+ * IRQ type
+ * @param nb
+ * IRQ number
+ * @param name
+ * IRQ name, null if not available
+ */
+ public LamiIRQ(Type irqType, int nb, @Nullable String name) {
+ fType = irqType;
+ fNumber = nb;
+ fName = name;
+ }
+
+ /**
+ * Get this IRQ's name. May be null if unavailable.
+ *
+ * @return The IRQ name
+ */
+ public @Nullable String getName() {
+ return fName;
+ }
+
+ /**
+ * Get this IRQ's type
+ *
+ * @return The IRQ type
+ */
+ public Type getType() {
+ return fType;
+ }
+
+ /**
+ * Get this IRQ's number.
+ *
+ * @return The IRQ number
+ */
+ public int getNumber() {
+ return fNumber;
+ }
+
+ @Override
+ public @Nullable String toString() {
+ StringBuilder sb = new StringBuilder();
+ switch (fType) {
+ case SOFT:
+ sb.append(Messages.LamiIRQ_SoftIRQ).append(' ');
+ break;
+ case HARD:
+ default:
+ sb.append(Messages.LamiIRQ_HardwareIRQ).append(' ');
+ break;
+ }
+
+ sb.append(String.valueOf(fNumber));
+
+ if (fName != null) {
+ sb.append(" (" + fName + ')'); //$NON-NLS-1$
+ }
+ return sb.toString();
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Integer data element
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiInteger extends LamiData {
+
+ private final long fValue;
+
+ /**
+ * Constructor
+ *
+ * @param value The integer value (as a long)
+ */
+ public LamiInteger(long value) {
+ fValue = value;
+ }
+
+ /**
+ * Return the value
+ *
+ * @return The value
+ */
+ public long getValue() {
+ return fValue;
+ }
+
+ @Override
+ public @Nullable String toString() {
+ return String.valueOf(fValue);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+class LamiNetworkInterface extends LamiString {
+ public LamiNetworkInterface(String value) {
+ super(value);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+class LamiNumber extends LamiData {
+
+ private final double fValue;
+
+ public LamiNumber(double value) {
+ fValue = value;
+ }
+
+ public double getValue() {
+ return fValue;
+ }
+
+ @Override
+ public @Nullable String toString() {
+ return Double.toString(fValue);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+class LamiPath extends LamiString {
+ public LamiPath(String value) {
+ super(value);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import java.util.StringJoiner;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Class defining a LAMI 'process' type.
+ *
+ * This is the representation of an operating system process.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiProcess extends LamiData {
+
+ private final @Nullable String fName;
+ private final @Nullable Long fPid;
+ private final @Nullable Long fTid;
+
+ private final String fString;
+
+ /**
+ * Constructor
+ *
+ * All parameters are optional, but realistically at least one should be
+ * provided!
+ *
+ * @param name
+ * The process name
+ * @param pid
+ * The process's PID
+ * @param tid
+ * The process's TID
+ */
+ public LamiProcess(@Nullable String name, @Nullable Long pid, @Nullable Long tid) {
+ fName = name;
+ fPid = pid;
+ fTid = tid;
+
+ fString = generateString();
+ }
+
+ /**
+ * Get this process's name, null if unavailable.
+ *
+ * @return The process name
+ */
+ public @Nullable String getName() {
+ return fName;
+ }
+
+ /**
+ * Get this process's PID, null if unavailable.
+ *
+ * @return The process PID
+ */
+ public @Nullable Long getPID() {
+ return fPid;
+ }
+
+ /**
+ * Get this process's TID, null if unavailable.
+ *
+ * @return The process TID
+ */
+ public @Nullable Long getTID() {
+ return fTid;
+ }
+
+ private String generateString() {
+ Long pid = fPid;
+ Long tid = fTid;
+
+ StringBuilder sb = new StringBuilder();
+ if (fName != null) {
+ sb.append(fName);
+ }
+
+ if (pid != null || tid != null) {
+ sb.append(' ');
+ StringJoiner sj = new StringJoiner(", ", "(", ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ if (pid != null) {
+ sj.add("pid=" + pid.toString()); //$NON-NLS-1$
+ }
+ if (tid != null) {
+ sj.add("tid=" + tid.toString()); //$NON-NLS-1$
+ }
+ sb.append(sj.toString());
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return fString;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+class LamiRatio extends LamiNumber {
+ public LamiRatio(double value) {
+ super(value);
+ }
+
+ @Override
+ public @Nullable String toString() {
+ return String.format("%.2f", getValue() * 100); //$NON-NLS-1$
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+class LamiSize extends LamiInteger {
+ public LamiSize(long value) {
+ super(value);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+class LamiString extends LamiData {
+
+ private final String fValue;
+
+ public LamiString(String value) {
+ fValue = value;
+ }
+
+ public String getValue() {
+ return fValue;
+ }
+
+ @Override
+ public @Nullable String toString() {
+ return fValue;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Data element for LAMI system call objects.
+ *
+ * @author Philippe Proulx
+ */
+public class LamiSystemCall extends LamiString {
+
+ private final String fString;
+
+ /**
+ * Constructor
+ *
+ * @param value
+ * The 'value' field of the system call, typically its name
+ */
+ public LamiSystemCall(String value) {
+ super(value);
+
+ if (value.endsWith("()")) { //$NON-NLS-1$
+ fString = value;
+ } else {
+ fString = value + "()"; //$NON-NLS-1$
+ }
+ }
+
+ @Override
+ public @Nullable String toString() {
+ return fString;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Lami time range data type
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiTimeRange extends LamiData {
+
+ private final long fStart;
+ private final long fEnd;
+ private final long fDuration;
+
+ /**
+ * Construct a new time range
+ *
+ * @param start
+ * Start time
+ * @param end
+ * End time
+ */
+ public LamiTimeRange(long start, long end) {
+ fStart = start;
+ fEnd = end;
+ fDuration = fEnd - fStart;
+ }
+
+ /**
+ * Get the start time of this time range.
+ *
+ * @return The start time
+ */
+ public long getStart() {
+ return fStart;
+ }
+
+ /**
+ * Get the end time of this time range.
+ *
+ * @return The end time
+ */
+ public long getEnd() {
+ return fEnd;
+ }
+ /**
+ * Get the duration of this time range.
+ *
+ * @return The duration
+ */
+ public long getDuration() {
+ return fDuration;
+ }
+
+ @Override
+ public @Nullable String toString() {
+ return "[" + String.valueOf(fStart) + " - " + String.valueOf(fEnd) + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+/**
+ * Lami timestamp data type
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiTimestamp extends LamiInteger {
+
+ /**
+ * Construct a time stamp from a value in ns.
+ *
+ * @param value
+ * The value
+ */
+ public LamiTimestamp(long value) {
+ super(value);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Lami 'unknown' value.
+ *
+ * The special unknown object represents an unknown value. It is typically used
+ * in result table cells where a given computation cannot produce a result for
+ * some reason.
+ *
+ * @author Alexandre Montplaisir
+ */
+class LamiUnknown extends LamiData {
+
+ public static final LamiUnknown INSTANCE = new LamiUnknown();
+
+ private LamiUnknown() {}
+
+ @Override
+ public @Nullable String toString() {
+ return null;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Lami 'version' data type
+ *
+ * @author Alexandre Montplaisir
+ */
+public class LamiVersion {
+
+ private final int fMajor;
+ private final int fMinor;
+ private final int fPatchLevel;
+ private final @Nullable String fExtra;
+
+ /**
+ * Construct a new version number. Normally to be show as:
+ *
+ * major.minor.patchlevel.extra
+ *
+ * @param major
+ * Major version number
+ * @param minor
+ * Minor version number
+ * @param patchLevel
+ * Patch version number
+ * @param extra
+ * Extra version number
+ */
+ public LamiVersion(int major, int minor, int patchLevel, @Nullable String extra) {
+ fMajor = major;
+ fMinor = minor;
+ fPatchLevel = patchLevel;
+ fExtra = extra;
+ }
+
+ /**
+ * @return The major version number
+ */
+ public int getMajor() {
+ return fMajor;
+ }
+
+ /**
+ * @return The minor version number
+ */
+ public int getMinor() {
+ return fMinor;
+ }
+
+ /**
+ * @return The patchlevel version number
+ */
+ public int getPatchLevel() {
+ return fPatchLevel;
+ }
+
+ /**
+ * @return The extra version number. May be a string, and may be null.
+ */
+ public @Nullable String getExtra() {
+ return fExtra;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String LamiBoolean_Yes;
+ public static String LamiBoolean_No;
+
+ public static String LamiData_Value;
+ public static String LamiData_UnitBytes;
+ public static String LamiData_UnitBitsPerSecond;
+
+ public static String LamiIRQ_SoftIRQ;
+ public static String LamiIRQ_HardwareIRQ;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2016 EfficiOS Inc. 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
+###############################################################################
+
+LamiBoolean_Yes = Yes
+LamiBoolean_No = No
+
+LamiData_Value = Value
+LamiData_UnitBytes = bytes
+LamiData_UnitBitsPerSecond = bps
+
+LamiIRQ_SoftIRQ = SoftIRQ
+LamiIRQ_HardwareIRQ = IRQ
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types;
###############################################################################
-# Copyright (c) 2015 EfficiOS Inc. and others
+# Copyright (c) 2015, 2016 EfficiOS Inc. and others
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
bin.includes = META-INF/,\
.,\
about.html,\
- plugin.properties
+ plugin.properties,\
+ plugin.xml
src.includes = about.html
additional.bundles = org.eclipse.jdt.annotation
jars.extra.classpath = platform:/plugin/org.eclipse.jdt.annotation
###############################################################################
-# Copyright (c) 2015 EfficiOS Inc. and others
+# Copyright (c) 2015, 2016 EfficiOS Inc. and others
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<!--
+ Copyright (c) 2016 EfficiOS Inc. 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
+ -->
+
+<plugin>
+ <extension
+ point="org.eclipse.ui.handlers">
+ <handler
+ class="org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.handler.RunAnalysisHandler"
+ commandId="org.eclipse.tracecompass.tmf.ui.command.analysis_run">
+ <activeWhen>
+ <and>
+ <count
+ value="1">
+ </count>
+ <iterate
+ operator="and">
+ <instanceof
+ value="org.eclipse.tracecompass.tmf.ui.project.model.TmfOnDemandAnalysisElement">
+ </instanceof>
+ </iterate>
+ </and>
+ </activeWhen>
+ </handler>
+ <handler
+ class="org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.handler.OpenReportHandler"
+ commandId="org.eclipse.tracecompass.tmf.ui.command.report_open">
+ <activeWhen>
+ <and>
+ <iterate
+ ifEmpty="false"
+ operator="and">
+ <instanceof
+ value="org.eclipse.tracecompass.tmf.ui.project.model.TmfReportElement">
+ </instanceof>
+ </iterate>
+ </and>
+ </activeWhen>
+ </handler>
+ <handler
+ class="org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.handler.DeleteReportHandler"
+ commandId="org.eclipse.tracecompass.tmf.ui.command.report_delete">
+ <activeWhen>
+ <and>
+ <iterate
+ ifEmpty="false"
+ operator="and">
+ <instanceof
+ value="org.eclipse.tracecompass.tmf.ui.project.model.TmfReportElement">
+ </instanceof>
+ </iterate>
+ </and>
+ </activeWhen>
+ </handler>
+ </extension>
+ <extension
+ point="org.eclipse.ui.commands">
+ <command
+ categoryId="org.eclipse.linuxtools.tmf.ui.commands.category"
+ description="%command.analysis_run.description"
+ id="org.eclipse.tracecompass.tmf.ui.command.analysis_run"
+ name="%command.analysis_run">
+ </command>
+ <command
+ categoryId="org.eclipse.linuxtools.tmf.ui.commands.category"
+ description="%command.report_open.description"
+ id="org.eclipse.tracecompass.tmf.ui.command.report_open"
+ name="%command.report_open">
+ </command>
+ <command
+ categoryId="org.eclipse.linuxtools.tmf.ui.commands.category"
+ description="%command.report_delete.description"
+ id="org.eclipse.tracecompass.tmf.ui.command.report_delete"
+ name="%command.report_delete">
+ </command>
+ </extension>
+ <extension
+ point="org.eclipse.ui.menus">
+ <menuContribution
+ locationURI="popup:org.eclipse.ui.popup.any?after=additions">
+ <command
+ commandId="org.eclipse.tracecompass.tmf.ui.command.analysis_run"
+ label="%command.analysis_run"
+ mnemonic="%command.analysis_run.mnemonic"
+ style="push"
+ tooltip="%command.analysis_run.description">
+ <visibleWhen
+ checkEnabled="false">
+ <with
+ variable="selection">
+ <count
+ value="1">
+ </count>
+ <iterate>
+ <instanceof
+ value="org.eclipse.tracecompass.tmf.ui.project.model.TmfOnDemandAnalysisElement">
+ </instanceof>
+ </iterate>
+ </with>
+ </visibleWhen>
+ </command>
+ </menuContribution>
+ <menuContribution
+ allPopups="false"
+ locationURI="popup:org.eclipse.ui.popup.any?after=additions">
+ <command
+ commandId="org.eclipse.tracecompass.tmf.ui.command.report_open"
+ label="%command.report_open"
+ mnemonic="%command.report_open.mnemonic"
+ style="push"
+ tooltip="%command.report_open.description">
+ <visibleWhen
+ checkEnabled="false">
+ <with
+ variable="selection">
+ <iterate
+ ifEmpty="false">
+ <instanceof
+ value="org.eclipse.tracecompass.tmf.ui.project.model.TmfReportElement">
+ </instanceof>
+ </iterate>
+ </with>
+ </visibleWhen>
+ </command>
+ <command
+ commandId="org.eclipse.tracecompass.tmf.ui.command.report_delete"
+ label="%command.report_delete"
+ mnemonic="%command.report_delete.mnemonic"
+ style="push"
+ tooltip="%command.report_delete.description">
+ <visibleWhen
+ checkEnabled="false">
+ <with
+ variable="selection">
+ <iterate
+ ifEmpty="false">
+ <instanceof
+ value="org.eclipse.tracecompass.tmf.ui.project.model.TmfReportElement">
+ </instanceof>
+ </iterate>
+ </with>
+ </visibleWhen>
+ </command>
+ </menuContribution>
+ </extension>
+ <extension
+ point="org.eclipse.ui.views">
+ <view
+ allowMultiple="true"
+ category="org.eclipse.linuxtools.tmf.ui.views.category"
+ class="org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views.LamiReportView"
+ id="org.eclipse.tracecompass.analysis.lami.views.reportview"
+ name="%analysis.report.view"
+ restorable="false">
+ </view>
+ </extension>
+
+</plugin>
/*******************************************************************************
- * Copyright (c) 2015 EfficiOS Inc., Alexandre Montplaisir
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.handler;
+
+import java.util.List;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.tracecompass.tmf.core.analysis.ondemand.IOnDemandAnalysisReport;
+import org.eclipse.tracecompass.tmf.ui.project.model.TmfReportElement;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The command handler for the "Delete Report" menu option for Report project
+ * model elements.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class DeleteReportHandler extends AbstractHandler {
+
+ @Override
+ public @Nullable Object execute(@Nullable ExecutionEvent event) throws ExecutionException {
+ /* Types should have been checked by the plugin.xml already */
+ ISelection selection = HandlerUtil.getCurrentSelectionChecked(event);
+ List<?> elements = ((IStructuredSelection) selection).toList();
+
+ /* Ask the parent element to remove each corresponding report. */
+ elements.stream()
+ .filter(elem -> elem instanceof TmfReportElement)
+ .map(elem -> (TmfReportElement) elem)
+ .forEach(reportElem -> {
+ IOnDemandAnalysisReport report = reportElem.getReport();
+ reportElem.getParent().removeReport(report);
+ });
+
+ return null;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String LamiAnalysis_MainTaskName;
+
+ public static String ParameterDialog_BaseCommand;
+ public static String ParameterDialog_ExternalParameters;
+ public static String ParameterDialog_ExternalParametersDescription;
+ public static String ParameterDialog_StringValidatorMessage;
+ public static String ParameterDialog_ReportNameSuffix;
+ public static String ParameterDialog_Error;
+ public static String ParameterDialog_ErrorMessage;
+
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.handler;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysisReport;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views.LamiReportViewFactory;
+import org.eclipse.tracecompass.tmf.core.analysis.ondemand.IOnDemandAnalysisReport;
+import org.eclipse.tracecompass.tmf.ui.project.model.TmfReportElement;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The command handler for the "Open Report" menu option for Report project
+ * model elements.
+ *
+ * Double-clicking should also call this handler.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class OpenReportHandler extends AbstractHandler {
+
+ @Override
+ public @Nullable Object execute(@Nullable ExecutionEvent event) throws ExecutionException {
+
+ /* Types should have been checked by the plugin.xml already */
+ ISelection selection = HandlerUtil.getCurrentSelectionChecked(event);
+ List<?> elements = ((IStructuredSelection) selection).toList();
+ List<TmfReportElement> reportElements = elements.stream()
+ .filter(elem -> elem instanceof TmfReportElement)
+ .map(elem -> (TmfReportElement) elem)
+ .collect(Collectors.toList());
+
+ for (TmfReportElement reportElem : reportElements) {
+ IOnDemandAnalysisReport report = reportElem.getReport();
+ if (!(report instanceof LamiAnalysisReport)) {
+ /* This handler deals with LAMI reports only */
+ continue;
+ }
+ LamiAnalysisReport lamiReport = (LamiAnalysisReport) report;
+
+ Display.getDefault().syncExec(() -> {
+ try {
+ LamiReportViewFactory.createNewViews(lamiReport);
+ } catch (PartInitException e) {
+ }
+ });
+ }
+
+ return null;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2000, 2016 IBM Corporation 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IInputValidator;
+import org.eclipse.jface.resource.StringConverter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Adaptation of {@link InputDialog}, to show the command in a grayed out,
+ * read-only input field, and a editable input field for the user to add extra
+ * parameters.
+ *
+ * @author Alexandre Montplaisir
+ */
+@NonNullByDefault({})
+class ParameterDialog extends Dialog {
+
+ private static final Color GRAY_COLOR = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY);
+
+ private String fTitle;
+ private String fMessage;
+ private String fValue = "";//$NON-NLS-1$
+ private IInputValidator fValidator;
+ private Button fOkButton;
+ private Text fText;
+ private Text fErrorMessageText;
+ private String fErrorMessage;
+
+ private Text fBaseCommandText;
+ private final String fBaseCommand;
+
+ public ParameterDialog(Shell parentShell,
+ String dialogTitle,
+ String dialogMessage,
+ String baseCommand,
+ IInputValidator validator) {
+ super(parentShell);
+ fTitle = dialogTitle;
+ fMessage = dialogMessage;
+ fValue = "";//$NON-NLS-1$
+ fValidator = validator;
+ fBaseCommand = baseCommand;
+ }
+
+ @Override
+ protected void buttonPressed(int buttonId) {
+ if (buttonId == IDialogConstants.OK_ID) {
+ fValue = fText.getText();
+ } else {
+ fValue = null;
+ }
+ super.buttonPressed(buttonId);
+ }
+
+ @Override
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ if (fTitle != null) {
+ shell.setText(fTitle);
+ }
+ }
+
+ @Override
+ protected void createButtonsForButtonBar(Composite parent) {
+ fOkButton = createButton(parent, IDialogConstants.OK_ID,
+ IDialogConstants.OK_LABEL, true);
+ createButton(parent, IDialogConstants.CANCEL_ID,
+ IDialogConstants.CANCEL_LABEL, false);
+ fText.setFocus();
+ if (fValue != null) {
+ fText.setText(fValue);
+ fText.selectAll();
+ }
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ // create composite
+ Composite composite = (Composite) super.createDialogArea(parent);
+
+ Label label = new Label(composite, SWT.WRAP);
+ label.setText(Messages.ParameterDialog_BaseCommand + ':');
+
+ fBaseCommandText = new Text(composite, getInputTextStyle() | SWT.READ_ONLY);
+ fBaseCommandText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
+ | GridData.HORIZONTAL_ALIGN_FILL));
+ fBaseCommandText.setText(fBaseCommand);
+ fBaseCommandText.setForeground(GRAY_COLOR);
+
+ // create message
+ if (fMessage != null) {
+ label = new Label(composite, SWT.WRAP);
+ label.setText(fMessage);
+ GridData data = new GridData(GridData.GRAB_HORIZONTAL
+ | GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL
+ | GridData.VERTICAL_ALIGN_CENTER);
+ data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH);
+ label.setLayoutData(data);
+ label.setFont(parent.getFont());
+ }
+
+
+
+ fText = new Text(composite, getInputTextStyle());
+ fText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
+ | GridData.HORIZONTAL_ALIGN_FILL));
+ fText.addModifyListener(e -> validateInput());
+ fErrorMessageText = new Text(composite, SWT.READ_ONLY | SWT.WRAP);
+ fErrorMessageText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
+ | GridData.HORIZONTAL_ALIGN_FILL));
+ fErrorMessageText.setBackground(fErrorMessageText.getDisplay()
+ .getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
+ setErrorMessage(fErrorMessage);
+
+ applyDialogFont(composite);
+ return composite;
+ }
+
+ /**
+ * Returns the ok button.
+ *
+ * @return the ok button
+ */
+ protected Button getOkButton() {
+ return fOkButton;
+ }
+
+ /**
+ * Returns the string typed into this input dialog.
+ *
+ * @return the input string
+ */
+ public String getValue() {
+ return fValue;
+ }
+
+ /**
+ * Validates the input.
+ * <p>
+ * The default implementation of this framework method delegates the request
+ * to the supplied input validator object; if it finds the input invalid,
+ * the error message is displayed in the dialog's message line. This hook
+ * method is called whenever the text changes in the input field.
+ * </p>
+ */
+ protected void validateInput() {
+ String errMsg = null;
+ if (fValidator != null) {
+ errMsg = fValidator.isValid(fText.getText());
+ }
+ setErrorMessage(errMsg);
+ }
+
+ /**
+ * Sets or clears the error message.
+ * If not <code>null</code>, the OK button is disabled.
+ *
+ * @param errorMessage
+ * the error message, or <code>null</code> to clear
+ */
+ private void setErrorMessage(String errorMessage) {
+ this.fErrorMessage = errorMessage;
+ if (fErrorMessageText != null && !fErrorMessageText.isDisposed()) {
+ fErrorMessageText.setText(errorMessage == null ? " \n " : errorMessage); //$NON-NLS-1$
+ boolean hasError = errorMessage != null && (StringConverter.removeWhiteSpaces(errorMessage)).length() > 0;
+ fErrorMessageText.setEnabled(hasError);
+ fErrorMessageText.setVisible(hasError);
+ fErrorMessageText.getParent().update();
+ Control button = getButton(IDialogConstants.OK_ID);
+ if (button != null) {
+ button.setEnabled(errorMessage == null);
+ }
+ }
+ }
+
+ /**
+ * Returns the style bits that should be used for the input text field.
+ * Defaults to a single line entry. Subclasses may override.
+ *
+ * @return the integer style bits that should be used when creating the
+ * input text
+ */
+ protected int getInputTextStyle() {
+ return SWT.SINGLE | SWT.BORDER;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.handler;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
+
+import java.util.List;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+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.Job;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.dialogs.IInputValidator;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysis;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysisReport;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views.LamiReportViewFactory;
+import org.eclipse.tracecompass.tmf.core.analysis.ondemand.IOnDemandAnalysis;
+import org.eclipse.tracecompass.tmf.core.analysis.ondemand.IOnDemandAnalysisReport;
+import org.eclipse.tracecompass.tmf.core.analysis.ondemand.OnDemandAnalysisException;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
+import org.eclipse.tracecompass.tmf.ui.project.model.TmfCommonProjectElement;
+import org.eclipse.tracecompass.tmf.ui.project.model.TmfOnDemandAnalysisElement;
+import org.eclipse.tracecompass.tmf.ui.project.model.TmfReportsElement;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The command handler for the "Run External Analysis" menu option.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class RunAnalysisHandler extends AbstractHandler {
+
+ @Override
+ public boolean isEnabled() {
+ // Check if we are closing down
+ final IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (window == null) {
+ return false;
+ }
+
+ // Get the selection
+ final IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ final IWorkbenchPart part = page.getActivePart();
+ if (part == null) {
+ return false;
+ }
+ final ISelectionProvider selectionProvider = part.getSite().getSelectionProvider();
+ if (selectionProvider == null) {
+ return false;
+ }
+ final ISelection selection = selectionProvider.getSelection();
+
+ /*
+ * plugin.xml should have done type verifications already
+ */
+ final Object element = ((IStructuredSelection) selection).getFirstElement();
+ TmfOnDemandAnalysisElement elem = (TmfOnDemandAnalysisElement) element;
+ if (elem.getAnalysis() instanceof LamiAnalysis && elem.canRun()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public @Nullable Object execute(@Nullable ExecutionEvent event) throws ExecutionException {
+
+ /* Types should have been checked by the plugin.xml already */
+ ISelection selection = HandlerUtil.getCurrentSelectionChecked(event);
+ Object element = ((IStructuredSelection) selection).getFirstElement();
+ final TmfOnDemandAnalysisElement analysisElem = (TmfOnDemandAnalysisElement) element;
+
+ TmfCommonProjectElement traceElem = analysisElem.getParent().getParent();
+ ITmfTrace trace = traceElem.getTrace();
+ if (trace == null) {
+ /* That trace is not currently opened */
+ return null;
+ }
+
+ /* Retrieve and initialize the analysis module, aka read the script's metadata */
+ IOnDemandAnalysis ondemandAnalysis = analysisElem.getAnalysis();
+ if (!(ondemandAnalysis instanceof LamiAnalysis)) {
+ return null;
+ }
+ LamiAnalysis analysis = (LamiAnalysis) ondemandAnalysis;
+
+ /* Retrieve the current time range, will be used as parameters to the analysis */
+ TmfTraceManager tm = TmfTraceManager.getInstance();
+ TmfTimeRange timeRange = tm.getCurrentTraceContext().getSelectionRange();
+ if (timeRange.getStartTime().equals(timeRange.getEndTime())) {
+ timeRange = null;
+ }
+ /* Job below needs a final reference... */
+ final TmfTimeRange tr = timeRange;
+
+ /* Pop the dialog to ask for extra parameters */
+ String baseCommand = analysis.getFullCommandAsString(trace, tr);
+
+ Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ ParameterDialog dialog = new ParameterDialog(shell, Messages.ParameterDialog_ExternalParameters,
+ Messages.ParameterDialog_ExternalParametersDescription,
+ baseCommand,
+ PARAM_STRING_VALIDATOR);
+ if (dialog.open() != Window.OK) {
+ /* User clicked Cancel, don't run */
+ return null;
+ }
+ String extraParams = nullToEmptyString(dialog.getValue());
+
+ /* Execute the analysis and produce the reports */
+ Job job = new Job(Messages.LamiAnalysis_MainTaskName) {
+ @Override
+ protected @Nullable IStatus run(@Nullable IProgressMonitor monitor) {
+ IProgressMonitor mon = (monitor == null ? new NullProgressMonitor() : monitor);
+ try {
+ List<LamiResultTable> results = analysis.execute(trace, tr, extraParams, mon);
+
+ String reportName = analysis.getName() +' ' + Messages.ParameterDialog_ReportNameSuffix;
+ LamiAnalysisReport report = new LamiAnalysisReport(reportName, results);
+ registerNewReport(analysisElem, report);
+
+ /* Automatically open the report for convenience */
+ Display.getDefault().syncExec(() -> {
+ try {
+ LamiReportViewFactory.createNewViews(report);
+ } catch (PartInitException e) {
+ }
+ });
+ return Status.OK_STATUS;
+
+ } catch (OnDemandAnalysisException e) {
+ String errMsg = e.getMessage();
+
+ if (errMsg != null) {
+ /* The analysis execution yielded an error */
+ Display.getDefault().asyncExec(() -> {
+ MessageDialog.openError(shell,
+ /* Dialog title */
+ Messages.ParameterDialog_Error,
+ /* Dialog message */
+ Messages.ParameterDialog_ErrorMessage + ":\n\n" + //$NON-NLS-1$
+ errMsg);
+ });
+ }
+
+ return Status.CANCEL_STATUS;
+ }
+ }
+ };
+ job.schedule();
+
+ return null;
+ }
+
+ private static final IInputValidator PARAM_STRING_VALIDATOR = text -> {
+ if (text.isEmpty() || text.matches("[a-zA-Z0-9\\,\\-\\s]+")) { //$NON-NLS-1$
+ return null;
+ }
+ return Messages.ParameterDialog_StringValidatorMessage;
+ };
+
+ /**
+ * Register a new report
+ *
+ * @param analysisElem
+ * The analysis's project element
+ * @param report
+ * The report to add
+ */
+ public void registerNewReport(TmfOnDemandAnalysisElement analysisElem, IOnDemandAnalysisReport report) {
+ /* For now the TmfProjectReportsElement manages the reports. */
+ TmfReportsElement reportsElement = analysisElem
+ .getParent()
+ .getParent()
+ .getChildElementReports();
+
+ reportsElement.addReport(report);
+ }
+
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2015, 2016 EfficiOS Inc. 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
+###############################################################################
+
+LamiAnalysis_MainTaskName = Invoking external analysis script
+
+ParameterDialog_BaseCommand = Base command
+ParameterDialog_ExternalParameters = External Analysis Parameters
+ParameterDialog_ExternalParametersDescription = Extra parameters to pass to the external analysis. Leave empty for none.
+ParameterDialog_StringValidatorMessage = Allowed characters are letters, numbers, ',' and '-'
+ParameterDialog_ReportNameSuffix = Report
+ParameterDialog_Error = Error running external script
+ParameterDialog_ErrorMessage = The script terminated abnormally
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.handler;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Jonathan Rajotte-Julien
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignal;
+
+/**
+ * Enable signal sending on selection inside a LamiViewer implementation.
+ *
+ * @author Jonathan Rajotte-Julien
+ */
+public class LamiSelectionUpdateSignal extends TmfSignal {
+
+ private final Set<Integer> fEntryIndexes;
+ /*
+ * TODO: replace this with an object to equals. A signalHash can only
+ * guaranty that objects are different, not that they are the same. Using
+ * this is looking for trouble.
+ */
+ private final int fSignalHash;
+
+ /**
+ * Constructor for a new signal.
+ *
+ * @param source
+ * The object sending this signal
+ * @param entryIndexList
+ * The list of selected indices
+ * @param signalHash
+ * The hash for exclusivity signaling
+ */
+ public LamiSelectionUpdateSignal(Object source, Set<Integer> entryIndexList, int signalHash) {
+ super(source);
+ fEntryIndexes = new HashSet<>(entryIndexList);
+ fSignalHash = signalHash;
+ }
+
+
+ @Override
+ public String toString() {
+ return "[" + this.getClass().getSimpleName() + " (" + fEntryIndexes + ")]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ /**
+ * Getter for the entryIndex
+ *
+ * @return
+ * The new selected entry
+ */
+ public Set<Integer> getEntryIndex() {
+ return fEntryIndexes;
+ }
+
+
+ /**
+ * Getter for the exclusivity hash
+ *
+ * @return
+ * The exclusivity hash
+ */
+ public int getSignalHash() {
+ return fSignalHash;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
+
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
+
+/**
+ * Common interface for all Lami viewers.
+ *
+ * @author Alexandre Montplaisir
+ */
+public interface ILamiViewer {
+
+ /**
+ * Dispose the viewer widget.
+ */
+ void dispose();
+
+ /**
+ * Factory method to create a new Table viewer.
+ *
+ * @param parent
+ * The parent composite
+ * @param resultTable
+ * The result table to display
+ * @return The new viewer
+ */
+ static ILamiViewer createLamiTable(Composite parent, LamiResultTable resultTable) {
+ TableViewer tableViewer = new TableViewer(parent, SWT.FULL_SELECTION | SWT.MULTI | SWT.VIRTUAL);
+ return new LamiTableViewer(tableViewer, resultTable);
+ }
+
+ /**
+ * Factory method to create a new chart viewer. The chart type is specified
+ * by the 'chartModel' parameter.
+ *
+ * @param parent
+ * The parent composite
+ * @param resultTable
+ * The result table to use as a data source
+ * @param chartModel
+ * The information about the chart to display
+ * @return The new viewer
+ */
+ static ILamiViewer createLamiChart(Composite parent, LamiResultTable resultTable, LamiChartModel chartModel) {
+ switch (chartModel.getChartType()) {
+ case BAR_CHART:
+ return new LamiBarChartViewer(parent, resultTable, chartModel);
+ case XY_SCATTER:
+ return new LamiScatterViewer(parent, resultTable, chartModel);
+ case PIE_CHART:
+ default:
+ throw new UnsupportedOperationException("Unsupported chart type: " + chartModel.toString()); //$NON-NLS-1$
+ }
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel.ChartType;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
+import org.swtchart.IAxis;
+import org.swtchart.IAxisTick;
+import org.swtchart.IBarSeries;
+import org.swtchart.ISeries;
+import org.swtchart.ISeries.SeriesType;
+import org.swtchart.Range;
+
+import com.google.common.collect.Iterators;
+
+/**
+ * Bar chart Viewer for LAMI views.
+ *
+ * @author Alexandre Montplaisir
+ * @author Jonathan Rajotte-Julien
+ * @author Mathieu Desnoyers
+ */
+public class LamiBarChartViewer extends LamiXYChartViewer {
+
+ private static final double LOGSCALE_EPSILON_FACTOR = 100.0;
+
+ private class Mapping {
+ final private @Nullable Integer fInternalValue;
+ final private @Nullable Integer fModelValue;
+
+ public Mapping(@Nullable Integer internalValue, @Nullable Integer modelValue) {
+ fInternalValue = internalValue;
+ fModelValue = modelValue;
+ }
+
+ public @Nullable Integer getInternalValue() {
+ return fInternalValue;
+ }
+
+ public @Nullable Integer getModelValue() {
+ return fModelValue;
+ }
+ }
+
+ private final String[] fCategories;
+ private final Map<ISeries, List<Mapping>> fIndexPerSeriesMapping;
+ private final Map<LamiTableEntry, Mapping> fEntryToCategoriesMap;
+
+ /**
+ * Creates a bar chart Viewer instance based on SWTChart.
+ *
+ * @param parent
+ * The parent composite to draw in.
+ * @param resultTable
+ * The result table containing the data from which to build the
+ * chart
+ * @param chartModel
+ * The information about the chart to build
+ */
+ public LamiBarChartViewer(Composite parent, LamiResultTable resultTable, LamiChartModel chartModel) {
+ super(parent, resultTable, chartModel);
+
+ List<LamiTableEntryAspect> xAxisAspects = getXAxisAspects();
+ List<LamiTableEntryAspect> yAxisAspects = getYAxisAspects();
+
+ /* bar chart cannot deal with multiple X series */
+ if (getChartModel().getChartType() != ChartType.BAR_CHART && xAxisAspects.size() != 1) {
+ throw new IllegalArgumentException("Invalid configuration passed to a bar chart."); //$NON-NLS-1$
+ }
+
+ /* Enable categories */
+ getChart().getAxisSet().getXAxis(0).enableCategory(true);
+
+ LamiTableEntryAspect xAxisAspect = xAxisAspects.get(0);
+ List<LamiTableEntry> entries = getResultTable().getEntries();
+ boolean logscale = chartModel.yAxisIsLog();
+ fIndexPerSeriesMapping = new HashMap<>();
+ fEntryToCategoriesMap = new HashMap<>();
+
+ /* Categories index mapping */
+ List<@Nullable String> xCategories = new ArrayList<>();
+ for (int i = 0; i < entries.size(); i++) {
+ String string = xAxisAspect.resolveString(entries.get(i));
+ if (string == null) {
+ fEntryToCategoriesMap.put(entries.get(i), new Mapping(null, i));
+ continue;
+ }
+ fEntryToCategoriesMap.put(entries.get(i), new Mapping(xCategories.size(), i));
+ xCategories.add(string);
+
+ }
+ fCategories = xCategories.toArray(new String[0]);
+
+ /*
+ * Log scale magic course 101:
+ *
+ * It uses the relative difference divided by a factor
+ * (100) to get as close as it can to the actual minimum but still a
+ * little bit smaller. This is used as a workaround of SWTCHART
+ * limitations regarding custom scale drawing in log scale mode, bogus
+ * representation of NaN double values and limited support of multiple
+ * size series.
+ *
+ * This should be good enough for most users.
+ */
+ double min = Double.MAX_VALUE;
+ double max = Double.MIN_VALUE;
+ double logScaleEpsilon = ZERO;
+ if (logscale) {
+
+ /* Find minimum and maximum values */
+ for (LamiTableEntryAspect aspect : yAxisAspects) {
+ for (LamiTableEntry entry : entries) {
+ Double value = aspect.resolveDouble(entry);
+ if (value == null || value <= 0) {
+ continue;
+ }
+ min = Math.min(min, value);
+ max = Math.max(max, value);
+ }
+ }
+
+ double delta = max - min;
+ logScaleEpsilon = min - ((min * delta) / (LOGSCALE_EPSILON_FACTOR * max));
+ }
+
+ for (LamiTableEntryAspect yAxisAspect : yAxisAspects) {
+ if (!yAxisAspect.isContinuous() || yAxisAspect.isTimeStamp()) {
+ /* Only plot continuous aspects */
+ continue;
+ }
+
+ List<Double> validXValues = new ArrayList<>();
+ List<Double> validYValues = new ArrayList<>();
+ List<Mapping> indexMapping = new ArrayList<>();
+
+ for (int i = 0; i < entries.size(); i++) {
+ Integer categoryIndex = checkNotNull(fEntryToCategoriesMap.get(checkNotNull(entries.get(i)))).fInternalValue;
+ Double yValue = yAxisAspect.resolveDouble(entries.get(i));
+ if (categoryIndex == null) {
+ /* Invalid value do not show */
+ continue;
+ }
+
+ if (yValue == null) {
+ /*
+ * Null value for y is the same as zero since this is a bar
+ * chart
+ */
+ yValue = Double.valueOf(ZERO);
+ }
+
+ if (logscale && yValue <= ZERO) {
+ /*
+ * Less or equal to 0 values can't be plotted on a log
+ * scale. We map them to the mean of the >=0 minimal value
+ * and the calculated log scale magic epsilon.
+ */
+ yValue = (min + logScaleEpsilon) / 2.0;
+ }
+
+ validXValues.add(checkNotNull(categoryIndex).doubleValue());
+ validYValues.add(yValue.doubleValue());
+ indexMapping.add(new Mapping(categoryIndex, checkNotNull(fEntryToCategoriesMap.get(checkNotNull(entries.get(i)))).fModelValue));
+ }
+
+ String name = yAxisAspect.getLabel();
+
+ if (validXValues.isEmpty() || validYValues.isEmpty()) {
+ /* No need to plot an empty series */
+ continue;
+ }
+
+ IBarSeries barSeries = (IBarSeries) getChart().getSeriesSet().createSeries(SeriesType.BAR, name);
+ barSeries.setXSeries(validXValues.stream().mapToDouble(Double::doubleValue).toArray());
+ barSeries.setYSeries(validYValues.stream().mapToDouble(Double::doubleValue).toArray());
+ fIndexPerSeriesMapping.put(barSeries, indexMapping);
+ }
+
+ setBarSeriesColors();
+
+ /* Set all y axis logscale mode */
+ Stream.of(getChart().getAxisSet().getYAxes()).forEach(axis -> axis.enableLogScale(logscale));
+
+ /* Set the formatter on the Y axis */
+ IAxisTick yTick = getChart().getAxisSet().getYAxis(0).getTick();
+ yTick.setFormat(getContinuousAxisFormatter(yAxisAspects, entries));
+ yTick.setTickLabelAngle(1);
+
+ /* Adjust the chart range */
+ getChart().getAxisSet().adjustRange();
+ if (logscale) {
+ getChart().getAxisSet().getYAxis(0).setRange(new Range(logScaleEpsilon, max));
+ }
+
+ /* Once the chart is filled, refresh the axis labels */
+ refreshDisplayLabels();
+
+ /* Add mouse listener */
+ getChart().getPlotArea().addMouseListener(new LamiBarChartMouseDownListener());
+
+ /* Custom Painter listener to highlight the current selection */
+ getChart().getPlotArea().addPaintListener(new LamiBarChartPainterListener());
+ }
+
+ private final class LamiBarChartMouseDownListener extends MouseAdapter {
+
+ @Override
+ public void mouseDown(@Nullable MouseEvent event) {
+ if (event == null || event.button != 1) {
+ return;
+ }
+
+ boolean ctrlMode = false;
+ int xMouseLocation = event.x;
+ int yMouseLocation = event.y;
+
+ Set<Integer> selections;
+ if ((event.stateMask & SWT.CTRL) != 0) {
+ ctrlMode = true;
+ selections = getSelection();
+ } else {
+ /* Reset selection state */
+ unsetSelection();
+ selections = new HashSet<>();
+ }
+
+ ISeries[] series = getChart().getSeriesSet().getSeries();
+
+ /*
+ * Iterate over all series, get the rectangle bounds for each
+ * category, and find the category index under the mouse.
+ *
+ * Since categories map directly to the index of the fResultTable
+ * and that this table is immutable the index of the entry
+ * corresponds to the categories index. Signal to all LamiViewer and
+ * LamiView the update of selection.
+ */
+ for (ISeries oneSeries : series) {
+ IBarSeries barSerie = ((IBarSeries) oneSeries);
+ Rectangle[] recs = barSerie.getBounds();
+
+ for (int j = 0; j < recs.length; j++) {
+ Rectangle rectangle = recs[j];
+ if (rectangle.contains(xMouseLocation, yMouseLocation)) {
+ int index = getTableEntryIndexFromGraphIndex(checkNotNull(oneSeries), j);
+ if (!ctrlMode || (index >= 0 && !selections.remove(index))) {
+ selections.add(index);
+ }
+ }
+ }
+ }
+
+ /* Save the current selection internally */
+ setSelection(selections);
+ /* Signal all Lami viewers & views of the selection */
+ LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(this,
+ selections, getResultTable().hashCode());
+ TmfSignalManager.dispatchSignal(signal);
+ redraw();
+ }
+ }
+
+ @Override
+ protected void redraw() {
+ setBarSeriesColors();
+ super.redraw();
+ }
+
+ /**
+ * Set the chart series colors according to the selection state. Use light
+ * colors when a selection is present.
+ */
+ private void setBarSeriesColors() {
+ Iterator<Color> colorsIt;
+
+ if (isSelected()) {
+ colorsIt = Iterators.cycle(LIGHT_COLORS);
+ } else {
+ colorsIt = Iterators.cycle(COLORS);
+ }
+
+ for (ISeries series : getChart().getSeriesSet().getSeries()) {
+ ((IBarSeries) series).setBarColor(colorsIt.next());
+ }
+ }
+
+ private final class LamiBarChartPainterListener implements PaintListener {
+ @Override
+ public void paintControl(@Nullable PaintEvent e) {
+ if (e == null || !isSelected()) {
+ return;
+ }
+
+ Iterator<Color> colorsIt = Iterators.cycle(COLORS);
+ GC gc = e.gc;
+
+ for (ISeries series : getChart().getSeriesSet().getSeries()) {
+ Color color = colorsIt.next();
+ for (int index : getSelection()) {
+ int graphIndex = getGraphIndexFromTableEntryIndex(series, index);
+ if (graphIndex < 0) {
+ /* Invalid index */
+ continue;
+ }
+
+ Rectangle[] bounds = ((IBarSeries) series).getBounds();
+ if (bounds.length != fCategories.length) {
+ /*
+ * The plot is too cramped and SWTChart currently does
+ * its best on rectangle drawing and returns the
+ * rectangle that it is able to draw.
+ *
+ * For now we simply do not draw since it is really hard
+ * to see anyway. A better way to visualize the value
+ * would be a full cross for each selection based on
+ * their coordinates.
+ */
+ continue;
+ }
+ Rectangle rectangle = bounds[graphIndex];
+ gc.setBackground(color);
+ gc.fillRectangle(rectangle);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void refreshDisplayLabels() {
+ /* Only if we have at least 1 category */
+ if (fCategories.length == 0) {
+ return;
+ }
+
+ /* Only refresh if labels are visible */
+ IAxis xAxis = getChart().getAxisSet().getXAxis(0);
+ if (!xAxis.getTick().isVisible() || !xAxis.isCategoryEnabled()) {
+ return;
+ }
+
+ /*
+ * Shorten all the labels to 5 characters plus "…" when the longest
+ * label length is more than 50% of the chart height.
+ */
+
+ Rectangle rect = getChart().getClientArea();
+ int lengthLimit = (int) (rect.height * 0.40);
+
+ GC gc = new GC(fParent);
+ gc.setFont(xAxis.getTick().getFont());
+
+ /* Find the longest category string */
+ String longestString = Arrays.stream(fCategories).max(Comparator.comparingInt(String::length)).orElse(fCategories[0]);
+
+ /* Get the length and height of the longest label in pixels */
+ Point pixels = gc.stringExtent(longestString);
+
+ // Completely arbitrary
+ int cutLen = 5;
+
+ String[] displayCategories = new String[fCategories.length];
+ if (pixels.x > lengthLimit) {
+ /* We have to cut down some strings */
+ for (int i = 0; i < fCategories.length; i++) {
+ if (fCategories[i].length() > cutLen) {
+ displayCategories[i] = fCategories[i].substring(0, cutLen) + ELLIPSIS;
+ } else {
+ displayCategories[i] = fCategories[i];
+ }
+ }
+ } else {
+ /* All strings should fit */
+ displayCategories = Arrays.copyOf(fCategories, fCategories.length);
+ }
+ xAxis.setCategorySeries(displayCategories);
+
+ /* Cleanup */
+ gc.dispose();
+ }
+
+ private int getTableEntryIndexFromGraphIndex(ISeries series, int index) {
+ List<Mapping> indexes = fIndexPerSeriesMapping.get(series);
+ if (indexes == null || index > indexes.size() || index < 0) {
+ return -1;
+ }
+
+ Mapping mapping = indexes.get(index);
+ Integer modelValue = mapping.getModelValue();
+ if (modelValue != null) {
+ return modelValue.intValue();
+ }
+ return -1;
+ }
+
+ private int getGraphIndexFromTableEntryIndex(ISeries series, int index) {
+ List<Mapping> indexes = fIndexPerSeriesMapping.get(series);
+ if (indexes == null || index < 0) {
+ return -1;
+ }
+
+ int internalIndex = -1;
+ for (Mapping mapping : indexes) {
+ if (mapping.getModelValue() == index) {
+ Integer internalValue = mapping.getInternalValue();
+ if (internalValue != null) {
+ internalIndex = internalValue.intValue();
+ break;
+ }
+ }
+ }
+ return internalIndex;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Jonathan Rajotte-Julien
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel.ChartType;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiLabelFormat;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
+import org.swtchart.IAxisTick;
+import org.swtchart.ILineSeries;
+import org.swtchart.ISeries;
+import org.swtchart.ISeries.SeriesType;
+import org.swtchart.LineStyle;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Iterators;
+
+/**
+ * XY Scatter chart viewer for Lami views
+ *
+ * @author Jonathan Rajotte-Julien
+ */
+public class LamiScatterViewer extends LamiXYChartViewer {
+
+ private static final int SELECTION_SNAP_RANGE_MULTIPLIER = 20;
+ private static final int SELECTION_CROSS_SIZE_MULTIPLIER = 3;
+
+ private final Map<ISeries, List<Integer>> fIndexMapping;
+
+ /* The current data point for the hovering cross */
+ private Point fHoveringCrossDataPoint;
+
+ /**
+ * Constructor
+ *
+ * @param parent
+ * parent
+ * @param resultTable
+ * Result table populating this chart
+ * @param graphModel
+ * Model of this chart
+ */
+ public LamiScatterViewer(Composite parent, LamiResultTable resultTable, LamiChartModel graphModel) {
+ super(parent, resultTable, graphModel);
+ if (getChartModel().getChartType() != ChartType.XY_SCATTER) {
+ throw new IllegalStateException("Chart type not a Scatter Chart " + getChartModel().getChartType().toString()); //$NON-NLS-1$
+ }
+
+ /* Inspect X series */
+ fIndexMapping = new HashMap<>();
+
+ fHoveringCrossDataPoint = new Point(-1, -1);
+
+ List<LamiTableEntryAspect> xAxisAspects = getXAxisAspects();
+ if (xAxisAspects.stream().distinct().count() == 1) {
+ LamiTableEntryAspect singleXAspect = xAxisAspects.get(0);
+ xAxisAspects.clear();
+ xAxisAspects.add(singleXAspect);
+ }
+
+ BiMap<@Nullable String, Integer> xMap = checkNotNull(HashBiMap.create());
+ boolean xIsLog = graphModel.xAxisIsLog();
+
+ boolean areXAspectsContinuous = areAspectsContinuous(xAxisAspects);
+ boolean areXAspectsTimeStamp = areAspectsTimeStamp(xAxisAspects);
+
+ /* Check all aspect are the same type */
+ for (LamiTableEntryAspect aspect : xAxisAspects) {
+ if (aspect.isContinuous() != areXAspectsContinuous) {
+ throw new IllegalStateException("Some X aspects are continuous and some are not"); //$NON-NLS-1$
+ }
+ if (aspect.isTimeStamp() != areXAspectsTimeStamp) {
+ throw new IllegalStateException("Some X aspects are time based and some are not"); //$NON-NLS-1$
+ }
+ }
+
+ /*
+ * When xAxisAspects are discrete create a map for all values of all
+ * series
+ */
+ if (!areXAspectsContinuous) {
+ generateLabelMap(xAxisAspects, checkNotNull(xMap));
+ }
+
+ /*
+ * Create Y series
+ */
+ List<LamiTableEntryAspect> yAxisAspects = getYAxisAspects();
+ BiMap<@Nullable String, Integer> yMap = checkNotNull(HashBiMap.create());
+ boolean yIsLog = graphModel.yAxisIsLog();
+
+ boolean areYAspectsContinuous = areAspectsContinuous(yAxisAspects);
+ boolean areYAspectsTimeStamp = areAspectsTimeStamp(yAxisAspects);
+
+ /* Check all aspect are the same type */
+ for (LamiTableEntryAspect aspect : yAxisAspects) {
+ if (aspect.isContinuous() != areYAspectsContinuous) {
+ throw new IllegalStateException("Some Y aspects are continuous and some are not"); //$NON-NLS-1$
+ }
+ if (aspect.isTimeStamp() != areYAspectsTimeStamp) {
+ throw new IllegalStateException("Some Y aspects are time based and some are not"); //$NON-NLS-1$
+ }
+ }
+
+ /*
+ * When yAspects are discrete create a map for all values of all series
+ */
+ if (!areYAspectsContinuous) {
+ generateLabelMap(yAxisAspects, yMap);
+ }
+
+ /* Plot the series */
+ int index = 0;
+ for (LamiTableEntryAspect yAspect : getYAxisAspects()) {
+ String name = ""; //$NON-NLS-1$
+ LamiTableEntryAspect xAspect;
+ if (xAxisAspects.size() == 1) {
+ /* Always map to the same x series */
+ xAspect = xAxisAspects.get(0);
+ name = yAspect.getLabel();
+ } else {
+ xAspect = xAxisAspects.get(index);
+ name = (yAspect.getName() + ' ' + Messages.LamiScatterViewer_by + ' ' + xAspect.getName());
+ }
+
+ List<@Nullable Double> xDoubleSeries = new ArrayList<>();
+ List<@Nullable Double> yDoubleSeries = new ArrayList<>();
+
+ if (xAspect.isContinuous()) {
+ xDoubleSeries = getResultTable().getEntries().stream().map((entry -> xAspect.resolveDouble(entry))).collect(Collectors.toList());
+ } else {
+ xDoubleSeries = getResultTable().getEntries().stream().map(entry -> {
+ String string = xAspect.resolveString(entry);
+ Integer value = xMap.get(string);
+ if (value != null) {
+ return Double.valueOf(value.doubleValue());
+ }
+ return null;
+
+ }).collect(Collectors.toList());
+ }
+
+ if (yAspect.isContinuous()) {
+ yDoubleSeries = getResultTable().getEntries().stream().map((entry -> yAspect.resolveDouble(entry))).collect(Collectors.toList());
+ } else {
+ yDoubleSeries = getResultTable().getEntries().stream().map(entry -> {
+ String string = yAspect.resolveString(entry);
+ Integer value = yMap.get(string);
+ if (value != null) {
+ return Double.valueOf(value.doubleValue());
+ }
+ return null;
+
+ }).collect(Collectors.toList());
+ }
+
+ List<@Nullable Double> validXDoubleSeries = new ArrayList<>();
+ List<@Nullable Double> validYDoubleSeries = new ArrayList<>();
+ List<Integer> indexSeriesCorrespondance = new ArrayList<>();
+
+ if (xDoubleSeries.size() != yDoubleSeries.size()) {
+ throw new IllegalStateException("Series sizes don't match!"); //$NON-NLS-1$
+ }
+
+ /* Check for invalid tuple value. Any null elements are invalid */
+ for (int i = 0; i < xDoubleSeries.size(); i++) {
+ Double xValue = xDoubleSeries.get(i);
+ Double yValue = yDoubleSeries.get(i);
+ if (xValue == null || yValue == null) {
+ /* Reject this tuple */
+ continue;
+ }
+ if ((xIsLog && xValue <= ZERO) || (yIsLog && yValue <= ZERO)) {
+ /*
+ * Equal or less than 0 values can't be plotted on log scale
+ */
+ continue;
+ }
+ validXDoubleSeries.add(xValue);
+ validYDoubleSeries.add(yValue);
+ indexSeriesCorrespondance.add(i);
+ }
+
+ if (validXDoubleSeries.isEmpty() || validXDoubleSeries.isEmpty()) {
+ /* No need to plot an empty series */
+ index++;
+ continue;
+ }
+
+ ILineSeries scatterSeries = (ILineSeries) getChart().getSeriesSet().createSeries(SeriesType.LINE, name);
+ scatterSeries.setLineStyle(LineStyle.NONE);
+
+ double[] xserie = validXDoubleSeries.stream().mapToDouble(elem -> checkNotNull(elem).doubleValue()).toArray();
+ double[] yserie = validYDoubleSeries.stream().mapToDouble(elem -> checkNotNull(elem).doubleValue()).toArray();
+ scatterSeries.setXSeries(xserie);
+ scatterSeries.setYSeries(yserie);
+ fIndexMapping.put(scatterSeries, indexSeriesCorrespondance);
+ index++;
+ }
+
+ /* Modify x axis related chart styling */
+ IAxisTick xTick = getChart().getAxisSet().getXAxis(0).getTick();
+ if (areXAspectsContinuous) {
+ xTick.setFormat(getContinuousAxisFormatter(xAxisAspects, getResultTable().getEntries()));
+ } else {
+ xTick.setFormat(new LamiLabelFormat(checkNotNull(xMap)));
+ updateTickMark(checkNotNull(xMap), xTick, getChart().getPlotArea().getSize().x);
+
+ /* Remove vertical grid line */
+ getChart().getAxisSet().getXAxis(0).getGrid().setStyle(LineStyle.NONE);
+ }
+
+ /* Modify Y axis related chart styling */
+ IAxisTick yTick = getChart().getAxisSet().getYAxis(0).getTick();
+ if (areYAspectsContinuous) {
+ yTick.setFormat(getContinuousAxisFormatter(yAxisAspects, getResultTable().getEntries()));
+ } else {
+ yTick.setFormat(new LamiLabelFormat(checkNotNull(yMap)));
+ updateTickMark(checkNotNull(yMap), yTick, getChart().getPlotArea().getSize().y);
+
+ /*
+ * SWTChart workaround: SWTChart fiddles with tick mark visibility
+ * based on the fact that it can parse the label to double or not.
+ *
+ * If the label happens to be a double, it checks for the presence
+ * of that value in its own tick labels to decide if it should add
+ * it or not. If it happens that the parsed value is already present
+ * in its map, the tick gets a visibility of false.
+ *
+ * The X axis does not have this problem since SWTCHART checks on
+ * label angle, and if it is != 0 simply does no logic regarding
+ * visibility. So simply set a label angle of 1 to the axis.
+ */
+ yTick.setTickLabelAngle(1);
+
+ /* Remove horizontal grid line */
+ getChart().getAxisSet().getYAxis(0).getGrid().setStyle(LineStyle.NONE);
+ }
+
+ setLineSeriesColor();
+
+ /* Put log scale if necessary */
+ if (xIsLog && areXAspectsContinuous && !areXAspectsTimeStamp) {
+ Stream.of(getChart().getAxisSet().getXAxes()).forEach(axis -> axis.enableLogScale(xIsLog));
+ }
+
+ if (yIsLog && areYAspectsContinuous && !areYAspectsTimeStamp) {
+ /* Set the axis as logscale */
+ Stream.of(getChart().getAxisSet().getYAxes()).forEach(axis -> axis.enableLogScale(yIsLog));
+ }
+ getChart().getAxisSet().adjustRange();
+
+ /*
+ * Selection listener
+ */
+ getChart().getPlotArea().addMouseListener(new LamiScatterMouseDownListener());
+
+ /*
+ * Hovering cross listener
+ */
+ getChart().getPlotArea().addMouseMoveListener(new HoveringCrossListener());
+
+ /*
+ * Mouse exit listener: reset state of hovering cross on mouse exit.
+ */
+ getChart().getPlotArea().addListener(SWT.MouseExit, new Listener() {
+
+ @Override
+ public void handleEvent(@Nullable Event event) {
+ if (event != null) {
+ fHoveringCrossDataPoint.x = -1;
+ fHoveringCrossDataPoint.y = -1;
+ redraw();
+ }
+ }
+ });
+
+ /*
+ * Selections and hovering cross painting
+ */
+ getChart().getPlotArea().addPaintListener(new LamiScatterPainterListener());
+
+ /* On resize check for axis tick updating */
+ getChart().addListener(SWT.Resize, new Listener() {
+ @Override
+ public void handleEvent(@Nullable Event event) {
+ if (yTick.getFormat() instanceof LamiLabelFormat) {
+ updateTickMark(checkNotNull(yMap), yTick, getChart().getPlotArea().getSize().y);
+ }
+ if (xTick.getFormat() instanceof LamiLabelFormat) {
+ updateTickMark(checkNotNull(xMap), xTick, getChart().getPlotArea().getSize().x);
+ }
+ }
+ });
+ }
+
+ private void generateLabelMap(List<LamiTableEntryAspect> aspects, BiMap<@Nullable String, Integer> map) {
+ TreeSet<@Nullable String> set = new TreeSet<>();
+ for (LamiTableEntryAspect aspect : aspects) {
+ for (LamiTableEntry entry : getResultTable().getEntries()) {
+ String string = aspect.resolveString(entry);
+ if (string != null) {
+ set.add(string);
+ }
+ }
+ }
+ /* Ordered label mapping to double */
+ for (String string : set) {
+ map.put(string, map.size());
+ }
+ }
+
+ /**
+ * Set the chart series colors.
+ */
+ private void setLineSeriesColor() {
+ Iterator<Color> colorsIt;
+
+ colorsIt = Iterators.cycle(COLORS);
+
+ for (ISeries series : getChart().getSeriesSet().getSeries()) {
+ ((ILineSeries) series).setSymbolColor((colorsIt.next()));
+ /*
+ * Generate initial array of Color to enable per point color change
+ * on selection in the future
+ */
+ ArrayList<Color> colors = new ArrayList<>();
+ for (int i = 0; i < series.getXSeries().length; i++) {
+ Color color = ((ILineSeries) series).getSymbolColor();
+ colors.add(checkNotNull(color));
+ }
+ ((ILineSeries) series).setSymbolColors(colors.toArray(new Color[colors.size()]));
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Listeners
+ // ------------------------------------------------------------------------
+
+ private final class HoveringCrossListener implements MouseMoveListener {
+
+ @Override
+ public void mouseMove(@Nullable MouseEvent e) {
+ if (e == null) {
+ return;
+ }
+ ISeries[] series = getChart().getSeriesSet().getSeries();
+ @Nullable Point closest = null;
+ double closestDistance = -1.0;
+
+ for (ISeries oneSeries : series) {
+ ILineSeries lineSerie = (ILineSeries) oneSeries;
+ for (int i = 0; i < lineSerie.getXSeries().length; i++) {
+ Point dataPoint = lineSerie.getPixelCoordinates(i);
+
+ /*
+ * Find the distance between the data point and the mouse
+ * location and compare it to the symbol size * the range
+ * multiplier, so when a user hovers the mouse near the dot
+ * the cursor cross snaps to it.
+ */
+ int snapRangeRadius = lineSerie.getSymbolSize() * SELECTION_SNAP_RANGE_MULTIPLIER;
+
+ /*
+ * FIXME if and only if performance of this code is an issue
+ * for large sets, this can be accelerated by getting the
+ * distance squared, and if it is smaller than
+ * snapRangeRadius squared, then check hypot.
+ */
+ double distance = Math.hypot(dataPoint.x - e.x, dataPoint.y - e.y);
+ if (distance < snapRangeRadius) {
+ if (closestDistance == -1 || distance < closestDistance) {
+ closest = dataPoint;
+ closestDistance = distance;
+ }
+ }
+ }
+ }
+ if (closest != null) {
+ fHoveringCrossDataPoint.x = closest.x;
+ fHoveringCrossDataPoint.y = closest.y;
+ } else {
+ fHoveringCrossDataPoint.x = -1;
+ fHoveringCrossDataPoint.y = -1;
+ }
+ refresh();
+ }
+ }
+
+ private final class LamiScatterMouseDownListener extends MouseAdapter {
+
+ @Override
+ public void mouseDown(@Nullable MouseEvent event) {
+ if (event == null || event.button != 1) {
+ return;
+ }
+
+ int xMouseLocation = event.x;
+ int yMouseLocation = event.y;
+
+ boolean ctrlMode = false;
+
+ ISeries[] series = getChart().getSeriesSet().getSeries();
+ Set<Integer> selections = getSelection();
+
+ /* Check for ctrl on click */
+ if ((event.stateMask & SWT.CTRL) != 0) {
+ selections = getSelection();
+ ctrlMode = true;
+ } else {
+ /* Reset selection */
+ unsetSelection();
+ selections = new HashSet<>();
+ }
+
+ for (ISeries oneSeries : series) {
+ ILineSeries lineSerie = (ILineSeries) oneSeries;
+
+ int closest = -1;
+ double closestDistance = -1;
+ for (int i = 0; i < lineSerie.getXSeries().length; i++) {
+ Point dataPoint = lineSerie.getPixelCoordinates(i);
+
+ /*
+ * Find the distance between the data point and the mouse
+ * location, and compare it to the symbol size so when a
+ * user clicks on a symbol it selects it.
+ */
+ double distance = Math.hypot(dataPoint.x - xMouseLocation, dataPoint.y - yMouseLocation);
+ int snapRangeRadius = lineSerie.getSymbolSize() * SELECTION_SNAP_RANGE_MULTIPLIER;
+ if (distance < snapRangeRadius) {
+ if (closestDistance == -1 || distance < closestDistance) {
+ closest = i;
+ closestDistance = distance;
+ }
+ }
+ }
+ if (closest != -1) {
+ /* Translate to global index */
+ int tableEntryIndex = getTableEntryIndexFromGraphIndex(checkNotNull(oneSeries), closest);
+ if (tableEntryIndex < 0) {
+ continue;
+ }
+ LamiTableEntry entry = getResultTable().getEntries().get(tableEntryIndex);
+ int index = getResultTable().getEntries().indexOf(entry);
+
+ if (!ctrlMode || !selections.remove(index)) {
+ selections.add(index);
+ }
+ /* Do no iterate since we already found a match */
+ break;
+ }
+ }
+ setSelection(selections);
+ /* Signal all Lami viewers & views of the selection */
+ LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(this,
+ selections, checkNotNull(getResultTable().hashCode()));
+ TmfSignalManager.dispatchSignal(signal);
+ refresh();
+ }
+ }
+
+ private final class LamiScatterPainterListener implements PaintListener {
+
+ @Override
+ public void paintControl(@Nullable PaintEvent e) {
+ if (e == null) {
+ return;
+ }
+ GC gc = e.gc;
+
+ /* Draw the selection */
+ drawSelectedDot(checkNotNull(gc));
+
+ /* Draw the hovering cross */
+ drawHoveringCross(checkNotNull(gc));
+ }
+
+ private void drawSelectedDot(GC gc) {
+ if (isSelected()) {
+ Iterator<Color> colorsIt;
+ colorsIt = Iterators.cycle(COLORS);
+ for (ISeries series : getChart().getSeriesSet().getSeries()) {
+
+ /* Get series colors */
+ Color color = colorsIt.next();
+ int symbolSize = ((ILineSeries) series).getSymbolSize();
+
+ for (int index : getInternalSelections()) {
+ int graphIndex = getGraphIndexFromTableEntryIndex(series, index);
+
+ if (graphIndex < 0) {
+ continue;
+ }
+ Point point = series.getPixelCoordinates(graphIndex);
+
+ /* Create a colored dot for selection */
+ gc.setBackground(color);
+ gc.fillOval(point.x - symbolSize, point.y - symbolSize, symbolSize * 2, symbolSize * 2);
+
+ /* Draw cross */
+ gc.setLineWidth(2);
+ gc.setLineStyle(SWT.LINE_SOLID);
+ /* Vertical line */
+ int drawingDelta = SELECTION_CROSS_SIZE_MULTIPLIER * symbolSize;
+ gc.drawLine(point.x, point.y - drawingDelta, point.x, point.y + drawingDelta);
+ /* Horizontal line */
+ gc.drawLine(point.x - drawingDelta, point.y, point.x + drawingDelta, point.y);
+
+ }
+ }
+ }
+ }
+
+ private void drawHoveringCross(GC gc) {
+ gc.setLineWidth(1);
+ gc.setLineStyle(SWT.LINE_SOLID);
+ gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
+ gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
+ /* Vertical line */
+ gc.drawLine(fHoveringCrossDataPoint.x, 0, fHoveringCrossDataPoint.x, getChart().getPlotArea().getSize().y);
+ /* Horizontal line */
+ gc.drawLine(0, fHoveringCrossDataPoint.y, getChart().getPlotArea().getSize().x, fHoveringCrossDataPoint.y);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility functions
+ // ------------------------------------------------------------------------
+
+ private int getTableEntryIndexFromGraphIndex(ISeries series, int index) {
+ List<Integer> indexes = fIndexMapping.get(series);
+ if (indexes == null || index > indexes.size() || index < 0) {
+ return -1;
+ }
+ return indexes.get(index);
+ }
+
+ private int getGraphIndexFromTableEntryIndex(ISeries series, int index) {
+ List<Integer> indexes = fIndexMapping.get(series);
+ if (indexes == null || !indexes.contains(index)) {
+ return -1;
+ }
+ return indexes.indexOf(index);
+ }
+
+ @Override
+ protected void refreshDisplayLabels() {
+ }
+
+ /**
+ * Return the current selection in internal mapping
+ *
+ * @return the internal selections
+ */
+ protected Set<Integer> getInternalSelections() {
+ /* Translate to internal table location */
+ Set<Integer> indexes = super.getSelection();
+ Set<Integer> internalIndexes = indexes.stream()
+ .mapToInt(index -> getResultTable().getEntries().indexOf((getResultTable().getEntries().get(index))))
+ .boxed()
+ .collect(Collectors.toSet());
+ return internalIndexes;
+ }
+
+ private static void updateTickMark(BiMap<@Nullable String, Integer> map, IAxisTick tick, int availableLenghtPixel) {
+ int nbLabels = Math.max(1, map.size());
+ int stepSizePixel = availableLenghtPixel / nbLabels;
+ /*
+ * This step is a limitation on swtchart side regarding minimal grid
+ * step hint size. When the step size are smaller it get defined as the
+ * "default" value for the axis instead of the smallest one.
+ */
+ if (IAxisTick.MIN_GRID_STEP_HINT > stepSizePixel) {
+ stepSizePixel = (int) IAxisTick.MIN_GRID_STEP_HINT;
+ }
+ tick.setTickMarkStepHint(stepSizePixel);
+ }
+
+ @Override
+ protected void setSelection(@NonNull Set<@NonNull Integer> selection) {
+ super.setSelection(selection);
+
+ /* Set color of selected symbol */
+ Iterator<Color> colorsIt = Iterators.cycle(COLORS);
+ Iterator<Color> lightColorsIt = Iterators.cycle(LIGHT_COLORS);
+
+ Set<Integer> currentSelections = getInternalSelections();
+
+ for (ISeries series : getChart().getSeriesSet().getSeries()) {
+ /* Series color */
+ Color lightColor = lightColorsIt.next();
+ Color color = colorsIt.next();
+ Color[] colors = ((ILineSeries) series).getSymbolColors();
+
+ if (currentSelections.isEmpty()) {
+ /* Put all symbols to the normal colors */
+ Arrays.fill(colors, color);
+ } else {
+ /*
+ * Fill with light colors to represent the deselected state. The
+ * paint listener is then responsible for drawing the cross and
+ * the dark colors for the selection.
+ */
+ Arrays.fill(colors, lightColor);
+ }
+ ((ILineSeries) series).setSymbolColors(colors);
+ }
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Ericsson, EfficiOS Inc. 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.tmf.ui.viewers.table.ISortingLazyContentProvider;
+
+/**
+ * Content provider for the latency table viewers.
+ *
+ * @author France Lapointe Nguyen
+ * @author Alexandre Montplaisir
+ */
+class LamiTableContentProvider implements ISortingLazyContentProvider {
+
+ /**
+ * Table viewer of the latency table viewer
+ */
+ private @Nullable TableViewer fTableViewer = null;
+
+ private List<LamiTableEntry> fCurrentEntries;
+
+ private @Nullable Comparator<LamiTableEntry> fComparator = null;
+
+ /**
+ * Flag to avoid recursive calls to {@link #updateElement} due to table
+ * refreshes.
+ */
+ private volatile boolean fOngoingUpdate = false;
+
+ /**
+ * Constructor.
+ */
+ public LamiTableContentProvider() {
+ fCurrentEntries = checkNotNull(Collections.EMPTY_LIST);
+ }
+
+ @Override
+ public void updateElement(int index) {
+ final TableViewer tableViewer = fTableViewer;
+ final List<LamiTableEntry> entries = fCurrentEntries;
+ if ((tableViewer != null) && (entries.size() > index) && !fOngoingUpdate) {
+ fOngoingUpdate = true;
+ tableViewer.replace(entries.get(index), index);
+ fOngoingUpdate = false;
+ }
+ }
+
+ @Override
+ public void dispose() {
+ fCurrentEntries = checkNotNull(Collections.EMPTY_LIST);
+ fTableViewer = null;
+ fComparator = null;
+ }
+
+ @Override
+ public void inputChanged(@Nullable Viewer viewer, @Nullable Object oldInput, @Nullable Object newInput) {
+ fTableViewer = (TableViewer) viewer;
+ if (!(newInput instanceof List<?>)) {
+ /*
+ * Should be a List<BabeltraceTableEntry>, but may be null if it is
+ * not yet set.
+ */
+ return;
+ }
+ @SuppressWarnings("unchecked")
+ List<LamiTableEntry> entries = (List<LamiTableEntry>) newInput;
+
+ /* Do a copy here so that we can sort it to our heart's content */
+ fCurrentEntries = new ArrayList<>(entries);
+
+ if (fComparator != null) {
+ Collections.sort(fCurrentEntries, fComparator);
+ }
+ }
+
+ @Override
+ public void setSortOrder(@Nullable Comparator<?> comparator) {
+ if (comparator == null) {
+ return;
+ }
+ final TableViewer tableViewer = fTableViewer;
+ if (tableViewer == null) {
+ return;
+ }
+ @SuppressWarnings("unchecked")
+ Comparator<LamiTableEntry> entryComparator = (Comparator<LamiTableEntry>) comparator;
+ fComparator = entryComparator;
+ Collections.sort(fCurrentEntries, fComparator);
+ tableViewer.refresh();
+ }
+
+ /**
+ * Get the segment count
+ *
+ * @return the segment count
+ */
+ public int getNbEntries() {
+ return fCurrentEntries.size();
+ }
+
+ /**
+ * Get the index of a table entry.
+ *
+ * @param entry
+ * Entry to look for
+ * @return the index of the table entry
+ */
+ public int getIndexOf(LamiTableEntry entry) {
+ return fCurrentEntries.indexOf(entry);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Ericsson, EfficiOS Inc. 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
+
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views.LamiReportView;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
+import org.eclipse.tracecompass.tmf.ui.viewers.table.TmfSimpleTableViewer;
+
+/**
+ * Table viewer to use in {@link LamiReportView}s.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class LamiTableViewer extends TmfSimpleTableViewer implements ILamiViewer {
+
+ // ------------------------------------------------------------------------
+ // Attributes
+ // ------------------------------------------------------------------------
+
+ private final LamiResultTable fResultTable;
+ private Set<Integer> fSelections;
+
+ // ------------------------------------------------------------------------
+ // Inner class definitions
+ // ------------------------------------------------------------------------
+
+ /**
+ * Abstract class for the column label provider for the latency analysis
+ * table viewer
+ */
+ private static class LamiTableColumnLabelProvider extends ColumnLabelProvider {
+
+ private final LamiTableEntryAspect fColumnAspect;
+
+ public LamiTableColumnLabelProvider(LamiTableEntryAspect aspect) {
+ fColumnAspect = aspect;
+ }
+
+ @Override
+ public String getText(@Nullable Object input) {
+ if (!(input instanceof LamiTableEntry)) {
+ /* Doubles as a null check */
+ return ""; //$NON-NLS-1$
+ }
+ LamiTableEntry entry = (LamiTableEntry) input;
+ return nullToEmptyString(fColumnAspect.resolveString(entry));
+ }
+ }
+
+ /**
+ * Listener to update in other viewers when a cell of the latency
+ * table view is selected
+ */
+ private class LamiTableSelectionListener extends SelectionAdapter {
+ @Override
+ public void widgetSelected(@Nullable SelectionEvent e) {
+
+ IStructuredSelection selections = getTableViewer().getStructuredSelection();
+
+ Set<Integer> selectionIndexes = new HashSet<>();
+ for (Object selectedEntry : selections.toArray() ) {
+ selectionIndexes.add(fResultTable.getEntries().indexOf(selectedEntry));
+ }
+
+ fSelections = selectionIndexes;
+
+ /* Signal all Lami viewers & views of the selection */
+ LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(LamiTableViewer.this, selectionIndexes, checkNotNull(fResultTable).hashCode());
+ TmfSignalManager.dispatchSignal(signal);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructor
+ *
+ * @param tableViewer
+ * Table viewer of the view
+ * @param resultTable
+ * Data table populating this viewer
+ */
+ public LamiTableViewer(TableViewer tableViewer, LamiResultTable resultTable) {
+ super(tableViewer);
+ /*
+ * The table viewer should always be the first element in the control.
+ */
+ tableViewer.getTable().moveAbove(null);
+
+ fResultTable = resultTable;
+ fSelections = new HashSet<>();
+
+ /* Default sort order of the content provider is by its first column */
+ getTableViewer().setContentProvider(new LamiTableContentProvider());
+ getTableViewer().getTable().addSelectionListener(new LamiTableSelectionListener());
+
+ createColumns();
+ fillData();
+ }
+
+ // ------------------------------------------------------------------------
+ // Operations
+ // ------------------------------------------------------------------------
+
+ private void createColumns() {
+ final List<LamiTableEntryAspect> aspects = fResultTable.getTableClass().getAspects();
+
+ Display.getDefault().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ for (LamiTableEntryAspect aspect : aspects) {
+ createColumn(aspect.getLabel(), new LamiTableColumnLabelProvider(aspect), aspect.getComparator());
+ }
+ }
+ });
+ }
+
+ /**
+ * Update the data in the table viewer
+ *
+ * @param dataInput
+ * New data input
+ */
+ private void fillData() {
+ final TableViewer tableViewer = getTableViewer();
+ Display.getDefault().asyncExec(() -> {
+ if (tableViewer.getTable().isDisposed()) {
+ return;
+ }
+ // Go to the top of the table
+ tableViewer.getTable().setTopIndex(0);
+ // Reset selected row
+ tableViewer.setSelection(StructuredSelection.EMPTY);
+
+ /* Fill the table data */
+ tableViewer.setInput(fResultTable.getEntries());
+ LamiTableContentProvider latencyContentProvider = (LamiTableContentProvider) getTableViewer().getContentProvider();
+ tableViewer.setItemCount(latencyContentProvider.getNbEntries());
+
+ /* Set the column's alignment and pack them */
+ TableColumn[] cols = tableViewer.getTable().getColumns();
+ for (int i = 0; i < cols.length; i++) {
+ LamiTableEntryAspect colAspect = fResultTable.getTableClass().getAspects().get(i);
+ int alignment = (colAspect.isContinuous() ? SWT.RIGHT : SWT.LEFT);
+ cols[i].setAlignment(alignment);
+
+ }
+
+ /*
+ * On creation check if there is selections if so update the table
+ * selections here. Selections needs the ContentProvider for valid
+ * index lookup and since the content provider is set in an
+ * asynchronous task we cannot use the normal signal handler since
+ * we have no guarantee of time of execution of the fill data.
+ */
+ if (!fSelections.isEmpty()) {
+ int[] selectionsIndexes = fSelections.stream().map(index -> fResultTable.getEntries().get(index)).mapToInt(entry -> ((LamiTableContentProvider) getTableViewer().getContentProvider()).getIndexOf(entry)).toArray();
+ Display.getDefault().asyncExec(() -> {
+ getTableViewer().getTable().setSelection(selectionsIndexes);
+ getTableViewer().getTable().redraw();
+ });
+ }
+ });
+ Display.getDefault().asyncExec(() -> {
+ TableColumn[] cols = tableViewer.getTable().getColumns();
+ for (int i = 0; i < cols.length; i++) {
+ cols[i].pack();
+ }
+ });
+ }
+
+ /**
+ * The signal handler for selection update.
+ *
+ * @param signal
+ * The selection update signal
+ */
+ @TmfSignalHandler
+ public void updateSelection(LamiSelectionUpdateSignal signal) {
+
+ if (fResultTable.hashCode() != signal.getSignalHash() || equals(signal.getSource())) {
+ /* The signal is not for us */
+ return;
+ }
+ /* Fetch the position of the selected entry in the actual table since it could be sorted by another column */
+ LamiTableContentProvider latencyContentProvider = (LamiTableContentProvider) getTableViewer().getContentProvider();
+
+ Set<Integer> selections = signal.getEntryIndex();
+
+ int[] selectionsIndexes = selections.stream()
+ .map(index -> fResultTable.getEntries().get(index))
+ .mapToInt(entry -> latencyContentProvider.getIndexOf(entry))
+ .toArray();
+
+ fSelections = new HashSet<>(selections);
+
+ Display.getDefault().asyncExec(() -> {
+ getTableViewer().getTable().setSelection(selectionsIndexes);
+ getTableViewer().getTable().redraw();
+ });
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Michael Jeanson
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
+
+import java.text.Format;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.ToDoubleFunction;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.tracecompass.common.core.format.DecimalUnitFormat;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTimeStampFormat;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
+import org.eclipse.tracecompass.tmf.ui.viewers.TmfViewer;
+import org.swtchart.Chart;
+import org.swtchart.ITitle;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Abstract XYChart Viewer for LAMI views.
+ *
+ * @author Michael Jeanson
+ *
+ */
+public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer {
+
+ /** Ellipsis character */
+ protected static final String ELLIPSIS = "…"; //$NON-NLS-1$
+
+ /**
+ * String representing unknown values. Can be present even in numerical
+ * aspects!
+ */
+ protected static final String UNKNOWN = "?"; //$NON-NLS-1$
+
+ /** Zero value */
+ protected static final double ZERO = 0.0;
+
+ /** Symbol for seconds (used in the custom ns -> s conversion) */
+ private static final String SECONDS_SYMBOL = "s"; //$NON-NLS-1$
+
+ /** Symbol for nanoseconds (used in the custom ns -> s conversion) */
+ private static final String NANOSECONDS_SYMBOL = "ns"; //$NON-NLS-1$
+
+ /**
+ * Function to use to map Strings read from the data table to doubles for
+ * use in SWTChart series.
+ */
+ protected static final ToDoubleFunction<@Nullable String> DOUBLE_MAPPER = str -> {
+ if (str == null || str.equals(UNKNOWN)) {
+ return ZERO;
+ }
+ return Double.parseDouble(str);
+ };
+
+ /**
+ * List of standard colors
+ */
+ protected static final List<@NonNull Color> COLORS = ImmutableList.of(
+ new Color(Display.getDefault(), 72, 120, 207),
+ new Color(Display.getDefault(), 106, 204, 101),
+ new Color(Display.getDefault(), 214, 95, 95),
+ new Color(Display.getDefault(), 180, 124, 199),
+ new Color(Display.getDefault(), 196, 173, 102),
+ new Color(Display.getDefault(), 119, 190, 219)
+ );
+
+ /**
+ * List of "light" colors (when unselected)
+ */
+ protected static final List<@NonNull Color> LIGHT_COLORS = ImmutableList.of(
+ new Color(Display.getDefault(), 173, 195, 233),
+ new Color(Display.getDefault(), 199, 236, 197),
+ new Color(Display.getDefault(), 240, 196, 196),
+ new Color(Display.getDefault(), 231, 213, 237),
+ new Color(Display.getDefault(), 231, 222, 194),
+ new Color(Display.getDefault(), 220, 238, 246)
+ );
+
+ /**
+ * Time stamp formatter for intervals in the days range.
+ */
+ protected static final LamiTimeStampFormat DAYS_FORMATTER = new LamiTimeStampFormat("dd HH:mm"); //$NON-NLS-1$
+
+ /**
+ * Time stamp formatter for intervals in the hours range.
+ */
+ protected static final LamiTimeStampFormat HOURS_FORMATTER = new LamiTimeStampFormat("HH:mm"); //$NON-NLS-1$
+
+ /**
+ * Time stamp formatter for intervals in the minutes range.
+ */
+ protected static final LamiTimeStampFormat MINUTES_FORMATTER = new LamiTimeStampFormat("mm:ss"); //$NON-NLS-1$
+
+ /**
+ * Time stamp formatter for intervals in the seconds range.
+ */
+ protected static final LamiTimeStampFormat SECONDS_FORMATTER = new LamiTimeStampFormat("ss"); //$NON-NLS-1$
+
+ /**
+ * Time stamp formatter for intervals in the milliseconds range.
+ */
+ protected static final LamiTimeStampFormat MILLISECONDS_FORMATTER = new LamiTimeStampFormat("ss.SSS"); //$NON-NLS-1$
+
+ /**
+ * Decimal formatter to display nanoseconds as seconds.
+ */
+ protected static final DecimalUnitFormat NANO_TO_SECS_FORMATTER = new DecimalUnitFormat(0.000000001);
+
+ /**
+ * Default decimal formatter.
+ */
+ protected static final DecimalUnitFormat DECIMAL_FORMATTER = new DecimalUnitFormat();
+
+ private final Listener fResizeListener = event -> {
+ /* Refresh the titles to fit the current chart size */
+ refreshDisplayTitles();
+
+ /* Refresh the Axis labels to fit the current chart size */
+ refreshDisplayLabels();
+ };
+
+ private final LamiResultTable fResultTable;
+ private final LamiChartModel fChartModel;
+
+ private final Chart fChart;
+
+ private final String fChartTitle;
+ private final String fXTitle;
+ private final String fYTitle;
+
+ private boolean fSelected;
+ private Set<Integer> fSelection;
+
+ /**
+ * Creates a Viewer instance based on SWTChart.
+ *
+ * @param parent
+ * The parent composite to draw in.
+ * @param resultTable
+ * The result table containing the data from which to build the
+ * chart
+ * @param chartModel
+ * The information about the chart to build
+ */
+ public LamiXYChartViewer(Composite parent, LamiResultTable resultTable, LamiChartModel chartModel) {
+ super(parent);
+
+ fParent = parent;
+ fResultTable = resultTable;
+ fChartModel = chartModel;
+ fSelection = new HashSet<>();
+
+ fChart = new Chart(parent, SWT.NONE);
+ fChart.addListener(SWT.Resize, fResizeListener);
+
+ /* Set Chart title */
+ fChartTitle = fResultTable.getTableClass().getTableTitle();
+
+ /* Set X axis title */
+ if (fChartModel.getXSeriesColumns().size() == 1) {
+ /*
+ * There is only 1 series in the chart, we will use its name as the
+ * Y axis (and hide the legend).
+ */
+ String seriesName = getChartModel().getXSeriesColumns().get(0);
+ // The time duration formatter converts ns to s on the axis
+ if (NANOSECONDS_SYMBOL.equals(getXAxisAspects().get(0).getUnits())) {
+ seriesName = getXAxisAspects().get(0).getName() + " (" + SECONDS_SYMBOL + ')'; //$NON-NLS-1$
+ }
+ fXTitle = seriesName;
+ } else {
+ /*
+ * There are multiple series in the chart, if they all share the same
+ * units, display that.
+ */
+ long nbDiffAspects = getXAxisAspects().stream()
+ .map(aspect -> aspect.getUnits())
+ .distinct()
+ .count();
+
+ String units = getXAxisAspects().get(0).getUnits();
+ if (nbDiffAspects == 1 && units != null) {
+ /* All aspects use the same unit type */
+
+ // The time duration formatter converts ns to s on the axis
+ if (NANOSECONDS_SYMBOL.equals(units)) {
+ units = SECONDS_SYMBOL;
+ }
+ fXTitle = Messages.LamiViewer_DefaultValueName + " (" + units + ')'; //$NON-NLS-1$
+ } else {
+ /* Various unit types, just say "Value" */
+ fXTitle = nullToEmptyString(Messages.LamiViewer_DefaultValueName);
+ }
+ }
+
+ /* Set Y axis title */
+ if (fChartModel.getYSeriesColumns().size() == 1) {
+ /*
+ * There is only 1 series in the chart, we will use its name as the
+ * Y axis (and hide the legend).
+ */
+ String seriesName = getChartModel().getYSeriesColumns().get(0);
+ // The time duration formatter converts ns to s on the axis
+ if (NANOSECONDS_SYMBOL.equals(getYAxisAspects().get(0).getUnits())) {
+ seriesName = getYAxisAspects().get(0).getName() + " (" + SECONDS_SYMBOL + ')'; //$NON-NLS-1$
+ }
+ fYTitle = seriesName;
+ fChart.getLegend().setVisible(false);
+ } else {
+ /*
+ * There are multiple series in the chart, if they all share the same
+ * units, display that.
+ */
+ long nbDiffAspects = getYAxisAspects().stream()
+ .map(aspect -> aspect.getUnits())
+ .distinct()
+ .count();
+
+ String units = getYAxisAspects().get(0).getUnits();
+ if (nbDiffAspects == 1 && units != null) {
+ /* All aspects use the same unit type */
+
+ // The time duration formatter converts ns to s on the axis
+ if (NANOSECONDS_SYMBOL.equals(units)) {
+ units = SECONDS_SYMBOL;
+ }
+ fYTitle = Messages.LamiViewer_DefaultValueName + " (" + units + ')'; //$NON-NLS-1$
+ } else {
+ /* Various unit types, just say "Value" */
+ fYTitle = nullToEmptyString(Messages.LamiViewer_DefaultValueName);
+ }
+
+ /* Put legend at the bottom */
+ fChart.getLegend().setPosition(SWT.BOTTOM);
+ }
+
+ /* Set all titles and labels font color to black */
+ fChart.getTitle().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
+ fChart.getAxisSet().getXAxis(0).getTitle().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
+ fChart.getAxisSet().getYAxis(0).getTitle().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
+ fChart.getAxisSet().getXAxis(0).getTick().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
+ fChart.getAxisSet().getYAxis(0).getTick().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
+
+ /* Set X label 90 degrees */
+ fChart.getAxisSet().getXAxis(0).getTick().setTickLabelAngle(90);
+
+ /* Refresh the titles to fit the current chart size */
+ refreshDisplayTitles();
+
+ fChart.addDisposeListener(e -> {
+ /* Dispose resources of this class */
+ LamiXYChartViewer.super.dispose();
+ });
+ }
+
+ /**
+ * Util method to check if a list of aspects are all continuous.
+ *
+ * @param axisAspects
+ * The list of aspects to check.
+ * @return true is all aspects are continuous, otherwise false.
+ */
+ protected static boolean areAspectsContinuous(List<LamiTableEntryAspect> axisAspects) {
+ return axisAspects.stream().allMatch(aspect -> aspect.isContinuous());
+ }
+
+ /**
+ * Util method to check if a list of aspects are all time stamps.
+ *
+ * @param axisAspects
+ * The list of aspects to check.
+ * @return true is all aspects are time stamps, otherwise false.
+ */
+ protected static boolean areAspectsTimeStamp(List<LamiTableEntryAspect> axisAspects) {
+ return axisAspects.stream().allMatch(aspect -> aspect.isTimeStamp());
+ }
+
+ /**
+ * Util method to check if a list of aspects are all time durations.
+ *
+ * @param axisAspects
+ * The list of aspects to check.
+ * @return true is all aspects are time durations, otherwise false.
+ */
+ protected static boolean areAspectsTimeDuration(List<LamiTableEntryAspect> axisAspects) {
+ return axisAspects.stream().allMatch(aspect -> aspect.isTimeDuration());
+ }
+
+ /**
+ * Util method that will return a formatter based on the aspects linked to an axis
+ *
+ * If all aspects are time stamps, return a timestamp formatter tuned to the interval.
+ * If all aspects are time durations, return the nanoseconds to seconds formatter.
+ * Otherwise, return the generic decimal formatter.
+ *
+ * @param axisAspects
+ * The list of aspects of the axis.
+ * @param entries
+ * The list of entries of the chart.
+ * @return a formatter for the axis.
+ */
+ protected static Format getContinuousAxisFormatter(List<LamiTableEntryAspect> axisAspects, List<LamiTableEntry> entries) {
+
+ if (areAspectsTimeStamp(axisAspects)) {
+ /* Set a TimeStamp formatter depending on the duration between the first and last value */
+ double max = Double.MIN_VALUE;
+ double min = Double.MAX_VALUE;
+
+ for (LamiTableEntry entry : entries) {
+ for (LamiTableEntryAspect aspect : axisAspects) {
+ Double current = aspect.resolveDouble(entry);
+ if (current != null) {
+ max = Math.max(max, current);
+ min = Math.min(min, current);
+ }
+ }
+ }
+ long duration = (long) max - (long) min;
+
+ if (duration > TimeUnit.DAYS.toNanos(1)) {
+ return DAYS_FORMATTER;
+ } else if (duration > TimeUnit.HOURS.toNanos(1)) {
+ return HOURS_FORMATTER;
+ } else if (duration > TimeUnit.MINUTES.toNanos(1)) {
+ return MINUTES_FORMATTER;
+ } else if (duration > TimeUnit.SECONDS.toNanos(15)) {
+ return SECONDS_FORMATTER;
+ } else {
+ return MILLISECONDS_FORMATTER;
+ }
+ } else if (areAspectsTimeDuration(axisAspects)) {
+ /* Set the time duration formatter */
+ return NANO_TO_SECS_FORMATTER;
+
+ } else {
+ /* For other numeric aspects, use the default decimal unit formatter */
+ return DECIMAL_FORMATTER;
+ }
+ }
+
+ /**
+ * Get the chart result table.
+ *
+ * @return The chart result table.
+ */
+ protected LamiResultTable getResultTable() {
+ return fResultTable;
+ }
+
+ /**
+ * Get the chart model.
+ *
+ * @return The chart model.
+ */
+ protected LamiChartModel getChartModel() {
+ return fChartModel;
+ }
+
+ /**
+ * Get the chart object.
+ * @return The chart object.
+ */
+ protected Chart getChart() {
+ return fChart;
+ }
+
+ /**
+ * Is a selection made in the chart.
+ *
+ * @return true if there is a selection.
+ */
+ protected boolean isSelected() {
+ return fSelected;
+ }
+
+ /**
+ * Set the selection index.
+ *
+ * @param selection the index to select.
+ */
+ protected void setSelection(Set<Integer> selection) {
+ fSelection = selection;
+ fSelected = !selection.isEmpty();
+ }
+
+ /**
+ * Unset the chart selection.
+ */
+ protected void unsetSelection() {
+ fSelection.clear();
+ fSelected = false;
+ }
+
+ /**
+ * Get the current selection index.
+ *
+ * @return the current selection index.
+ */
+ protected Set<Integer> getSelection() {
+ return fSelection;
+ }
+
+ @Override
+ public @Nullable Control getControl() {
+ return fChart.getParent();
+ }
+
+ @Override
+ public void refresh() {
+ Display.getDefault().asyncExec(() -> {
+ if (!fChart.isDisposed()) {
+ fChart.redraw();
+ }
+ });
+ }
+
+ @Override
+ public void dispose() {
+ fChart.dispose();
+ /* The control's DisposeListener will call super.dispose() */
+ }
+
+ /**
+ * Get a list of all the aspect of the Y axis.
+ *
+ * @return The aspects for the Y axis
+ */
+ protected List<LamiTableEntryAspect> getYAxisAspects() {
+
+ List<LamiTableEntryAspect> yAxisAspects = new ArrayList<>();
+
+ for (String colName : getChartModel().getYSeriesColumns()) {
+ yAxisAspects.add(checkNotNull(getAspectFromName(getResultTable().getTableClass().getAspects(), colName)));
+ }
+
+ return yAxisAspects;
+ }
+
+ /**
+ * Get a list of all the aspect of the X axis.
+ *
+ * @return The aspects for the X axis
+ */
+ protected List<LamiTableEntryAspect> getXAxisAspects() {
+
+ List<LamiTableEntryAspect> xAxisAspects = new ArrayList<>();
+
+ for (String colName : getChartModel().getXSeriesColumns()) {
+ xAxisAspects.add(checkNotNull(getAspectFromName(getResultTable().getTableClass().getAspects(), colName)));
+ }
+
+ return xAxisAspects;
+ }
+
+ /**
+ * Set the ITitle object text to a substring of canonicalTitle that when
+ * rendered in the chart will fit maxPixelLength.
+ */
+ private void refreshDisplayTitle(ITitle title, String canonicalTitle, int maxPixelLength) {
+ if (title.isVisible()) {
+
+ String newTitle = canonicalTitle;
+
+ /* Get the title font */
+ Font font = title.getFont();
+
+ GC gc = new GC(fParent);
+ gc.setFont(font);
+
+ /* Get the length and height of the canonical title in pixels */
+ Point pixels = gc.stringExtent(canonicalTitle);
+
+ /*
+ * If the title is too long, generate a shortened version based on the
+ * average character width of the current font.
+ */
+ if (pixels.x > maxPixelLength) {
+ int charwidth = gc.getFontMetrics().getAverageCharWidth();
+
+ int minimum = 3;
+
+ int strLen = ((maxPixelLength / charwidth) - minimum);
+
+ if (strLen > minimum) {
+ newTitle = canonicalTitle.substring(0, strLen) + ELLIPSIS;
+ } else {
+ newTitle = ELLIPSIS;
+ }
+ }
+
+ title.setText(newTitle);
+
+ // Cleanup
+ gc.dispose();
+ }
+ }
+
+ /**
+ * Refresh the Chart, XAxis and YAxis titles to fit the current
+ * chart size.
+ */
+ private void refreshDisplayTitles() {
+ Rectangle chartRect = fChart.getClientArea();
+ Rectangle plotRect = fChart.getPlotArea().getClientArea();
+
+ ITitle chartTitle = checkNotNull(fChart.getTitle());
+ refreshDisplayTitle(chartTitle, fChartTitle, chartRect.width);
+
+ ITitle xTitle = checkNotNull(fChart.getAxisSet().getXAxis(0).getTitle());
+ refreshDisplayTitle(xTitle, fXTitle, plotRect.width);
+
+ ITitle yTitle = checkNotNull(fChart.getAxisSet().getYAxis(0).getTitle());
+ refreshDisplayTitle(yTitle, fYTitle, plotRect.height);
+ }
+
+ /**
+ * Get the aspect with the given name
+ *
+ * @param aspects
+ * The list of aspects to search into
+ * @param aspectName
+ * The name of the aspect we are looking for
+ * @return The corresponding aspect
+ */
+ protected static @Nullable LamiTableEntryAspect getAspectFromName(List<LamiTableEntryAspect> aspects, String aspectName) {
+ for (LamiTableEntryAspect lamiTableEntryAspect : aspects) {
+
+ if (lamiTableEntryAspect.getLabel().equals(aspectName)) {
+ return lamiTableEntryAspect;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Refresh the axis labels to fit the current chart size.
+ */
+ protected abstract void refreshDisplayLabels();
+
+ /**
+ * Redraw the chart.
+ */
+ protected void redraw() {
+ refresh();
+ }
+
+ /**
+ * Signal handler for selection update.
+ *
+ * @param signal
+ * The selection update signal
+ */
+ @TmfSignalHandler
+ public void updateSelection(LamiSelectionUpdateSignal signal) {
+ if (getResultTable().hashCode() != signal.getSignalHash() || equals(signal.getSource())) {
+ /* The signal is not for us */
+ return;
+ }
+ setSelection(signal.getEntryIndex());
+
+ redraw();
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String LamiViewer_DefaultValueName;
+
+ public static String LamiScatterViewer_by;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2015, 2016 EfficiOS Inc. 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
+###############################################################################
+
+LamiViewer_DefaultValueName = Value
+
+LamiScatterViewer_by = by
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Jonathan Rajotte-Julien
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views;
+
+import java.util.function.Predicate;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+
+/**
+ * Basic representation of a check box option for dialog.
+ *
+ * @author Jonathan Rajotte-Julien
+ */
+class LamiAxisCheckBoxOption {
+
+ private final String fName;
+ private final boolean fDefaultValue;
+ private @Nullable Button fButton;
+ private boolean fValue;
+ private final Predicate<LamiTableEntryAspect> fAppliesToAspect;
+
+ /**
+ * Constructor
+ *
+ * @param name
+ * The name of the check box. The actual string shown to user.
+ * @param defaultValue
+ * The default value of the check box.
+ * @param validationPredicate
+ * The predicate to check if an option can be applied to an aspect
+ */
+ public LamiAxisCheckBoxOption(String name, boolean defaultValue, Predicate<LamiTableEntryAspect> validationPredicate) {
+ fName = name;
+ this.fDefaultValue = defaultValue;
+ this.fValue = defaultValue;
+ fButton = null;
+ fAppliesToAspect = validationPredicate;
+ }
+
+ public String getName() {
+ return fName;
+ }
+
+ public boolean getDefaultValue() {
+ return fDefaultValue;
+ }
+
+ public void setButton(Button button) {
+ fButton = button;
+ }
+
+ public boolean getValue() {
+ return fValue;
+ }
+
+ public void updateValue() {
+ if (fButton != null) {
+ fValue = fButton.getSelection();
+ }
+ }
+
+ public void setButtonEnabled(boolean enabled) {
+ @Nullable Button button = fButton;
+ if (button != null) {
+ /* Only change state when necessary */
+ if (button.getEnabled() != enabled) {
+ button.setEnabled(enabled);
+ button.setSelection(fDefaultValue);
+ }
+ }
+ }
+
+ public boolean getButtonEnabled() {
+ if (fButton != null) {
+ return fButton.getEnabled();
+ }
+ return false;
+ }
+
+ public Predicate<LamiTableEntryAspect> getPredicate() {
+ return fAppliesToAspect;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiEmptyAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel.ChartType;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiXYSeriesDescription;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
+import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
+import org.eclipse.tracecompass.tmf.ui.views.TmfView;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * Base view showing output of Babeltrace scripts.
+ *
+ * Implementations can specify which analysis modules to use, which will define
+ * the scripts and parameters to use accordingly.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class LamiReportView extends TmfView {
+
+ // ------------------------------------------------------------------------
+ // Attributes
+ // ------------------------------------------------------------------------
+
+ /** View ID */
+ public static final String VIEW_ID = "org.eclipse.tracecompass.analysis.lami.views.reportview"; //$NON-NLS-1$
+
+ private final @Nullable LamiResultTable fResultTable;
+
+ private @Nullable LamiViewerControl fTableViewerControl;
+ private final Set<LamiViewerControl> fPredefGraphViewerControls = new LinkedHashSet<>();
+ private final Set<LamiViewerControl> fCustomGraphViewerControls = new LinkedHashSet<>();
+ private @Nullable SashForm fSashForm;
+ private Set<Integer> fSelectionIndexes;
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructor
+ */
+ public LamiReportView() {
+ super(VIEW_ID);
+ fResultTable = LamiReportViewFactory.getCurrentResultTable();
+ fSelectionIndexes = new HashSet<>();
+ if (fResultTable != null) {
+ fSelectionIndexes = getIndexOfEntriesIntersectingTimerange(checkNotNull(fResultTable), TmfTraceManager.getInstance().getCurrentTraceContext().getSelectionRange());
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // ViewPart
+ // ------------------------------------------------------------------------
+
+ @Override
+ public void createPartControl(@Nullable Composite parent) {
+ LamiResultTable resultTable = fResultTable;
+ if (resultTable == null || parent == null) {
+ return;
+ }
+
+ SashForm sf = new SashForm(parent, SWT.NONE);
+ fSashForm = sf;
+ setPartName(resultTable.getTableClass().getTableTitle());
+
+ /* Prepare the table viewer, which is always present */
+ LamiViewerControl tableViewerControl = new LamiViewerControl(sf, resultTable);
+ fTableViewerControl = tableViewerControl;
+
+ /* Prepare the predefined graph viewers, if any */
+ resultTable.getTableClass().getPredefinedViews()
+ .forEach(graphModel -> fPredefGraphViewerControls.add(new LamiViewerControl(sf, resultTable, graphModel)));
+
+ /* Automatically open the table viewer initially */
+ tableViewerControl.getToggleAction().run();
+
+ /* Add toolbar buttons */
+ IToolBarManager toolbarMgr = getViewSite().getActionBars().getToolBarManager();
+ toolbarMgr.add(tableViewerControl.getToggleAction());
+ fPredefGraphViewerControls.stream()
+ .map(LamiViewerControl::getToggleAction)
+ .forEach(toolbarMgr::add);
+
+ IMenuManager menuMgr = getViewSite().getActionBars().getMenuManager();
+ IAction newBarChartAction = new NewChartAction(checkNotNull(parent.getShell()), sf, resultTable, ChartType.BAR_CHART);
+ IAction newXYScatterAction = new NewChartAction(checkNotNull(parent.getShell()), sf, resultTable, ChartType.XY_SCATTER);
+
+ newBarChartAction.setText(Messages.LamiReportView_NewCustomBarChart);
+ newXYScatterAction.setText(Messages.LamiReportView_NewCustomScatterChart);
+
+
+ IAction clearCustomViewsAction = new Action() {
+ @Override
+ public void run() {
+ fCustomGraphViewerControls.forEach(LamiViewerControl::dispose);
+ fCustomGraphViewerControls.clear();
+ sf.layout();
+
+ }
+ };
+ clearCustomViewsAction.setText(Messages.LamiReportView_ClearAllCustomViews);
+
+ menuMgr.add(newBarChartAction);
+ menuMgr.add(newXYScatterAction);
+ menuMgr.add(new Separator());
+ menuMgr.add(clearCustomViewsAction);
+
+ /* Simulate a new external signal to the default viewer */
+ LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(LamiReportView.this, fSelectionIndexes, checkNotNull(fResultTable).hashCode());
+ TmfSignalManager.dispatchSignal(signal);
+ }
+
+ // ------------------------------------------------------------------------
+ // Operations
+ // ------------------------------------------------------------------------
+
+ @Override
+ public void setFocus() {
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ if (fSashForm != null) {
+ fSashForm.dispose();
+ }
+ if (fTableViewerControl != null) {
+ fTableViewerControl.dispose();
+ }
+ fPredefGraphViewerControls.forEach(LamiViewerControl::dispose);
+ fCustomGraphViewerControls.forEach(LamiViewerControl::dispose);
+ }
+
+ private class NewChartAction extends Action {
+
+ private final Shell icfDialogParentShell;
+ private final Composite icfChartViewerParent;
+ private final LamiResultTable icfResultTable;
+ private boolean icfXLogScale;
+ private boolean icfYLogScale;
+ private final ChartType icfChartType;
+
+ public NewChartAction(Shell parentShell, Composite chartViewerParent,
+ LamiResultTable resultTable, ChartType chartType) {
+ icfDialogParentShell = parentShell;
+ icfChartViewerParent = chartViewerParent;
+ icfResultTable = resultTable;
+ icfXLogScale = false;
+ icfYLogScale = false;
+ icfChartType = chartType;
+ }
+
+ @Override
+ public void run() {
+ int xLogScaleOptionIndex = -1;
+ int yLogScaleOptionIndex = -1;
+
+ List<LamiTableEntryAspect> xStringColumn = icfResultTable.getTableClass().getAspects().stream()
+ .filter(aspect -> !(aspect instanceof LamiEmptyAspect))
+ .collect(Collectors.toList());
+
+ /* Get the flattened aspects for Y since mapping an aggregate aspect to y series make no sense so far */
+ List<LamiTableEntryAspect> yStringColumn = icfResultTable.getTableClass().getAspects().stream()
+ .filter(aspect -> !(aspect instanceof LamiEmptyAspect))
+ .collect(Collectors.toList());
+
+ switch (icfChartType) {
+ case BAR_CHART:
+ /* Y value must strictly continous and non timestamp */
+ yStringColumn = yStringColumn.stream()
+ .filter(aspect -> !aspect.isTimeStamp() && aspect.isContinuous())
+ .collect(Collectors.toList());
+ break;
+ case PIE_CHART:
+ break;
+ case XY_SCATTER:
+ break;
+ default:
+ break;
+ }
+
+ IStructuredContentProvider contentProvider = checkNotNull(ArrayContentProvider.getInstance());
+
+ LamiSeriesDialog dialog = new LamiSeriesDialog(icfDialogParentShell,
+ icfChartType,
+ xStringColumn,
+ yStringColumn,
+ contentProvider,
+ new LabelProvider() {
+ @Override
+ public String getText(@Nullable Object element) {
+ return ((LamiTableEntryAspect) checkNotNull(element)).getLabel();
+ }
+ },
+ contentProvider,
+ new LabelProvider() {
+ @Override
+ public String getText(@Nullable Object element) {
+ return ((LamiTableEntryAspect) checkNotNull(element)).getLabel();
+ }
+ });
+ dialog.setTitle(icfChartType.toString() + ' ' + Messages.LamiSeriesDialog_creation);
+
+ /* X options per chart type */
+ switch (icfChartType) {
+ case XY_SCATTER:
+ xLogScaleOptionIndex = dialog.addXCheckBoxOption(
+ Messages.LamiSeriesDialog_x_axis + ' ' + Messages.LamiReportView_LogScale,
+ false, new Predicate<LamiTableEntryAspect>() {
+ @Override
+ public boolean test(@NonNull LamiTableEntryAspect t) {
+ return t.isContinuous() && !t.isTimeStamp();
+ }
+ });
+ break;
+ case BAR_CHART:
+ case PIE_CHART:
+ default:
+ break;
+ }
+
+ /* Y options per chart type */
+ switch (icfChartType) {
+ case BAR_CHART:
+ case XY_SCATTER:
+ yLogScaleOptionIndex = dialog.addYCheckBoxOption(
+ Messages.LamiSeriesDialog_y_axis + ' ' + Messages.LamiReportView_LogScale,
+ false, new Predicate<LamiTableEntryAspect>() {
+ @Override
+ public boolean test(@NonNull LamiTableEntryAspect t) {
+ return t.isContinuous() && !t.isTimeStamp();
+ }
+ });
+ break;
+
+ case PIE_CHART:
+ default:
+ break;
+ }
+
+ if (dialog.open() != Window.OK) {
+ return;
+ }
+
+ List<LamiXYSeriesDescription> results = Arrays.stream(dialog.getResult())
+ .map(serie -> (LamiXYSeriesDescription) serie)
+ .collect(Collectors.toList());
+
+ boolean[] xCheckBoxOptionsResults = dialog.getXCheckBoxOptionValues();
+ boolean[] yCheckBoxOptionsResults = dialog.getYCheckBoxOptionValues();
+
+ /* Get X log scale option */
+ if (xLogScaleOptionIndex > -1 && xLogScaleOptionIndex < xCheckBoxOptionsResults.length) {
+ icfXLogScale = xCheckBoxOptionsResults[xLogScaleOptionIndex];
+ }
+ /* Get Y log scale option */
+ if (yLogScaleOptionIndex > -1 && yLogScaleOptionIndex < yCheckBoxOptionsResults.length) {
+ icfYLogScale = yCheckBoxOptionsResults[yLogScaleOptionIndex];
+ }
+
+ List<String> xAxisColString = new ArrayList<>();
+ List<String> yAxisColString = new ArrayList<>();
+
+ /* Specific chart type result fetching */
+ switch (icfChartType) {
+ case PIE_CHART:
+ case BAR_CHART:
+ /* Validate that we only have 1 X aspect */
+ if (results.stream()
+ .map(element -> element.getXAspect().getLabel())
+ .distinct()
+ .count() != 1) {
+ throw new IllegalStateException("No unique X axis label for results"); //$NON-NLS-1$
+ }
+ xAxisColString = results.stream()
+ .map(element -> element.getXAspect().getLabel())
+ .distinct()
+ .collect(Collectors.toList());
+ break;
+ case XY_SCATTER:
+ xAxisColString = results.stream()
+ .map(element -> element.getXAspect().getLabel())
+ .collect(Collectors.toList());
+ break;
+ default:
+ break;
+ }
+
+ yAxisColString = results.stream()
+ .map(element -> element.getYAspect().getLabel())
+ .collect(Collectors.toList());
+
+ LamiChartModel model = new LamiChartModel(icfChartType,
+ nullToEmptyString(Messages.LamiReportView_Custom),
+ xAxisColString,
+ yAxisColString,
+ icfXLogScale,
+ icfYLogScale);
+
+ LamiViewerControl viewerControl = new LamiViewerControl(icfChartViewerParent, icfResultTable, model);
+ fCustomGraphViewerControls.add(viewerControl);
+ viewerControl.getToggleAction().run();
+
+ /* Signal the current selection to the newly created graph */
+ LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(LamiReportView.this, fSelectionIndexes, checkNotNull(fResultTable).hashCode());
+ TmfSignalManager.dispatchSignal(signal);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Signals
+ // ------------------------------------------------------------------------
+
+ /**
+ * Signal handler for selection update.
+ * Propagate a TmfSelectionRangeUpdatedSignal if possible.
+ *
+ * @param signal
+ * The selection update signal
+ */
+ @TmfSignalHandler
+ public void updateSelection(LamiSelectionUpdateSignal signal) {
+ LamiResultTable table = fResultTable;
+ if (table == null) {
+ return;
+ }
+
+ if (table.hashCode() != signal.getSignalHash() || equals(signal.getSource())) {
+ /* The signal is not for us */
+ return;
+ }
+
+ Set<Integer> entryIndex = signal.getEntryIndex();
+
+ /*
+ * Since most of the external viewers deal only with continuous time
+ * ranges and do not allow multi-time range selection, simply signal
+ * only when one selection is present.
+ */
+
+ if (entryIndex.isEmpty()) {
+ /*
+ * In an ideal world we would send a null signal to reset all view
+ * and simply show no selection. But since this is Tracecompass
+ * there is no notion of "unselected state" in most of the viewers so
+ * we do not update/clear the last timerange and show false information to the user.
+ */
+ return;
+ }
+
+ if (entryIndex.size() == 1) {
+ int index = Iterables.getOnlyElement(entryIndex).intValue();
+ LamiTimeRange timeRange = table.getEntries().get(index).getCorrespondingTimeRange();
+ if (timeRange != null) {
+ /* Send Range update to other views */
+ ITmfTimestamp start = TmfTimestamp.fromNanos(timeRange.getStart());
+ ITmfTimestamp end = TmfTimestamp.fromNanos(timeRange.getEnd());
+ TmfSignalManager.dispatchSignal(new TmfSelectionRangeUpdatedSignal(LamiReportView.this, start, end));
+ }
+ }
+
+ fSelectionIndexes = entryIndex;
+ }
+
+ /**
+ * Signal handler for time range selections
+ *
+ * @param signal
+ * The received signal
+ */
+ @TmfSignalHandler
+ public void externalUpdateSelection(TmfSelectionRangeUpdatedSignal signal) {
+ LamiResultTable table = fResultTable;
+ if (table == null) {
+ return;
+ }
+
+ if (signal.getSource() == this) {
+ /* We are the source */
+ return;
+ }
+ TmfTimeRange range = new TmfTimeRange(signal.getBeginTime(), signal.getEndTime());
+
+ Set<Integer> selections = getIndexOfEntriesIntersectingTimerange(table, range);
+
+ /* Update all LamiViewer */
+ LamiSelectionUpdateSignal signal1 = new LamiSelectionUpdateSignal(LamiReportView.this, selections, table.hashCode());
+ TmfSignalManager.dispatchSignal(signal1);
+ }
+
+ private static Set<Integer> getIndexOfEntriesIntersectingTimerange(LamiResultTable table, TmfTimeRange range) {
+ Set<Integer> selections = new HashSet<>();
+ for (LamiTableEntry entry : table.getEntries()) {
+ LamiTimeRange timerange = entry.getCorrespondingTimeRange();
+ if (timerange == null) {
+ /* Return since the table have no timerange */
+ return selections;
+ }
+
+ TmfTimeRange tempTimeRange = new TmfTimeRange(TmfTimestamp.fromNanos(timerange.getStart()), TmfTimestamp.fromNanos(timerange.getEnd()));
+ if (tempTimeRange.getIntersection(range) != null) {
+ selections.add(table.getEntries().indexOf(entry));
+ }
+ }
+ return selections;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysisReport;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * Factory to instantiate and display new Lami report views.
+ *
+ * It works by setting a static field, then having the view access it.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class LamiReportViewFactory {
+
+ private LamiReportViewFactory() {
+ }
+
+ private static @Nullable LamiResultTable currentTable;
+ private static int secondaryViewId = 1;
+
+ /**
+ * Return the current result table
+ *
+ * @return The current result table
+ */
+ public static @Nullable LamiResultTable getCurrentResultTable() {
+ return currentTable;
+ }
+
+ /**
+ * Create all the views from a given report
+ *
+ * @param report
+ * The report to open
+ * @throws PartInitException
+ * If there was a problem initializing a view
+ */
+ public static synchronized void createNewViews(LamiAnalysisReport report) throws PartInitException {
+ boolean firstView = true;
+
+ for (LamiResultTable table : report.getTables()) {
+ currentTable = table;
+
+ int mode = (firstView ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_VISIBLE);
+
+ final IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ page.showView(LamiReportView.VIEW_ID, String.valueOf(secondaryViewId), mode);
+ secondaryViewId++;
+
+ currentTable = null;
+ firstView = false;
+ }
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Jonathan Rajotte-Julien
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.IntStream;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel.ChartType;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiXYSeriesDescription;
+import org.eclipse.ui.dialogs.SelectionDialog;
+
+/**
+ * Series creation dialog
+ *
+ * @author Jonathan Rajotte-Julien
+ */
+public class LamiSeriesDialog extends SelectionDialog {
+
+ private static final int MINIMUM_COLUMN_WIDTH = 30;
+ private static final int MININAL_SERIES_TABLE_HEIGHT = 150;
+
+ /* The root element to populate the viewer with */
+ private final Object fXInputElement;
+ private final Object fYInputElement;
+ private final List<LamiXYSeriesDescription> series;
+
+ /* Providers for populating dialog */
+ private final ILabelProvider fXLabelProvider;
+ private final IStructuredContentProvider fXContentProvider;
+ private final ILabelProvider fYLabelProvider;
+ private final IStructuredContentProvider fYContentProvider;
+ private final IStructuredContentProvider fSeriesContentProvider;
+
+ private final boolean fRestrictXSeriesNumbers;
+
+ private final List<LamiAxisCheckBoxOption> fXCheckBoxOptions;
+ private final List<LamiAxisCheckBoxOption> fYCheckBoxOptions;
+
+ // the visual selection widget group
+ private TableViewer fXTableViewer;
+ private CheckboxTableViewer fYCheckBoxViewer;
+ private TableViewer fSeriesListViewer;
+
+ private Label fWarning;
+
+ /**
+ * @param parentShell
+ * The parent shell of the dialog
+ * @param chartType
+ * The chart type for which the dialog construct series
+ * @param xInput
+ * The possible X axis set of values
+ * @param yInput
+ * The possible Y axis set of values
+ * @param xContentProvider
+ * A content provider for the X axis set
+ * @param xLabelProvider
+ * The label provider for the X axis set
+ * @param yContentProvider
+ * The content provider for the Y axis set
+ * @param yLabelProvider
+ * The label provider for the Y axis set
+ */
+ public LamiSeriesDialog(Shell parentShell, ChartType chartType, Object xInput,
+ Object yInput,
+ IStructuredContentProvider xContentProvider,
+ ILabelProvider xLabelProvider,
+ IStructuredContentProvider yContentProvider,
+ ILabelProvider yLabelProvider) {
+ super(parentShell);
+ fXInputElement = xInput;
+ fYInputElement = yInput;
+ fXContentProvider = xContentProvider;
+ fXLabelProvider = xLabelProvider;
+ fYContentProvider = yContentProvider;
+ fYLabelProvider = yLabelProvider;
+ series = new ArrayList<>();
+ fSeriesContentProvider = checkNotNull(ArrayContentProvider.getInstance());
+
+ fXCheckBoxOptions = new ArrayList<>();
+ fYCheckBoxOptions = new ArrayList<>();
+ fSeriesListViewer = new TableViewer(parentShell);
+ fXTableViewer = new TableViewer(parentShell);
+ fYCheckBoxViewer = checkNotNull(CheckboxTableViewer.newCheckList(parentShell, SWT.NONE));
+
+ /* Dynamic restriction per chart type */
+ switch (chartType) {
+ case XY_SCATTER:
+ fRestrictXSeriesNumbers = false;
+ break;
+ case BAR_CHART:
+ case PIE_CHART:
+ default:
+ fRestrictXSeriesNumbers = true;
+ break;
+ }
+
+ this.fWarning = new Label(parentShell, SWT.NONE);
+ }
+
+ @Override
+ protected Control createDialogArea(@Nullable Composite parent) {
+
+ Composite composite = (Composite) super.createDialogArea(parent);
+ initializeDialogUnits(composite);
+
+ /* Base 3 column grid layout */
+ GridLayout gridLayout = new GridLayout(3, false);
+ composite.setLayout(gridLayout);
+
+ GridData gridData = new GridData(GridData.FILL_BOTH);
+ gridData.horizontalSpan = 3;
+ Group seriesGroup = new Group(composite, SWT.NONE);
+ seriesGroup.setLayoutData(gridData);
+ seriesGroup.setLayout(new GridLayout(3, false));
+ seriesGroup.setText(Messages.LamiSeriesDialog_series);
+
+ /*
+ * New sub group for the series table.
+ */
+ gridData = new GridData(GridData.FILL_BOTH);
+ gridData.horizontalSpan = 2;
+ gridData.heightHint = MININAL_SERIES_TABLE_HEIGHT;
+ Group seriesTableGroup = new Group(seriesGroup, SWT.NONE);
+ seriesTableGroup.setLayoutData(gridData);
+ TableColumnLayout layout = new TableColumnLayout();
+ seriesTableGroup.setLayout(layout);
+
+ /* Current series */
+ fSeriesListViewer = new TableViewer(seriesTableGroup, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
+ fSeriesListViewer.setContentProvider(fSeriesContentProvider);
+ fSeriesListViewer.setInput(series);
+ fSeriesListViewer.getTable().setHeaderVisible(true);
+ fSeriesListViewer.getTable().setLinesVisible(true);
+ TableViewerColumn column1 = createTableViewerColumn(fSeriesListViewer, Messages.LamiSeriesDialog_x_values, element -> element.getXAspect().getLabel());
+ TableViewerColumn column2 = createTableViewerColumn(fSeriesListViewer, Messages.LamiSeriesDialog_y_values, element -> element.getYAspect().getLabel());
+ layout.setColumnData(column1.getColumn(), new ColumnWeightData(1, MINIMUM_COLUMN_WIDTH, true));
+ layout.setColumnData(column2.getColumn(), new ColumnWeightData(1, MINIMUM_COLUMN_WIDTH, true));
+
+ /* Delete series button */
+ gridData = new GridData(GridData.CENTER);
+ gridData.horizontalSpan = 1;
+ Button deleteSeries = new Button(seriesGroup, SWT.PUSH);
+ deleteSeries.setText(Messages.LamiSeriesDialog_delete);
+ deleteSeries.setLayoutData(gridData);
+ deleteSeries.addSelectionListener(new SelectionListener() {
+ @Override
+ public void widgetSelected(@Nullable SelectionEvent e) {
+ /* Remove the selectecd series */
+ IStructuredSelection selections = (IStructuredSelection) fSeriesListViewer.getSelection();
+ for (Object selection : selections.toList()) {
+ series.remove(selection);
+ }
+ /* When table is empty reset to initial state */
+ if (series.isEmpty()) {
+ /* Make sure the OK button is disabled */
+ getButton(IDialogConstants.OK_ID).setEnabled(false);
+ /* Hide the selection warning */
+ fWarning.setVisible(false);
+
+ /*
+ * Reset the initial selection of the X axis selection table
+ */
+ fXTableViewer.refresh();
+ /* Reset check boxes options */
+ fXCheckBoxOptions.forEach(checkBox -> {
+ checkBox.setButtonEnabled(true);
+ });
+ fYCheckBoxOptions.forEach(checkBox -> {
+ checkBox.setButtonEnabled(true);
+ });
+ }
+ /* Refresh the series table to show the added series */
+ fSeriesListViewer.refresh();
+ }
+
+ @Override
+ public void widgetDefaultSelected(@Nullable SelectionEvent e) {
+ }
+ });
+
+ /*
+ * Series creator subgroup
+ */
+ gridData = new GridData(GridData.FILL_BOTH);
+ gridData.horizontalSpan = 3;
+ Group seriesCreatorGroup = new Group(composite, getShellStyle());
+ seriesCreatorGroup.setLayoutData(gridData);
+ seriesCreatorGroup.setLayout(new GridLayout(3, false));
+ seriesCreatorGroup.setText(Messages.LamiSeriesDialog_serie_creator);
+
+ /* X axis sash label */
+ gridData = new GridData(GridData.FILL_BOTH | GridData.VERTICAL_ALIGN_END);
+ gridData.horizontalSpan = 1;
+ Label xSeriesCreatorLabel = new Label(seriesCreatorGroup, SWT.CENTER);
+ xSeriesCreatorLabel.setLayoutData(gridData);
+ xSeriesCreatorLabel.setText(Messages.LamiSeriesDialog_x_axis);
+
+ gridData = new GridData(GridData.FILL_BOTH | GridData.VERTICAL_ALIGN_END);
+ gridData.horizontalSpan = 1;
+ Label ySeriesCreatorLabel = new Label(seriesCreatorGroup, SWT.CENTER);
+ ySeriesCreatorLabel.setLayoutData(gridData);
+ ySeriesCreatorLabel.setText(Messages.LamiSeriesDialog_y_axis);
+
+ /* Empty label for grid layout */
+ gridData = new GridData(GridData.FILL_BOTH);
+ gridData.horizontalSpan = 1;
+ Label emptyLabel = new Label(seriesCreatorGroup, SWT.CENTER);
+ emptyLabel.setLayoutData(gridData);
+
+ SashForm sash1 = new SashForm(seriesCreatorGroup, SWT.BORDER | SWT.HORIZONTAL);
+ gridData = new GridData(GridData.FILL_BOTH);
+ gridData.horizontalSpan = 2;
+ sash1.setLayoutData(gridData);
+ sash1.setVisible(true);
+
+ fXTableViewer = new TableViewer(sash1, getTableStyle());
+ fXTableViewer.setContentProvider(fXContentProvider);
+ fXTableViewer.setLabelProvider(fXLabelProvider);
+ fXTableViewer.setInput(fXInputElement);
+
+ fYCheckBoxViewer = checkNotNull(CheckboxTableViewer.newCheckList(sash1, SWT.BORDER));
+ fYCheckBoxViewer.setLabelProvider(fYLabelProvider);
+ fYCheckBoxViewer.setContentProvider(fYContentProvider);
+ fYCheckBoxViewer.setInput(fYInputElement);
+
+ gridData = new GridData(SWT.FILL, SWT.NONE, true, true);
+ gridData.horizontalSpan = 1;
+ Button button1 = new Button(seriesCreatorGroup, SWT.PUSH);
+ button1.setText(Messages.LamiSeriesDialog_add);
+ button1.setLayoutData(gridData);
+ button1.addSelectionListener(new SelectionListener() {
+
+ @Override
+ public void widgetSelected(@Nullable SelectionEvent e) {
+ Object[] ySelections = fYCheckBoxViewer.getCheckedElements();
+ IStructuredSelection xSelections = (IStructuredSelection) fXTableViewer.getSelection();
+ @Nullable Object x = xSelections.getFirstElement();
+ if (!(x instanceof LamiTableEntryAspect) || ySelections.length == 0) {
+ return;
+ }
+
+ /* Add selection to series if it doesn not already exist in the list */
+ for (Object y : ySelections) {
+ if(!(y instanceof LamiTableEntryAspect)) {
+ continue;
+ }
+ LamiXYSeriesDescription serie = new LamiXYSeriesDescription((LamiTableEntryAspect) x, ((LamiTableEntryAspect) y));
+ if (!series.contains(serie)) {
+ series.add(serie);
+ fSeriesListViewer.refresh();
+ }
+ }
+
+ /* Set label warning visible and enable OK button */
+ fWarning.setVisible(true);
+ getButton(IDialogConstants.OK_ID).setEnabled(true);
+
+ /* Update possible X selection based on current series */
+ TableItem[] items = fXTableViewer.getTable().getItems();
+ Arrays.stream(items).forEach(item -> {
+ LamiTableEntryAspect aspect = (LamiTableEntryAspect) item.getData();
+ if (!aspect.arePropertiesEqual(series.get(0).getXAspect())) {
+ fXTableViewer.remove(aspect);
+ }
+ if (fRestrictXSeriesNumbers && aspect != (series.get(0).getXAspect())) {
+ fXTableViewer.remove(aspect);
+ }
+ });
+
+ /*
+ * Disable all checkBox that do not apply to aspects series.
+ * Simply take the first one since all series should comply to
+ * the same restriction
+ */
+ fXCheckBoxOptions.forEach(checkBox -> {
+ checkBox.setButtonEnabled(checkBox.getPredicate().test(series.get(0).getXAspect()));
+ });
+ fYCheckBoxOptions.forEach(checkBox -> {
+ checkBox.setButtonEnabled(checkBox.getPredicate().test(series.get(0).getYAspect()));
+ });
+ }
+
+ @Override
+ public void widgetDefaultSelected(@Nullable SelectionEvent e) {
+ }
+ });
+
+
+ gridData = new GridData(GridData.FILL_BOTH | GridData.VERTICAL_ALIGN_END);
+ gridData.horizontalSpan = 3;
+ fWarning = new Label(seriesCreatorGroup, SWT.LEFT);
+ fWarning.setLayoutData(gridData);
+ fWarning.setText(Messages.LamiSeriesDialog_selectionRestrictionWarning);
+ fWarning.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
+ fWarning.setVisible(false);
+
+ gridData = new GridData(GridData.FILL_BOTH);
+ gridData.horizontalSpan = 3;
+ Group optionGroups = new Group(composite, getShellStyle());
+ optionGroups.setLayoutData(gridData);
+ optionGroups.setLayout(new GridLayout(3, false));
+ optionGroups.setText(Messages.LamiSeriesDialog_chart_options);
+
+ for (LamiAxisCheckBoxOption checkBox : fXCheckBoxOptions) {
+ Button button = new Button(optionGroups, SWT.CHECK);
+ button.setSelection(checkBox.getDefaultValue());
+ button.setText(checkBox.getName());
+ checkBox.setButton(button);
+ }
+
+ for (LamiAxisCheckBoxOption checkBox : fYCheckBoxOptions) {
+ Button button = new Button(optionGroups, SWT.CHECK);
+ button.setSelection(checkBox.getDefaultValue());
+ button.setText(checkBox.getName());
+ checkBox.setButton(button);
+ }
+
+ fYCheckBoxViewer.getTable().addSelectionListener(new SelectionListener() {
+
+ @Override
+ public void widgetSelected(@Nullable SelectionEvent e) {
+ /* On check */
+ if (e != null && e.detail == SWT.CHECK) {
+ /* Change possible selection */
+ IStructuredSelection selections = (IStructuredSelection) fYCheckBoxViewer.getSelection();
+ if (selections.getFirstElement() != null) {
+
+ boolean checked = fYCheckBoxViewer.getChecked(selections.getFirstElement());
+ /*
+ * If just selected look for stuff to disable. If not no
+ * need to look for stuff to disable since it was
+ * already done before.
+ */
+ if (checked) {
+ TableItem[] items = fYCheckBoxViewer.getTable().getItems();
+ Arrays.stream(items).forEach(item -> {
+ LamiTableEntryAspect aspect = (LamiTableEntryAspect) item.getData();
+ if (!aspect.arePropertiesEqual((LamiTableEntryAspect) checkNotNull(selections.getFirstElement()))) {
+ fYCheckBoxViewer.remove(aspect);
+ }
+ });
+ } else if (!checked && fYCheckBoxViewer.getCheckedElements().length == 0 && fSeriesListViewer.getTable().getItemCount() == 0) {
+ fYCheckBoxViewer.refresh();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(@Nullable SelectionEvent e) {
+ }
+ });
+
+ Dialog.applyDialogFont(composite);
+ return composite;
+ }
+
+ /*
+ * Disable OK button on dialog creation.
+ */
+ @Override
+ protected void createButtonsForButtonBar(@Nullable Composite parent) {
+ super.createButtonsForButtonBar(parent);
+ getButton(IDialogConstants.OK_ID).setEnabled(false);
+ }
+
+ /**
+ * Return the style flags for the table viewer.
+ *
+ * @return int
+ */
+ protected int getTableStyle() {
+ return SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER;
+ }
+
+ /**
+ * Add check box option for X series.
+ *
+ * @param name
+ * The name of the option. The actual text shown to the user.
+ * @param defaultValue
+ * The default state of the check box option.
+ * @param predicate
+ * The predicate to check if the option applies to the given
+ * aspect
+ * @return The index of the option value in the result table.
+ */
+ public int addXCheckBoxOption(String name, boolean defaultValue, Predicate<LamiTableEntryAspect> predicate) {
+ LamiAxisCheckBoxOption checkBox = new LamiAxisCheckBoxOption(name, defaultValue, predicate);
+ fXCheckBoxOptions.add(checkBox);
+ return fXCheckBoxOptions.size() - 1;
+ }
+
+ /**
+ * Add check box option for Y series.
+ *
+ * @param name
+ * The name of the option. The actual text shown to the user.
+ * @param defaultValue
+ * The default state of the check box option.
+ * @param predicate
+ * The predicate to check if the option applies to the given
+ * aspect
+ * @return The index of the option value in the result table.
+ */
+ public int addYCheckBoxOption(String name, boolean defaultValue, Predicate<LamiTableEntryAspect> predicate) {
+ LamiAxisCheckBoxOption checkbox = new LamiAxisCheckBoxOption(name, defaultValue, predicate);
+ fYCheckBoxOptions.add(checkbox);
+ return fYCheckBoxOptions.size() - 1;
+ }
+
+ /**
+ * @return The final values of X series check boxes.
+ */
+ public boolean[] getXCheckBoxOptionValues() {
+ boolean[] selections = new boolean[fXCheckBoxOptions.size()];
+ if (selections.length != 0) {
+ IntStream.range(0, selections.length).forEach(i -> selections[i] = fXCheckBoxOptions.get(i).getValue());
+ }
+ return selections;
+ }
+
+ /**
+ * @return The final values of Y series check boxes.
+ */
+ public boolean[] getYCheckBoxOptionValues() {
+ boolean[] selections = new boolean[fYCheckBoxOptions.size()];
+ if (selections.length != 0) {
+ IntStream.range(0, selections.length).forEach(i -> selections[i] = fYCheckBoxOptions.get(i).getValue());
+ }
+ return selections;
+ }
+
+ @Override
+ protected void okPressed() {
+ for (LamiAxisCheckBoxOption checkBox : fXCheckBoxOptions) {
+ checkBox.updateValue();
+ }
+ for (LamiAxisCheckBoxOption checkBox : fYCheckBoxOptions) {
+ checkBox.updateValue();
+ }
+ super.okPressed();
+ }
+
+ @Override
+ public Object[] getResult() {
+ return series.toArray();
+ }
+
+ private static <T extends Comparable<T>> TableViewerColumn createTableViewerColumn(TableViewer viewer, String name,
+ Function<LamiXYSeriesDescription, T> propertyFunction) {
+ TableViewerColumn viewerColumn = new TableViewerColumn(viewer, SWT.CENTER);
+ viewerColumn.setLabelProvider(new ColumnLabelProvider() {
+ @Override
+ public @Nullable String getText(@Nullable Object element) {
+ if (element != null) {
+ return propertyFunction.apply((LamiXYSeriesDescription) element).toString();
+ }
+ return null;
+ }
+ });
+
+ TableColumn column = viewerColumn.getColumn();
+ column.setText(name);
+ return viewerColumn;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.tracecompass.internal.analysis.lami.ui.Activator;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers.ILamiViewer;
+
+/**
+ * Control for Lami viewers.
+ *
+ * Since viewers can be disposed, the "viewer control" will remain and be ready
+ * to re-instantiate the viewer if required to.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class LamiViewerControl {
+
+ private final Action fToggleAction;
+
+ private @Nullable ILamiViewer fViewer;
+
+ /**
+ * Build a new control for a Lami table viewer.
+ *
+ * @param parent
+ * The parent composite
+ * @param table
+ * The results table populating the table viewer
+ */
+ public LamiViewerControl(Composite parent, LamiResultTable table) {
+ fToggleAction = new Action() {
+ @Override
+ public void run() {
+ ILamiViewer viewer = fViewer;
+ if (viewer == null) {
+ fViewer = ILamiViewer.createLamiTable(parent, table);
+ } else {
+ viewer.dispose();
+ fViewer = null;
+ }
+ parent.layout();
+ }
+ };
+ fToggleAction.setText(Messages.LamiReportView_ActivateTableAction_ButtonName);
+ fToggleAction.setToolTipText(Messages.LamiReportView_ActivateTableAction_ButtonTooltip);
+ fToggleAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath("icons/table.gif")); //$NON-NLS-1$
+ }
+
+ /**
+ * Build a new control for a graph viewer.
+ *
+ * @param parent
+ * The parent composite
+ * @param table
+ * The table containing the source data
+ * @param graphModel
+ * The graph model
+ */
+ public LamiViewerControl(Composite parent, LamiResultTable table, LamiChartModel graphModel) {
+ fToggleAction = new Action() {
+ @Override
+ public void run() {
+ ILamiViewer viewer = fViewer;
+ if (viewer == null) {
+ fViewer = ILamiViewer.createLamiChart(parent, table, graphModel);
+ } else {
+ viewer.dispose();
+ fViewer = null;
+ }
+ parent.layout();
+ }
+ };
+ fToggleAction.setText(Messages.LamiReportView_ToggleAction_ButtonNamePrefix + ' ' + graphModel.getName());
+ fToggleAction.setToolTipText(Messages.LamiReportView_ToggleAction_ButtonTooltip);
+ fToggleAction.setImageDescriptor(getIconForGraphType(graphModel.getChartType()));
+ }
+
+ /**
+ * Get the viewer of this control. Returns null if the viewer is current
+ * disposed.
+ *
+ * @return The viewer
+ */
+ public @Nullable ILamiViewer getViewer() {
+ return fViewer;
+ }
+
+ /**
+ * Get the toggle action that shows/hide this control's viewer.
+ *
+ * @return The toggle action
+ */
+ public Action getToggleAction() {
+ return fToggleAction;
+ }
+
+ /**
+ * Explicitly dispose this control's viewer.
+ */
+ public void dispose() {
+ if (fViewer != null) {
+ fViewer.dispose();
+ }
+ }
+
+ private static @Nullable ImageDescriptor getIconForGraphType(LamiChartModel.ChartType graphType) {
+ switch (graphType) {
+ case BAR_CHART:
+ return Activator.getDefault().getImageDescripterFromPath("icons/histogram.gif"); //$NON-NLS-1$
+ case PIE_CHART:
+ case XY_SCATTER:
+ default:
+ // FIXME Use other icons
+ return Activator.getDefault().getImageDescripterFromPath("icons/histogram.gif"); //$NON-NLS-1$
+ }
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String LamiReportView_ActivateTableAction_ButtonName;
+ public static String LamiReportView_ActivateTableAction_ButtonTooltip;
+
+ public static String LamiReportView_ToggleAction_ButtonNamePrefix;
+ public static String LamiReportView_ToggleAction_ButtonTooltip;
+
+ public static String LamiReportView_NewCustomBarChart;
+ public static String LamiReportView_NewCustomScatterChart;
+ public static String LamiReportView_ClearAllCustomViews;
+ public static String LamiReportView_LogScale;
+ public static String LamiReportView_SelectColumnForX;
+ public static String LamiReportView_SelectColumnsForCategories;
+ public static String LamiReportView_SelectColumnsForSeries;
+ public static String LamiReportView_Custom;
+
+ public static String LamiSeriesDialog_creation;
+ public static String LamiSeriesDialog_add;
+ public static String LamiSeriesDialog_chart_options;
+ public static String LamiSeriesDialog_delete;
+ public static String LamiSeriesDialog_selectionRestrictionWarning;
+ public static String LamiSeriesDialog_serie_creator;
+ public static String LamiSeriesDialog_series;
+ public static String LamiSeriesDialog_x_axis;
+ public static String LamiSeriesDialog_x_values;
+ public static String LamiSeriesDialog_y_axis;
+ public static String LamiSeriesDialog_y_values;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2015, 2016 EfficiOS Inc. 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
+###############################################################################
+
+LamiReportView_ActivateTableAction_ButtonName = Toggle Table
+LamiReportView_ActivateTableAction_ButtonTooltip = Toggle the Table view of the results
+
+LamiReportView_ToggleAction_ButtonNamePrefix = Toggle
+LamiReportView_ToggleAction_ButtonTooltip = Toggle showing this graph in the view
+
+LamiReportView_NewCustomBarChart = New custom bar chart
+LamiReportView_NewCustomScatterChart = New custom scatter chart
+LamiReportView_ClearAllCustomViews = Clear all custom views
+LamiReportView_LogScale = Log scale
+LamiReportView_SelectColumnForX = Select the column used for the X axis
+LamiReportView_SelectColumnsForCategories = Select the columns used for the categories
+LamiReportView_SelectColumnsForSeries = Select the columns used for the series
+LamiReportView_Custom = Custom
+
+LamiSeriesDialog_creation = chart series creation
+LamiSeriesDialog_add = Add
+LamiSeriesDialog_chart_options = Chart options
+LamiSeriesDialog_delete = Delete
+LamiSeriesDialog_selectionRestrictionWarning = Note: Selection might be restricted based on type checking of previously selected series
+LamiSeriesDialog_serie_creator = Series creator
+LamiSeriesDialog_series = Series
+LamiSeriesDialog_x_axis = X axis
+LamiSeriesDialog_x_values = X-Values
+LamiSeriesDialog_y_axis = Y axis
+LamiSeriesDialog_y_values = Y-Values
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 EfficiOS Inc. 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
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views;