blob: 7ada55d0d4725eba1973ce2afe909d9986c48760 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2018 EfficiOS Inc. and others
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tracecompass.statesystem.core.snapshot;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.statesystem.core.Activator;
import org.eclipse.tracecompass.internal.statesystem.core.interval.json.TmfIntervalDeserializer;
import org.eclipse.tracecompass.internal.statesystem.core.interval.json.TmfIntervalSerializer;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.statesystem.core.interval.TmfStateInterval;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Since;
/**
* Wrapper object representing a full query, along with its corresponding
* attributes. It allows to reconstruct an initial state from scratch.
*
* @author Alexandre Montplaisir
* @author Philippe Proulx
* @since 4.0
*/
@NonNullByDefault
public class StateSnapshot {
private static final class AttributeAndInterval {
@SerializedName("Path")
@Since(1.0)
private final List<@NonNull String> fAttribute;
@SerializedName("Value")
@Since(1.0)
private final ITmfStateInterval fInterval;
/**
* Constructor
*
* @param path
* attribute path
* @param value
* the interval
*/
public AttributeAndInterval(List<@NonNull String> path, ITmfStateInterval value) {
fAttribute = path;
fInterval = value;
}
public List<@NonNull String> getPath() {
return fAttribute;
}
public ITmfStateInterval getInterval() {
return fInterval;
}
}
/** File format version. Bump if the format changes */
private static final int SNAPSHOT_FORMAT_VERSION = 1;
private static final String SNAPSHOT_DIRECTORY = ".tc-states"; //$NON-NLS-1$
private static final String FILE_SUFFIX = ".snapshot.json"; //$NON-NLS-1$
private static final Gson GSON = Objects.requireNonNull(new GsonBuilder()
.registerTypeAdapter(ITmfStateInterval.class, new TmfIntervalDeserializer())
.registerTypeAdapter(ITmfStateInterval.class, new TmfIntervalSerializer())
.create());
@SerializedName("snapshot_format_version")
@Since(1.0)
private int fStateFormatVersion = SNAPSHOT_FORMAT_VERSION;
@SerializedName("ssid")
@Since(1.0)
private String fSsid;
@SerializedName("snapshot_version")
@Since(1.0)
private int fVersion;
@SerializedName("timestamp")
@Since(1.0)
private int fTimestamp;
@SerializedName("attributes")
@Since(1.0)
private Collection<AttributeAndInterval> fAttributes = Collections.emptyList();
private final long fEndTime;
private long fStartTime;
/**
* Get the format version
*
* @return the format
*/
public int getStateFormatVersion() {
return fStateFormatVersion;
}
/**
* Get the state system ID
*
* @return the ssid
*/
public String getSsid() {
return fSsid;
}
/**
* Get the states
*
* @return the states
*/
@SuppressWarnings("null")
public Map<List<String>, ITmfStateInterval> getStates() {
return fAttributes.stream()
.collect(
Collectors.toMap(
AttributeAndInterval::getPath,
AttributeAndInterval::getInterval));
}
/**
* Clamping constructor. Builds a snapshot from a given state system and
* timestamp.
*
* @param ss
* The state system for which to build the state dump
* @param start
* The timestamp at which to query the state to dump
* @param end
* The timestamp at which the
* @param version
* Version of the snapshot
* @since 4.1
*/
public StateSnapshot(ITmfStateSystem ss, long start, long end, int version) {
fStartTime = start;
fEndTime = end;
List<ITmfStateInterval> fullQuery;
fVersion = version;
fSsid = String.valueOf(ss.getSSID());
try {
fullQuery = ss.queryFullState(start);
} catch (StateSystemDisposedException e1) {
fVersion = -1;
return;
}
ImmutableList.Builder<@NonNull AttributeAndInterval> states = new ImmutableList.Builder<>();
for (int quark = 0; quark < ss.getNbAttributes(); quark++) {
String @NonNull [] fullAttributePathArray = ss.getFullAttributePathArray(quark);
ITmfStateInterval interval = fullQuery.get(quark);
long startTime = Math.max(interval.getStartTime(), fStartTime);
/* Negative end time is used to prevent insertion of null future state */
long endTime = interval.getEndTime() > fEndTime ? Long.MIN_VALUE : interval.getEndTime();
interval = new TmfStateInterval(startTime, endTime, interval.getAttribute(), interval.getValue());
states.add(new AttributeAndInterval(Arrays.asList(fullAttributePathArray), interval));
}
fAttributes = states.build();
}
/**
* "Online" constructor. Builds a snapshot from a given state system and
* timestamp.
*
* @param ss
* The state system for which to build the state dump
* @param timestamp
* The timestamp at which to query the state to dump
* @param version
* Version of the snapshot
*/
public StateSnapshot(ITmfStateSystem ss, long timestamp, int version) {
this(ss, timestamp, timestamp, version);
}
/**
* Get the version of this snapshot. Can be used to consider if a snapshot
* should be read or not if the analysis changed since it was written.
*
* @return The snapshot's version
*/
public int getVersion() {
return fVersion;
}
/**
* Save this snapshot at the given location.
*
* @param parentPath
* The location where to save the snapshot file, usually in or close
* to its corresponding trace. It will be put under a Trace
* Compass-specific sub-directory.
* @throws IOException
* If there are problems creating or writing to the target directory
*/
public void write(Path parentPath) throws IOException {
/* Create directory if it does not exist */
Path sdPath = parentPath.resolve(SNAPSHOT_DIRECTORY);
if (!sdPath.toFile().exists()) {
Files.createDirectory(sdPath);
}
/* Create state dump file */
String fileName = fSsid + FILE_SUFFIX;
Path filePath = sdPath.resolve(fileName);
if (filePath.toFile().exists()) {
Files.delete(filePath);
}
Files.createFile(filePath);
try (Writer bw = Files.newBufferedWriter(filePath, Charsets.UTF_8)) {
String json = GSON.toJson(this);
bw.write(json);
}
}
/**
* Retrieve a previously-saved snapshot.
*
* @param parentPath
* The expected location of the snapshot file. This is the parent
* path of the TC-specific subdirectory.
* @param ssid
* The ID of the state system to retrieve
* @return The corresponding de-serialized snapshot. Returns null if there are
* no snapshot for this state system ID (or no snapshot directory at
* all).
*/
public static @Nullable StateSnapshot read(Path parentPath, String ssid) {
/* Find the state dump directory */
Path sdPath = parentPath.resolve(SNAPSHOT_DIRECTORY);
if (!sdPath.toFile().isDirectory()) {
return null;
}
/* Find the state dump file */
String fileName = ssid + FILE_SUFFIX;
Path filePath = sdPath.resolve(fileName);
if (!filePath.toFile().exists()) {
return null;
}
try (InputStreamReader in = new InputStreamReader(Files.newInputStream(filePath, StandardOpenOption.READ))) {
BufferedReader bufReader = new BufferedReader(in);
String json = bufReader.lines().collect(Collectors.joining("\n")); //$NON-NLS-1$
return GSON.fromJson(json, StateSnapshot.class);
} catch (IOException e) {
Activator.getDefault().logError("Error reading snapshot " + parentPath + " " + ssid, e); //$NON-NLS-1$ //$NON-NLS-2$
}
return null;
}
}