![JAR search and dependency download from the Maven repository](/logo.png)
com.bigdata.service.Event Maven / Gradle / Ivy
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Feb 3, 2009
*/
package com.bigdata.service;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;
import com.bigdata.counters.AbstractStatisticsCollector;
import com.bigdata.journal.ITransactionService;
/**
* An event. Events are queued by the {@link IBigdataClient} and self-reported
* periodically to the {@link ILoadBalancerService}. The event is assigned a
* {@link UUID} when it is created and the {@link ILoadBalancerService} assigned
* start and end event times based on its local clock as the events are received
* (this helps to reduce the demand on the {@link ITransactionService} for
* global timestamp).
*
* @author Bryan Thompson
* @version $Id$
*
* @todo compact event serialization when reporting to the
* {@link ILoadBalancerService}, including factoring out of the common
* metadata (some stuff will always be the same for a given reported and
* does not need to be reported with each event).
*
* @todo Add a level property to events? (but events are not meant to replace
* logging)
*/
public class Event implements Serializable {
protected static transient final Logger log = Logger.getLogger(Event.class);
/**
*
*/
private static final long serialVersionUID = 2651293369056916231L;
protected transient IBigdataFederation fed;
/**
* Unique event identifier.
*/
public final UUID eventUUID;
/**
* The host on which the event was generated.
*/
public final String hostname;
/**
* The most interesting class or interface for the service which generated
* that event.
*/
public final Class serviceIface;
/**
* The name of the service which generated the event.
*/
public final String serviceName;
/**
* The {@link UUID} for the service which generated that event.
*/
public final UUID serviceUUID;
/**
* The resource for which the event is reported (store file, index name,
* etc). Note that the {@link #serviceUUID} will also be reported.
*/
public final EventResource resource;
/**
* Major event type (classification or category).
*/
public final Object majorEventType;
/**
* Minor event type (classification or category).
*/
public final Object minorEventType;
/**
* Event details.
*/
protected Map details;
/**
* Event details.
*/
public Map getDetails() {
return details;
}
/**
* Add details.
* @param details
* @return This {@link Event}.
*/
public Event addDetails(final Map details) {
if(details == null)
throw new IllegalArgumentException();
synchronized (this) {
if (this.details == null) {
this.details = newDetails();
}
this.details.putAll(details);
}
return this;
}
/**
* Factory for the details hash map. This is a synchronized collection since
* the event can be serialized concurrent with {@link #end()} which would
* otherwise lead to a {@link ConcurrentModificationException}. It can not
* be a {@link ConcurrentHashMap} since that class does not support
* null
values.
*/
protected static Map newDetails() {
return Collections.synchronizedMap(new HashMap());
}
/**
* Add a detail.
* @param name
* @param value
* @return This {@link Event}.
*/
public Event addDetail(final String name, final Object value) {
if(name == null)
throw new IllegalArgumentException();
synchronized (this) {
if (details == null) {
details = newDetails();
}
details.put(name, value);
}
return this;
}
/**
* The event start time. Assigned locally. The recipient may use [endTime -
* startTime] to adjust the event to its local clock.
*/
protected long startTime;
/**
* The event end time. Assigned locally. The recipient may use [endTime -
* startTime] to adjust the event to its local clock.
*/
protected long endTime;
/**
* The time when the event was received. The recipient may use [endTime -
* startTime] to adjust the event to its local clock.
*/
transient protected long receiptTime;
/**
* true
iff the event event has been generated.
*/
protected boolean complete = false;
/**
* true
iff the event event has been generated.
*/
public boolean isComplete() {
return complete;
}
public long getStartTime() {
return startTime;
}
public long getEndTime() {
return endTime;
}
/**
* The elapsed time for the event.
*/
public long getElapsed() {
if (endTime == 0)
return System.currentTimeMillis() - startTime;
else
return endTime - startTime;
}
public Event(final IBigdataFederation fed, final EventResource resource,
final Object majorEventType) {
this(fed, resource, majorEventType, (Map) null/* details */);
}
/**
* Event ctor.
*
* @param fed
* The federation object (used to send the event to an
* aggregator).
* @param resource
* The resource for which the event was generated (store, index
* partition, etc).
* @param majorEventType
* The major type of the event (use of enums is encouraged).
* @param details
* Optional details for the event.
*/
public Event(final IBigdataFederation fed, final EventResource resource,
final Object majorEventType, final Map details) {
this(fed, resource, majorEventType, ""/* minorEventType */, details);
}
/**
* Sub-event ctor.
*
* @param fed
* The federation object (used to send the event to an
* aggregator).
* @param resource
* The resource for which the event was generated (store, index
* partition, etc).
* @param majorEventType
* The major type of the event (use of enums is encouraged).
* @param minorEventType
* The minor type of the event (use of enums is encouraged).
* @param details
* Optional details for the event.
*
* @todo consider passing along the {@link UUID} of the parent event but
* then must correlate that {@link UUID} when the event is received.
*/
protected Event(final IBigdataFederation fed, final EventResource resource,
final Object majorEventType, final Object minorEventType,
final Map details) {
if (fed == null)
throw new IllegalArgumentException();
if (resource == null)
throw new IllegalArgumentException();
if (majorEventType == null)
throw new IllegalArgumentException();
if (minorEventType == null)
throw new IllegalArgumentException();
this.fed = fed;
this.eventUUID = UUID.randomUUID();
this.hostname = AbstractStatisticsCollector.fullyQualifiedHostName;
this.serviceIface = this.fed.getServiceIface();
this.serviceName = this.fed.getServiceName();
this.serviceUUID = this.fed.getServiceUUID();
this.resource = resource;
this.majorEventType = majorEventType;
this.minorEventType = minorEventType;
// MAY be null.
if (details != null) {
addDetails(details);
}
/*
* @todo Assignment should be based on a federation configuration
* option.
*
* if global timestamps, then assign startTime.
*
* if auto-assign, then assigned by the recipient.
*
* if local-assign, then use System.currentTimeMillis().
*/
}
/**
* A child event (major type is the type of the parent).
*
* @param minorEventType
*
* @return The sub-event.
*/
public Event newSubEvent(final Object minorEventType) {
return new Event(fed, this.resource, this.majorEventType,
minorEventType, this.details);
}
/**
* A child event (major type is the type of the parent).
*
* @param minorEventType
*
* @param details
* Optional ordered (name,value) array.
*
* @return The sub-event.
*/
public Event newSubEvent(final Object minorEventType,
final Map details) {
final Event e = newSubEvent(minorEventType);
if( details != null ) {
e.addDetails(details);
}
// final Map m;
// if (this.details == null && details == null) {
// m = null;
// } else {
// m = new HashMap();
// if (this.details != null) {
// // details from the parent event.
// m.putAll(this.details);
// }
// if (details != null) {
// // given details.
// m.putAll(details);
// }
// }
return e;
}
/**
* Send the start event.
*
* Note: If you want to report an event with a duration then you MUST use
* {@link #start()} when the event starts and {@link #end()} when the event
* ends. If you want to report an "instantaneous" event then you can just use
* {@link #end()}.
*/
synchronized public Event start() {
if (startTime != 0) {
// start already reported.
throw new IllegalStateException();
}
// assign start time.
startTime = System.currentTimeMillis();
try {
sendEvent();
} catch (Throwable t) {
log.warn(t);
}
return this;
}
/**
* Sends the end event.
*
* Note: You can use this method for "instantaneous" events.
*
* @return The event.
*/
synchronized public Event end() {
if (complete) {
throw new IllegalStateException();
}
complete = true;
endTime = System.currentTimeMillis();
if (startTime == 0L) {
// an "instantenous" event.
startTime = endTime;
}
try {
sendEvent();
} catch (Throwable t) {
log.warn(t);
}
return this;
}
/**
* Dispatch the {@link Event} via
* {@link AbstractFederation#sendEvent(Event)}.
*/
protected void sendEvent() throws IOException {
if(fed instanceof AbstractFederation) {
((AbstractFederation) fed).sendEvent(this);
} else {
if (log.isInfoEnabled())
log.info(this);
}
}
/**
* A header that can be used to interpret the output of {@link #toString()}
* (with newline).
*/
static public String getHeader() {
return "eventUUID"
+ "\tindexName"
+ "\tpartitionId"
+ "\tfile"
+ "\tmajorEventType"
+ "\tmajorEventValue"
+ "\tminorEventType"
+ "\tminorEventValue"
+ "\tstartTime"
+ "\tendTime"
+ "\telapsed"
+ "\tcomplete"
+ "\thostname"
+ "\tserviceIface"
+ "\tserviceName"
+ "\tserviceUUID"
+ "\tdetails"
+ "\n"
;
}
/**
* Tab-delimited format (with newline). The details columns are written in a
* dense format where each (name,value) pair is expressed as
* name=value
, so only the non-null columns are written out.
*/
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(eventUUID); sb.append('\t');
sb.append(resource.indexName); sb.append('\t');
sb.append(resource.partitionId); sb.append('\t');
sb.append(resource.file); sb.append('\t');
sb.append(majorEventType.getClass().getName()); sb.append('\t');
sb.append(majorEventType); sb.append('\t');
sb.append(minorEventType.getClass().getName()); sb.append('\t');
sb.append(minorEventType); sb.append('\t');
sb.append(startTime); sb.append('\t');
sb.append(endTime); sb.append('\t');
if (complete) {
sb.append(endTime - startTime);
sb.append('\t');
} else {
sb.append('\t');
}
sb.append(complete); sb.append('\t');
sb.append(hostname); sb.append('\t');
sb.append(serviceIface.getName()); sb.append('\t');
sb.append(serviceName); sb.append('\t');
sb.append(serviceUUID);
if (details != null) {
final String[] keys = details.keySet().toArray(new String[]{});
Arrays.sort(keys);
for (String s : keys) {
sb.append('\t');
sb.append(s);
sb.append("=");
sb.append("" + details.get(s));
}
// for (Map.Entry entry : details.entrySet()) {
// sb.append('\t');
// sb.append(entry.getKey());
// sb.append("=");
// sb.append("" + entry.getValue());
// }
}
sb.append('\n');
return sb.toString();
}
// /**
// * A class that knows how to render events in a stable tab-delimited format.
// * Known detail columns are predeclared and will be written out in the order
// * in which they were declared. Additional detail columns are recognized the
// * first time they are used and will always be written out at the column
// * index that they were assigned. This makes it possible to reimport the
// * events data into a worksheet for processing.
// *
// * @author Bryan Thompson
// * @version $Id$
// */
// public static class EventRenderer {
//
// public void declareColumn(String) {
//
// }
// public String render(Event e) {
//
// }
//
// }
//
// /**
// * Class which parses events written by the {@link EventRenderer}.
// *
// * @todo we can't really reverse the {@link EventRenderer} when there are
// * columns that are not predeclared since their values will never be
// * written out.
// *
// * @author Bryan Thompson
// * @version $Id$
// */
// public static class EventParser {
//
// public Event parseEvent(String s) {
//
// }
//
// }
/**
* Construct an event from the tab-delimited serialization produced from
* {@link #toString()}.
*
* @param s
* the tab delimited serialization
*
* @throws ClassNotFoundException
* if any fields specify an invalid classname
*/
protected Event(final String s) throws ClassNotFoundException {
// System.err.println(s);
final MyStringTokenizer st = new MyStringTokenizer(s, "\t");
try {
this.eventUUID = UUID.fromString(st.nextToken());
// System.err.println("eventUUID: <"+eventUUID+">");
String resourceIndexName = st.nextToken();
// System.err.println("resource.indexName: <"+resourceIndexName+">");
String resourcePartitionId = st.nextToken();
// System.err.println("resource.partitionId: <"+resourcePartitionId+">");
String resourceFile = st.nextToken();
// System.err.println("resource.file: <"+resourceFile+">");
this.resource = new EventResource(resourceIndexName, resourcePartitionId, resourceFile);
String majorEventClass = st.nextToken();
// System.err.println("majorEventClass: <"+majorEventClass+">");
if (majorEventClass != null) {
Class majorEventType = Class.forName(majorEventClass);
String majorEventValue = st.nextToken();
// System.err.println("majorEventValue: <"+majorEventValue+">");
if (majorEventValue == null) {
majorEventValue = "";
}
if (majorEventType.equals(String.class)) {
this.majorEventType = majorEventValue;
} else if (majorEventType.isEnum()) {
this.majorEventType = Enum.valueOf(majorEventType, majorEventValue);
} else {
throw new UnsupportedOperationException();
}
} else {
this.majorEventType = "";
}
String minorEventClass = st.nextToken();
// System.err.println("minorEventClass: <"+minorEventClass+">");
if (minorEventClass != null) {
Class minorEventType = Class.forName(minorEventClass);
String minorEventValue = st.nextToken();
// System.err.println("minorEventValue: <"+minorEventValue+">");
if (minorEventValue == null) {
minorEventValue = "";
}
if (minorEventType.equals(String.class)) {
this.minorEventType = minorEventValue;
} else if (minorEventType.isEnum()) {
this.minorEventType = Enum.valueOf(minorEventType, minorEventValue);
} else {
throw new UnsupportedOperationException();
}
} else {
this.minorEventType = "";
}
String startTime = st.nextToken();
// System.err.println("startTime: <"+startTime+">");
this.startTime = Long.valueOf(startTime);
String endTime = st.nextToken();
// System.err.println("endTime: <"+endTime+">");
this.endTime = Long.valueOf(endTime);
// do nothing with this one
String elapsed = st.nextToken();
this.complete = Boolean.valueOf(st.nextToken());
this.hostname = st.nextToken();
String serviceIfaceName = st.nextToken();
this.serviceIface = Class.forName(serviceIfaceName);
this.serviceName = st.nextToken();
this.serviceUUID = UUID.fromString(st.nextToken());
this.details = newDetails();
while(st.hasMoreTokens()) {
final String t = st.nextToken();
final int pos = t.indexOf('=');
final String name = t.substring(0,pos);
final String value = t.substring(pos+1,t.length());
this.details.put(name,value);
}
} catch(Throwable t) {
throw new RuntimeException("At field: " + st.getCurrentFieldIndex()
+ " of " + st.getFieldCount() + " : " + t, t);
}
}
/**
* Reconstruct an event object from a string. Useful for post-mortem
* analysis.
*
* @param s the tab-delimited format create by {@link #toString()}.
* @return a new event object
* @throws ClassNotFoundException
* if any CSV fields specify an invalid classname
*/
public static Event fromString(String s) throws ClassNotFoundException {
return new Event(s);
}
/**
* Dissimilar from the regular StringTokenizer, which is cannot handle
* empty fields.
*
* @author Mike Personick
*/
private static class MyStringTokenizer {
private final List tokens;
private final String delim;
private int i;
public MyStringTokenizer(final String s, final String delim) {
this.tokens = new ArrayList(20);
final StringTokenizer st = new StringTokenizer(s, delim, true);
while (st.hasMoreTokens()) {
tokens.add(st.nextToken());
}
this.delim = delim;
this.i = 0;
}
/**
* The #of fields that were extracted.
*/
public int getFieldCount() {
return tokens.size();
}
/**
* The index of the last field returned in [0:nfields-1]
*/
public int getCurrentFieldIndex() {
return i;
}
public boolean hasMoreTokens() {
return i < tokens.size() &&
!(i == tokens.size()-1 && delim.contains(tokens.get(tokens.size()-1)));
}
public String nextToken() {
String token = tokens.get(i++);
if (delim.contains(token)) {
// handles a blank column
token = tokens.get(i);
if (delim.contains(token)) {
token = BLANK;
} else {
i++;
}
}
if (token != null) {
token = token.trim();
if (token.length() == 0) {
// handles a column of whitespace.
token = BLANK;
}
}
return token;
}
}
static final String BLANK = "";
}