blob: 5d8d84485486a587d331d13c0a72d30c1dabda88 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.phoenix.trace;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.htrace.Span;
import org.apache.htrace.Trace;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.metrics.MetricInfo;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.query.QueryServicesOptions;
import org.apache.phoenix.util.LogUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.primitives.Longs;
/**
* Read the traces written to phoenix tables by the {@link TraceWriter}.
*/
public class TraceReader {
private static final Logger LOGGER = LoggerFactory.getLogger(TraceReader.class);
private final Joiner comma = Joiner.on(',');
private String knownColumns;
{
// the order here dictates the order we pull out the values below. For now, just keep them
// in sync - so we can be efficient pulling them off the results.
knownColumns =
comma.join(MetricInfo.TRACE.columnName, MetricInfo.PARENT.columnName,
MetricInfo.SPAN.columnName, MetricInfo.DESCRIPTION.columnName,
MetricInfo.START.columnName, MetricInfo.END.columnName,
MetricInfo.HOSTNAME.columnName, TraceWriter.TAG_COUNT,
TraceWriter.ANNOTATION_COUNT);
}
private Connection conn;
private String table;
private int pageSize;
public TraceReader(Connection conn, String tracingTableName) throws SQLException {
this.conn = conn;
this.table = tracingTableName;
String ps = conn.getClientInfo(QueryServices.TRACING_PAGE_SIZE_ATTRIB);
this.pageSize = ps == null ? QueryServicesOptions.DEFAULT_TRACING_PAGE_SIZE : Integer.parseInt(ps);
}
/**
* Read all the currently stored traces.
* <p>
* <b>Be Careful!</b> This could cause an OOME if there are a lot of traces.
* @param limit max number of traces to return. If -1, returns all known traces.
* @return the found traces
* @throws SQLException
*/
public Collection<TraceHolder> readAll(int limit) throws SQLException {
Set<TraceHolder> traces = new HashSet<TraceHolder>();
// read all the known columns from the table, sorting first by trace column (so the same
// trace
// goes together), and then by start time (so parent spans always appear before child spans)
String query =
"SELECT " + knownColumns + " FROM " + table
+ " ORDER BY " + MetricInfo.TRACE.columnName + " DESC, "
+ MetricInfo.START.columnName + " ASC" + " LIMIT " + pageSize;
int resultCount = 0;
ResultSet results = conn.prepareStatement(query).executeQuery();
TraceHolder trace = null;
// the spans that are not the root span, but haven't seen their parent yet
List<SpanInfo> orphans = null;
while (results.next()) {
int index = 1;
long traceid = results.getLong(index++);
long parent = results.getLong(index++);
long span = results.getLong(index++);
String desc = results.getString(index++);
long start = results.getLong(index++);
long end = results.getLong(index++);
String host = results.getString(index++);
int tagCount = results.getInt(index++);
int annotationCount = results.getInt(index++);
// we have a new trace
if (trace == null || traceid != trace.traceid) {
// only increment if we are on a new trace, to ensure we get at least one
if (trace != null) {
resultCount++;
}
// we beyond the limit, so we stop
if (resultCount >= limit) {
break;
}
trace = new TraceHolder();
// add the orphans, so we can track them later
orphans = new ArrayList<SpanInfo>();
trace.orphans = orphans;
trace.traceid = traceid;
traces.add(trace);
}
// search the spans to determine the if we have a known parent
SpanInfo parentSpan = null;
if (parent != Span.ROOT_SPAN_ID) {
// find the parent
for (SpanInfo p : trace.spans) {
if (p.id == parent) {
parentSpan = p;
break;
}
}
}
SpanInfo spanInfo =
new SpanInfo(parentSpan, parent, span, desc, start, end, host, tagCount,
annotationCount);
// search the orphans to see if this is the parent id
for (int i = 0; i < orphans.size(); i++) {
SpanInfo orphan = orphans.get(i);
// we found the parent for the orphan
if (orphan.parentId == span) {
// update the bi-directional relationship
orphan.parent = spanInfo;
spanInfo.children.add(orphan);
// / its no longer an orphan
LOGGER.trace(addCustomAnnotations("Found parent for span: " + span));
orphans.remove(i--);
}
}
if (parentSpan != null) {
// add this as a child to the parent span
parentSpan.children.add(spanInfo);
} else if (parent != Span.ROOT_SPAN_ID) {
// add the span to the orphan pile to check for the remaining spans we see
LOGGER.info(addCustomAnnotations("No parent span found for span: " + span + " (root span id: "
+ Span.ROOT_SPAN_ID + ")"));
orphans.add(spanInfo);
}
// add the span to the full known list
trace.spans.add(spanInfo);
// go back and find the tags for the row
spanInfo.tags.addAll(getTags(traceid, parent, span, tagCount));
spanInfo.annotations.addAll(getAnnotations(traceid, parent, span, annotationCount));
}
// make sure we clean up after ourselves
results.close();
return traces;
}
private Collection<? extends String> getTags(long traceid, long parent, long span, int count)
throws SQLException {
return getDynamicCountColumns(traceid, parent, span, count,
TraceWriter.TAG_FAMILY, MetricInfo.TAG.columnName);
}
private Collection<? extends String> getAnnotations(long traceid, long parent, long span,
int count) throws SQLException {
return getDynamicCountColumns(traceid, parent, span, count,
TraceWriter.ANNOTATION_FAMILY, MetricInfo.ANNOTATION.columnName);
}
private Collection<? extends String> getDynamicCountColumns(long traceid, long parent,
long span, int count, String family, String columnName) throws SQLException {
if (count == 0) {
return Collections.emptyList();
}
// build the column strings, family.column<index>
String[] parts = new String[count];
for (int i = 0; i < count; i++) {
parts[i] = TraceWriter.getDynamicColumnName(family, columnName, i);
}
// join the columns together
String columns = comma.join(parts);
// redo them and add "VARCHAR to the end, so we can specify the columns
for (int i = 0; i < count; i++) {
parts[i] = parts[i] + " VARCHAR";
}
String dynamicColumns = comma.join(parts);
String request =
"SELECT " + columns + " from " + table + "(" + dynamicColumns + ") WHERE "
+ MetricInfo.TRACE.columnName + "=" + traceid + " AND "
+ MetricInfo.PARENT.columnName + "=" + parent + " AND "
+ MetricInfo.SPAN.columnName + "=" + span;
LOGGER.trace(addCustomAnnotations("Requesting columns with: " + request));
ResultSet results = conn.createStatement().executeQuery(request);
List<String> cols = new ArrayList<String>();
while (results.next()) {
for (int index = 1; index <= count; index++) {
cols.add(results.getString(index));
}
}
if (cols.size() < count) {
LOGGER.error(addCustomAnnotations("Missing tags! Expected " + count +
", but only got " + cols.size() + " tags from rquest " + request));
}
return cols;
}
private String addCustomAnnotations(String logLine) throws SQLException {
if (conn.isWrapperFor(PhoenixConnection.class)) {
PhoenixConnection phxConn = conn.unwrap(PhoenixConnection.class);
logLine = LogUtil.addCustomAnnotations(logLine, phxConn);
}
return logLine;
}
/**
* Holds information about a trace
*/
public static class TraceHolder {
public List<SpanInfo> orphans;
public long traceid;
public TreeSet<SpanInfo> spans = new TreeSet<SpanInfo>();
@Override
public int hashCode() {
return new Long(traceid).hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof TraceHolder) {
return traceid == ((TraceHolder) o).traceid;
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Trace: " + traceid + "\n");
// get the first span, which is always going to be the root span
SpanInfo root = spans.iterator().next();
if (root.parent != null) {
sb.append("Root span not present! Just printing found spans\n");
for (SpanInfo span : spans) {
sb.append(span.toString() + "\n");
}
} else {
// print the tree of spans
List<SpanInfo> toPrint = new ArrayList<SpanInfo>();
toPrint.add(root);
while (!toPrint.isEmpty()) {
SpanInfo span = toPrint.remove(0);
sb.append(span.toString() + "\n");
toPrint.addAll(span.children);
}
}
if (orphans.size() > 0) {
sb.append("Found orphan spans:\n" + orphans);
}
return sb.toString();
}
}
public static class SpanInfo implements Comparable<SpanInfo> {
public SpanInfo parent;
public List<SpanInfo> children = new ArrayList<SpanInfo>();
public String description;
public long id;
public long start;
public long end;
public String hostname;
public int tagCount;
public List<String> tags = new ArrayList<String>();
public int annotationCount;
public List<String> annotations = new ArrayList<String>();
private long parentId;
public SpanInfo(SpanInfo parent, long parentid, long span, String desc, long start,
long end, String host, int tagCount, int annotationCount) {
this.parent = parent;
this.parentId = parentid;
this.id = span;
this.description = desc;
this.start = start;
this.end = end;
this.hostname = host;
this.tagCount = tagCount;
this.annotationCount = annotationCount;
}
@Override
public int hashCode() {
return new Long(id).hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof SpanInfo) {
return id == ((SpanInfo) o).id;
}
return false;
}
/**
* Do the same sorting that we would get from reading the table with a {@link TraceReader},
* specifically, by trace and then by start/end. However, these are only every stored in a
* single trace, so we can just sort on start/end times.
*/
@Override
public int compareTo(SpanInfo o) {
// root span always comes first
if (this.parentId == Span.ROOT_SPAN_ID) {
return -1;
} else if (o.parentId == Span.ROOT_SPAN_ID) {
return 1;
}
int compare = Longs.compare(start, o.start);
if (compare == 0) {
compare = Longs.compare(end, o.end);
if (compare == 0) {
return Longs.compare(id, o.id);
}
}
return compare;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Span: " + id + "\n");
sb.append("\tdescription=" + description);
sb.append("\n");
sb.append("\tparent="
+ (parent == null ? (parentId == Span.ROOT_SPAN_ID ? "ROOT" : "[orphan - id: "
+ parentId + "]") : parent.id));
sb.append("\n");
sb.append("\tstart,end=" + start + "," + end);
sb.append("\n");
sb.append("\telapsed=" + (end - start));
sb.append("\n");
sb.append("\thostname=" + hostname);
sb.append("\n");
sb.append("\ttags=(" + tagCount + ") " + tags);
sb.append("\n");
sb.append("\tannotations=(" + annotationCount + ") " + annotations);
sb.append("\n");
sb.append("\tchildren=");
for (SpanInfo child : children) {
sb.append(child.id + ", ");
}
sb.append("\n");
return sb.toString();
}
public long getParentIdForTesting() {
return parentId;
}
}
}