package org.radargun.reporting;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.radargun.utils.TimeService;
/**
* Events that should be presented in report
*
* @author Radim Vansa <rvansa@redhat.com>
*/
public class Timeline implements Serializable, Comparable<Timeline> {
public final int slaveIndex;
/* Events plotted on all charts as marker events. */
private Map<String, List<MarkerEvent>> events = new HashMap<>();
/* Values plotted in separate charts */
private Map<Category, List<Value>> values = new HashMap<>();
private long firstTimestamp = Long.MAX_VALUE;
private long lastTimestamp = Long.MIN_VALUE;
public Timeline(int slaveIndex) {
this.slaveIndex = slaveIndex;
}
public synchronized void addEvent(String category, MarkerEvent e) {
List<MarkerEvent> cat = events.get(category);
if (cat == null) {
cat = new ArrayList<>();
events.put(category, cat);
}
cat.add(e);
updateTimestamps(e);
}
public synchronized void addValue(Category category, Value e) {
List<Value> cat = values.get(category);
if (cat == null) {
cat = new ArrayList<>();
values.put(category, cat);
}
cat.add(e);
updateTimestamps(e);
}
public boolean containsValuesOfType(Category.Type type) {
return values.keySet().stream().anyMatch(e -> e.getType().equals(type));
}
public static class Category implements Serializable, Comparable<Category> {
private final String name;
private final Type type;
private Category(String name, Type type) {
this.name = name;
this.type = type;
}
@Override
public int compareTo(Category o) {
return this.getName().compareTo(o.getName());
}
public enum Type {
/* All events related to system resources (CPU, memory, network, etc.) */
SYSMONITOR,
/* Any other type of events, e.g. recording values in background stages */
CUSTOM
}
public static Category sysCategory(String name) {
return new Category(name, Type.SYSMONITOR);
}
public static Category customCategory(String name) {
return new Category(name, Type.CUSTOM);
}
public String getName() {
return name;
}
public Type getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Category category = (Category) o;
if (!name.equals(category.name)) return false;
return type == category.type;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + type.hashCode();
return result;
}
}
private void updateTimestamps(MarkerEvent e) {
firstTimestamp = Math.min(firstTimestamp, e.getStarted());
lastTimestamp = Math.max(lastTimestamp, e.getEnded());
}
private void updateTimestamps(Value v) {
firstTimestamp = Math.min(firstTimestamp, v.getStarted());
lastTimestamp = Math.max(lastTimestamp, v.getEnded());
}
public synchronized Set<String> getEventCategories() {
return events.keySet();
}
public synchronized Set<Category> getValueCategories() {
return values.keySet();
}
public synchronized List<MarkerEvent> getEvents(String category) {
return events.get(category);
}
public synchronized List<Value> getValues(Category category) {
return values.get(category);
}
public long getFirstTimestamp() {
return firstTimestamp;
}
public long getLastTimestamp() {
return lastTimestamp;
}
@Override
public int compareTo(Timeline o) {
return Integer.compare(slaveIndex, o.slaveIndex);
}
/**
* A single value in the chart in time, such as CPU utilization. The value is reported
* in a single chart dedicated for this type of values.
*/
public static class Value implements Serializable, Comparable<MarkerEvent> {
public final Number value;
public final long timestamp;
public Value(long timestamp, Number value) {
this.timestamp = timestamp;
this.value = value;
}
public Value(Number value) {
this.timestamp = TimeService.currentTimeMillis();
this.value = value;
}
@Override
public String toString() {
// doubles require %f, integers %d -> we use %s
return String.format("Value{timestamp=%d, value=%s}", timestamp, value);
}
@Override
public int compareTo(MarkerEvent o) {
return Long.compare(timestamp, o.timestamp);
}
public long getStarted() {
return timestamp;
}
public long getEnded() {
return timestamp;
}
}
/**
* Generic event in timeline
*/
public abstract static class MarkerEvent implements Serializable, Comparable<MarkerEvent> {
public final long timestamp;
protected MarkerEvent(long timestamp) {
this.timestamp = timestamp;
}
protected MarkerEvent() {
this(TimeService.currentTimeMillis());
}
@Override
public int compareTo(MarkerEvent o) {
return Long.compare(timestamp, o.timestamp);
}
public long getStarted() {
return timestamp;
}
public long getEnded() {
return timestamp;
}
}
/**
* Occurence of this event is not a value in any series, such as slave crash.
*/
public static class TextEvent extends MarkerEvent {
public final String text;
public TextEvent(long timestamp, String text) {
super(timestamp);
this.text = text;
}
public TextEvent(String text) {
this.text = text;
}
@Override
public String toString() {
return String.format("TextEvent{timestamp=%d, text=%s}", timestamp, text);
}
}
/**
* MarkerEvent representing some continuous operation taking place for some period of time
*/
public static class IntervalEvent extends MarkerEvent {
public final String description;
public final long duration; // milliseconds
public IntervalEvent(long timestamp, String description, long duration) {
super(timestamp);
this.description = description;
this.duration = duration;
}
public IntervalEvent(String description, long duration) {
this.description = description;
this.duration = duration;
}
@Override
public long getEnded() {
return timestamp + duration;
}
@Override
public String toString() {
return String.format("IntervalEvent{timestamp=%d, duration=%d, description=%s}",
timestamp, duration, description);
}
}
/**
* Dummy class used for remote signalization
*/
public static class Request implements Serializable {
}
}