package org.voltdb; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.voltdb.catalog.Statement; import org.voltdb.messaging.FastDeserializer; import org.voltdb.messaging.FastSerializable; import org.voltdb.messaging.FastSerializer; import org.voltdb.utils.Pair; import edu.brown.catalog.special.CountedStatement; import edu.brown.hstore.estimators.EstimatorState; import edu.brown.hstore.estimators.Estimate; import edu.brown.hstore.txns.LocalTransaction; import edu.brown.utils.PartitionSet; import edu.brown.utils.StringUtil; /** * Special wrapper object that contains extra information about a transaction */ public class ClientResponseDebug implements FastSerializable { /** * Inner class that converts CountedStatements into ids+counter, since * we won't have the catalog when we deserialize it on the client side */ private class QueryEstimate implements FastSerializable { /** * Pair -> StatementId : StatementCounter */ private final List<Pair<Integer, Integer>> statements = new ArrayList<Pair<Integer, Integer>>(); private int partition; public QueryEstimate() { // Nothing to do } public QueryEstimate(int partition, Collection<CountedStatement> statements) { this.partition = partition; for (CountedStatement cntStmt : statements) { this.statements.add(Pair.of(cntStmt.statement.getId(), cntStmt.counter)); } // FOR } @Override public void readExternal(FastDeserializer in) throws IOException { this.partition = in.readInt(); int num_statements = in.readShort(); this.statements.clear(); for (int i = 0; i < num_statements; i++) { int stmtId = in.readInt(); int stmtCtr = in.readShort(); this.statements.add(Pair.of(stmtId, stmtCtr)); } // FOR assert(num_statements == this.statements.size()); } @Override public void writeExternal(FastSerializer out) throws IOException { out.writeInt(this.partition); out.writeShort(this.statements.size()); for (Pair<Integer, Integer> stmt : this.statements) { out.writeInt(stmt.getFirst()); out.writeShort(stmt.getSecond()); } // FOR } @Override public String toString() { return String.format("{Partition #%02d - %s}", this.partition, this.statements); } } // CLASS private boolean predict_singlePartition; private boolean predict_abortable; private boolean predict_readOnly; private final PartitionSet predict_touchedPartitions = new PartitionSet(); private String estimateType; private int estimateUpdateCount = 0; /** * Partition -> List<QueryEstimate> */ private final Map<Integer, List<QueryEstimate>> estimateRemoteQueryUpdates = new TreeMap<Integer, List<QueryEstimate>>(); /** * Partitions that were marked for early 2PC */ private final PartitionSet early_preparePartitions = new PartitionSet(); private boolean prefetched = false; private final PartitionSet exec_touchedPartitions = new PartitionSet(); // ---------------------------------------------------------------------------- // INITIALIZATION // ---------------------------------------------------------------------------- public ClientResponseDebug() { // Needed for serialization } public ClientResponseDebug(LocalTransaction ts) { this.predict_singlePartition = ts.isPredictSinglePartition(); this.predict_abortable = ts.isPredictAbortable(); this.predict_readOnly = ts.isPredictReadOnly(); this.predict_touchedPartitions.addAll(ts.getPredictTouchedPartitions()); this.prefetched = ts.hasPrefetchQueries(); this.exec_touchedPartitions.addAll(ts.getTouchedPartitions().values()); if (this.predict_singlePartition == false) { this.early_preparePartitions.addAll(ts.getDonePartitions()); } EstimatorState t_state = ts.getEstimatorState(); if (t_state != null) { this.estimateType = t_state.getClass().getSimpleName(); for (Estimate est : t_state.getEstimates()) { this.estimateUpdateCount++; for (int partition : this.exec_touchedPartitions) { if (est.hasQueryEstimate(partition)) { Collection<CountedStatement> stmts = est.getQueryEstimate(partition); this.addQueryEstimate(new QueryEstimate(partition, stmts)); } } // FOR (partition) } // FOR (estimate) } } protected void addQueryEstimate(QueryEstimate query_estimate) { List<QueryEstimate> estimates = this.estimateRemoteQueryUpdates.get(query_estimate.partition); if (estimates == null) { estimates = new ArrayList<QueryEstimate>(); this.estimateRemoteQueryUpdates.put(query_estimate.partition, estimates); } estimates.add(query_estimate); } // ---------------------------------------------------------------------------- // DATA METHODS // ---------------------------------------------------------------------------- public boolean isPredictSinglePartition() { return this.predict_singlePartition; } public boolean isPredictAbortable() { return this.predict_abortable; } public boolean isPredictReadOnly() { return this.predict_readOnly; } public PartitionSet getPredictTouchedPartitions() { return this.predict_touchedPartitions; } public PartitionSet getExecTouchedPartitions() { return this.exec_touchedPartitions; } /** * Get the set of partitions that were marked for early 2PC prepare * @return */ public PartitionSet getEarlyPreparePartitions() { return this.early_preparePartitions; } /** * Returns true if this transaction was executed with prefetched queries. * @return */ public boolean hadPrefetchedQueries() { return this.prefetched; } public String getEstimatorType() { return (this.estimateType); } public int getEstimatorUpdateCount() { return (this.estimateUpdateCount); } public List<CountedStatement>[] getRemoteQueryEstimates(CatalogContext catalogContext, int partition) { List<QueryEstimate> estimates = this.estimateRemoteQueryUpdates.get(partition); int num_estimates = (estimates != null ? estimates.size() : 0); @SuppressWarnings("unchecked") List<CountedStatement> result[] = (List<CountedStatement>[])new List<?>[num_estimates]; for (int i = 0; i < result.length; i++) { result[i] = new ArrayList<CountedStatement>(); QueryEstimate query_est = estimates.get(i); for (Pair<Integer, Integer> p : query_est.statements) { Statement stmt = catalogContext.getStatementById(p.getFirst()); assert(stmt != null) : "Invalid Statement id '" + p.getFirst() + "'"; result[i].add(new CountedStatement(stmt, p.getSecond())); } // FOR } // FOR return (result); } // ---------------------------------------------------------------------------- // SERIALIZATION METHODS // ---------------------------------------------------------------------------- @Override public void readExternal(FastDeserializer in) throws IOException { this.predict_singlePartition = in.readBoolean(); this.predict_abortable = in.readBoolean(); this.predict_readOnly = in.readBoolean(); this.predict_touchedPartitions.readExternal(in); this.early_preparePartitions.readExternal(in); this.prefetched = in.readBoolean(); this.exec_touchedPartitions.readExternal(in); // TXN ESTIMATE INFO this.estimateType = in.readString(); this.estimateUpdateCount = in.readInt(); // QUERY ESTIMATES int num_partitions = in.readShort(); for (int i = 0; i < num_partitions; i++) { int partition = in.readInt(); int num_estimates = in.readShort(); for (int j = 0; j < num_estimates; j++) { QueryEstimate query_est = new QueryEstimate(); query_est.readExternal(in); assert(partition == query_est.partition); this.addQueryEstimate(query_est); } // FOR } // FOR } @Override public void writeExternal(FastSerializer out) throws IOException { out.writeBoolean(this.predict_singlePartition); out.writeBoolean(this.predict_abortable); out.writeBoolean(this.predict_readOnly); this.predict_touchedPartitions.writeExternal(out); this.early_preparePartitions.writeExternal(out); out.writeBoolean(this.prefetched); this.exec_touchedPartitions.writeExternal(out); // TXN ESTIMATE INFO out.writeString(this.estimateType); out.writeInt(this.estimateUpdateCount); // QUERY ESTIMATES out.writeShort(this.estimateRemoteQueryUpdates.size()); for (Entry<Integer, List<QueryEstimate>> e : this.estimateRemoteQueryUpdates.entrySet()) { out.writeInt(e.getKey()); out.writeShort(e.getValue().size()); for (QueryEstimate query_est : e.getValue()) { query_est.writeExternal(out); } // FOR } // FOR } @Override public String toString() { List<Map<String, Object>> maps = new ArrayList<Map<String,Object>>(); Map<String, Object> m; m = new LinkedHashMap<String, Object>(); m.put("Predict Single-Partitioned", this.predict_singlePartition); m.put("Predict Touched Partitions", this.predict_touchedPartitions); m.put("Predict Read Only", this.predict_readOnly); m.put("Predict Abortable", this.predict_abortable); m.put("Had Prefetched Queries", this.prefetched); maps.add(m); m = new LinkedHashMap<String, Object>(); m.put("Estimator Type", this.estimateType); m.put("Estimate Updates", this.estimateUpdateCount); m.put("Remote Query Estimates", this.estimateRemoteQueryUpdates); maps.add(m); m = new LinkedHashMap<String, Object>(); m.put("Exec Touched Partitions", this.exec_touchedPartitions); m.put("Early 2PC Partitions", this.early_preparePartitions); maps.add(m); return StringUtil.formatMaps(maps.toArray(new Map<?, ?>[maps.size()])); } }