/***************************************************************************
* Copyright (C) 2009 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
package edu.brown.workload;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections15.OrderedMap;
import org.apache.commons.collections15.map.LinkedMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
import org.voltdb.VoltType;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.ProcParameter;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Statement;
import edu.brown.catalog.CatalogUtil;
import edu.brown.utils.ClassUtil;
import edu.brown.utils.StringUtil;
/**
*
* @author Andy Pavlo <pavlo@cs.brown.edu>
*
*/
public class TransactionTrace extends AbstractTraceElement<Procedure> {
public enum Members {
TXN_ID,
QUERIES
};
private long txn_id;
private List<QueryTrace> queries = new ArrayList<QueryTrace>();
private transient LinkedMap<Integer, List<QueryTrace>> query_batches = new LinkedMap<Integer, List<QueryTrace>>();
public TransactionTrace() {
super();
}
private TransactionTrace(long xact_id, String proc_name, Object params[]) {
super(proc_name, params);
this.txn_id = xact_id;
}
public TransactionTrace(long xact_id, Procedure catalog_proc, Object params[]) {
this(xact_id, catalog_proc.getName(), params);
}
@SuppressWarnings("unchecked")
@Override
protected TransactionTrace cloneImpl() {
TransactionTrace clone = new TransactionTrace(this.txn_id, this.catalog_item_name, this.params);
clone.queries.clear();
clone.query_batches.clear();
for (QueryTrace qt : this.queries) {
QueryTrace clone_q = (QueryTrace)qt.clone();
clone.addQuery(clone_q);
} // FOR
return (clone);
}
@Override
public String toString() {
String ret = String.format("%s[%s:#%d]",
this.getClass().getSimpleName(),
this.catalog_item_name,
this.txn_id);
if (this.getWeight() > 1) {
ret += " - Weight:" + this.getWeight();
}
return (ret);
}
/**
* Change this TransactionTrace's txn id. Should only be used for testing
* @param txn_id
*/
public void setTransactionId(long txn_id) {
this.txn_id = txn_id;
}
/**
* Return the TransactionId for this TransactionTrace
* @return the xact_id
*/
public long getTransactionId() {
return this.txn_id;
}
@Override
public Procedure getCatalogItem(Database catalog_db) {
assert(catalog_db != null);
return (catalog_db.getProcedures().get(this.catalog_item_name));
}
/**
* Set the given list of queries as the queries executed by this transaction
* This should only be used for testing
* @param queries
*/
public void setQueries(Collection<QueryTrace> queries) {
this.queries.clear();
this.query_batches.clear();
for (QueryTrace q : queries) {
this.addQuery(q);
} // FOR
}
public void addQuery(QueryTrace query) {
this.queries.add(query);
if (!this.query_batches.containsKey(query.getBatchId())) {
this.query_batches.put(query.getBatchId(), new ArrayList<QueryTrace>());
}
this.query_batches.get((Integer)query.getBatchId()).add(query);
}
@Override
public String debug(Database catalog_db) {
final Procedure catalog_proc = this.getCatalogItem(catalog_db);
final String thick_line = StringUtil.DOUBLE_LINE;
final String thin_line = StringUtil.SINGLE_LINE;
// Header Info
StringBuilder sb = new StringBuilder();
sb.append(thick_line)
.append(catalog_proc.getName().toUpperCase() + " - Txn#" + this.txn_id + "\n")
.append("Start Time: " + this.start_timestamp + "\n")
.append("Stop Time: " + this.stop_timestamp + "\n")
.append("Run Time: " + (this.stop_timestamp != null ? this.stop_timestamp - this.start_timestamp : "???") + "\n")
.append("Txn Aborted: " + this.aborted + "\n")
.append("Weight: " + this.weight + "\n")
.append("# of Queries: " + this.queries.size() + "\n")
.append("# of Batches: " + this.query_batches.size() + "\n");
// Params
sb.append("Transasction Parameters: [" + this.params.length + "]\n");
for (int i = 0; i < this.params.length; i++) {
ProcParameter catalog_param = catalog_proc.getParameters().get(i);
Object param = this.params[i];
String type_name = VoltType.get(catalog_param.getType()).name();
if (ClassUtil.isArray(param)) type_name += "[" + ((Object[])param).length + "]";
sb.append(" [" + i + "] -> ")
.append(String.format("%-11s ", "(" + type_name + ")"))
.append(ClassUtil.isArray(param) ? Arrays.toString((Object[])param) : param)
.append("\n");
} // FOR
// Queries
sb.append(thin_line);
int ctr = 0;
for (Integer batch_id : this.query_batches.keySet()) {
sb.append("Batch #" + batch_id + " (" + this.query_batches.get(batch_id).size() + ")\n");
for (QueryTrace query : this.query_batches.get(batch_id)) {
sb.append(" [" + (ctr++) + "] " + query.debug(catalog_db) + "\n");
} // FOR
} // FOR
sb.append(thin_line);
return (sb.toString());
}
public Map<Statement, Integer> getStatementCounts(Database catalog_db) {
Map<Statement, Integer> counts = new HashMap<Statement, Integer>();
Procedure catalog_proc = this.getCatalogItem(catalog_db);
for (Statement stmt : catalog_proc.getStatements()) {
counts.put(stmt, 0);
}
for (QueryTrace query : this.queries) {
Statement stmt = query.getCatalogItem(catalog_db);
assert(stmt != null) : "Invalid query name '" + query.getCatalogItemName() + "' for " + catalog_proc;
assert(counts.containsKey(stmt)) : "Unexpected " + CatalogUtil.getDisplayName(stmt) + " in " + catalog_proc;
counts.put(stmt, counts.get(stmt) + 1);
}
return (counts);
}
/**
* @return the queries
*/
public List<QueryTrace> getQueries() {
return this.queries;
}
public int getQueryCount() {
return (this.queries.size());
}
public int getWeightedQueryCount() {
int ctr = 0;
for (QueryTrace qt : this.queries) {
ctr += qt.getWeight();
}
return (ctr);
}
public QueryTrace getQuery(int idx) {
return (this.queries.get(idx));
}
/**
* Returns the number of batches in this transaction
* @return
*/
public int getBatchCount() {
return (this.query_batches.size());
}
/**
* Returns an ordered set of query batch ids for this Transaction
* @return
*/
public List<Integer> getBatchIds() {
return (this.query_batches.asList());
}
/**
* Return a mapping of batch ids to a list of QueryTrace elements
* @return
*/
public OrderedMap<Integer, List<QueryTrace>> getBatches() {
return (this.query_batches);
}
/**
* Return the list of queries in the given batch
* @param batch_id
* @return
*/
public List<QueryTrace> getBatchQueries(int batch_id) {
return (this.query_batches.getValue(batch_id));
}
public void toJSONString(JSONStringer stringer, Database catalog_db) throws JSONException {
super.toJSONString(stringer, catalog_db);
stringer.key(Members.TXN_ID.name()).value(this.txn_id);
stringer.key(Members.QUERIES.name()).array();
for (QueryTrace query : this.queries) {
stringer.object();
query.toJSONString(stringer, catalog_db);
stringer.endObject();
} // FOR
stringer.endArray();
}
@Override
protected void fromJSONObject(JSONObject object, Database db) throws JSONException {
super.fromJSONObject(object, db);
this.txn_id = object.getLong(Members.TXN_ID.name());
Procedure catalog_proc = (Procedure)db.getProcedures().get(this.catalog_item_name);
assert(catalog_proc != null) : "Unexpected procedure '" + this.catalog_item_name + "'";
try {
super.paramsFromJSONObject(object, catalog_proc.getParameters(), "type");
} catch (Exception ex) {
LOG.fatal("Failed to extract procedure params for txn #" + this.txn_id, ex);
throw new JSONException(ex);
}
JSONArray jsonQueries = object.getJSONArray(Members.QUERIES.name());
for (int i = 0; i < jsonQueries.length(); i++) {
JSONObject jsonQuery = jsonQueries.getJSONObject(i);
if (jsonQuery.isNull(AbstractTraceElement.Members.NAME.name())) {
LOG.warn("The catalog name is null for Query #" + i + " in " + this + ". Ignoring...");
continue;
}
try {
QueryTrace query = QueryTrace.loadFromJSONObject(jsonQuery, catalog_proc);
this.addQuery(query);
} catch (JSONException ex) {
throw new RuntimeException("Failed to load query trace #" + i + " for transaction record on " + this.catalog_item_name + "]", ex);
}
} // FOR
}
public static TransactionTrace loadFromJSONObject(JSONObject object, Database db) throws JSONException {
TransactionTrace xact = new TransactionTrace();
xact.fromJSONObject(object, db);
return (xact);
}
} // END CLASS