http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/main/java/org/apache/htrace/core/TraceScope.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/main/java/org/apache/htrace/core/TraceScope.java b/htrace-core4/src/main/java/org/apache/htrace/core/TraceScope.java new file mode 100644 index 0000000..05a053e --- /dev/null +++ b/htrace-core4/src/main/java/org/apache/htrace/core/TraceScope.java @@ -0,0 +1,128 @@ +/* + * 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.htrace.core; + +import java.io.Closeable; + +/** + * Create a new TraceScope at major transitions. Hosts current tracing context. + */ +public class TraceScope implements Closeable { + /** + * The tracer to use for this scope. + */ + final Tracer tracer; + + /** + * The trace span for this scope, or null if the scope is closed. + * + * If the scope is closed, it must also be detached. + */ + private final Span span; + + /** + * The parent of this trace scope, or null if there is no parent. + */ + private TraceScope parent; + + /** + * True if this scope is detached. + */ + boolean detached; + + TraceScope(Tracer tracer, Span span, TraceScope parent) { + this.tracer = tracer; + this.span = span; + this.parent = parent; + this.detached = false; + } + + /** + * Returns the span which this scope is managing. + */ + public Span getSpan() { + return span; + } + + /** + * Returns the span ID which this scope is managing. + */ + public SpanId getSpanId() { + return span.getSpanId(); + } + + TraceScope getParent() { + return parent; + } + + void setParent(TraceScope parent) { + this.parent = parent; + } + + /** + * Detach this TraceScope from the current thread. + * + * It is OK to "leak" TraceScopes which have been detached. They will not + * consume any resources other than a small amount of memory until they are + * garbage collected. On the other hand, trace scopes which are still + * attached must never be leaked. + */ + public void detach() { + if (detached) { + Tracer.throwClientError("Can't detach this TraceScope because " + + "it is already detached."); + } + tracer.detachScope(this); + detached = true; + parent = null; + } + + /** + * Attach this TraceScope to the current thread. + */ + public void reattach() { + if (!detached) { + Tracer.throwClientError("Can't reattach this TraceScope because " + + "it is not detached."); + } + tracer.reattachScope(this); + detached = false; + } + + /** + * Close this TraceScope, ending the trace span it is managing. + */ + @Override + public void close() { + tracer.closeScope(this); + } + + public void addKVAnnotation(String key, String value) { + span.addKVAnnotation(key, value); + } + + public void addTimelineAnnotation(String msg) { + span.addTimelineAnnotation(msg); + } + + @Override + public String toString() { + return "TraceScope(tracerId=" + tracer.getTracerId() + + ", span=" + span.toJson() + + ", detached=" + detached + ")"; + } +}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/main/java/org/apache/htrace/core/Tracer.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/main/java/org/apache/htrace/core/Tracer.java b/htrace-core4/src/main/java/org/apache/htrace/core/Tracer.java new file mode 100644 index 0000000..39d972d --- /dev/null +++ b/htrace-core4/src/main/java/org/apache/htrace/core/Tracer.java @@ -0,0 +1,673 @@ +/* + * 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.htrace.core; + +import java.io.Closeable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Use a Tracer instance inside a 'process' to collect and distribute its trace Spans. + * Example processes are an HDFS DataNode or an HBase RegionServer. A Tracer instance is your + * one-stop shop for all things tracing. + * + * <p> + */ +public class Tracer implements Closeable { + private static final Log LOG = LogFactory.getLog(Tracer.class); + + public final static String SPAN_RECEIVER_CLASSES_KEY = "span.receiver.classes"; + public final static String SAMPLER_CLASSES_KEY = "sampler.classes"; + + public static class Builder { + private String name; + private HTraceConfiguration conf = HTraceConfiguration.EMPTY; + private ClassLoader classLoader = + Builder.class.getClassLoader(); + private TracerPool tracerPool = TracerPool.GLOBAL; + + /** + * @deprecated Since 4.0.0. Use Constructor that takes a <code>name</code> argument instead + */ + @Deprecated + public Builder() { + } + + public Builder(final String name) { + name(name); + } + + /** + * @param name + * @return This + * @deprecated Since 4.0.0. Use Constructor that takes a <code>name</code> argument instead. + */ + @Deprecated + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder conf(HTraceConfiguration conf) { + this.conf = conf; + return this; + } + + public Builder tracerPool(TracerPool tracerPool) { + this.tracerPool = tracerPool; + return this; + } + + private void loadSamplers(List<Sampler> samplers) { + String classNamesStr = conf.get(SAMPLER_CLASSES_KEY, ""); + List<String> classNames = getClassNamesFromConf(classNamesStr); + StringBuilder bld = new StringBuilder(); + String prefix = ""; + for (String className : classNames) { + try { + Sampler sampler = new Sampler.Builder(conf). + className(className). + classLoader(classLoader). + build(); + samplers.add(sampler); + bld.append(prefix).append(className); + prefix = ", "; + } catch (Throwable e) { + LOG.error("Failed to create SpanReceiver of type " + className, e); + } + } + String resultString = bld.toString(); + if (resultString.isEmpty()) { + resultString = "no samplers"; + } + LOG.debug(SAMPLER_CLASSES_KEY + " = " + classNamesStr + + "; loaded " + resultString); + } + + private void loadSpanReceivers() { + String classNamesStr = conf.get(SPAN_RECEIVER_CLASSES_KEY, ""); + List<String> classNames = getClassNamesFromConf(classNamesStr); + StringBuilder bld = new StringBuilder(); + String prefix = ""; + for (String className : classNames) { + try { + tracerPool.loadReceiverType(className, conf, classLoader); + bld.append(prefix).append(className); + prefix = ", "; + } catch (Throwable e) { + LOG.error("Failed to create SpanReceiver of type " + className, e); + } + } + String resultString = bld.toString(); + if (resultString.isEmpty()) { + resultString = "no span receivers"; + } + LOG.debug(SPAN_RECEIVER_CLASSES_KEY + " = " + classNamesStr + + "; loaded " + resultString); + } + + /** + * Get a list of class names from the HTrace configuration. + * Entries which are empty will be removed. Entries which lack a package will + * be given the default package. + * + * @param classNamesStr A semicolon-separated string containing a list + * of class names. + * @return A list of class names. + */ + private List<String> getClassNamesFromConf(String classNamesStr) { + String classNames[] = classNamesStr.split(";"); + LinkedList<String> cleanedClassNames = new LinkedList<String>(); + for (String className : classNames) { + String cleanedClassName = className.trim(); + if (!cleanedClassName.isEmpty()) { + cleanedClassNames.add(cleanedClassName); + } + } + return cleanedClassNames; + } + + public Tracer build() { + if (name == null) { + throw new RuntimeException("You must specify a name for this Tracer."); + } + LinkedList<Sampler> samplers = new LinkedList<Sampler>(); + loadSamplers(samplers); + String tracerId = new TracerId(conf, name).get(); + Tracer tracer = new Tracer(tracerId, tracerPool, + samplers.toArray(new Sampler[samplers.size()])); + tracerPool.addTracer(tracer); + loadSpanReceivers(); + if (LOG.isTraceEnabled()) { + LOG.trace("Created " + tracer + " for " + name); + } + return tracer; + } + } + + /** + * The thread-specific context for this Tracer. + * + * This tracks the current number of trace scopes in a particular thread + * created by this tracer. We use this to apply our samplers only for the + * "top-level" spans. + * + * Note that we can't put the TraceScope objects themselves in this context, + * since we need to be able to use TraceScopes created by other Tracers, and + * this context is per-Tracer. + */ + private static class ThreadContext { + private long depth; + + ThreadContext() { + this.depth = 0; + } + + boolean isTopLevel() { + return (depth == 0); + } + + void pushScope() { + depth++; + } + + TraceScope pushNewScope(Tracer tracer, Span span, TraceScope parentScope) { + TraceScope scope = new TraceScope(tracer, span, parentScope); + threadLocalScope.set(scope); + depth++; + return scope; + } + + void popScope() { + if (depth <= 0) { + throwClientError("There were more trace scopes closed than " + + "were opened."); + } + depth--; + } + }; + + /** + * A subclass of ThreadLocal that starts off with a non-null initial value in + * each thread. + */ + private static class ThreadLocalContext extends ThreadLocal<ThreadContext> { + @Override + protected ThreadContext initialValue() { + return new ThreadContext(); + } + }; + + /** + * The current trace scope. This is global, so it is shared amongst all + * libraries using HTrace. + */ + final static ThreadLocal<TraceScope> threadLocalScope = + new ThreadLocal<TraceScope>(); + + /** + * An empty array of SpanId objects. Can be used rather than constructing a + * new object whenever we need an empty array. + */ + private static final SpanId EMPTY_PARENT_ARRAY[] = new SpanId[0]; + + /** + * The tracerId. + */ + private final String tracerId; + + /** + * The TracerPool which this Tracer belongs to. + * + * This gets set to null after the Tracer is closed in order to catch some + * use-after-close errors. Note that we do not synchronize access on this + * field, since it only changes when the Tracer is closed, and the Tracer + * should not be used after that. + */ + private TracerPool tracerPool; + + /** + * The current thread-local context for this particualr Tracer. + */ + private final ThreadLocalContext threadContext; + + /** + * The NullScope instance for this Tracer. + */ + private final NullScope nullScope; + + /** + * The currently active Samplers. + * + * Arrays are immutable once set. You must take the Tracer lock in order to + * set this to a new array. If this is null, the Tracer is closed. + */ + private volatile Sampler[] curSamplers; + + /** + * Log a client error, and throw an exception. + * + * @param str The message to use in the log and the exception. + */ + static void throwClientError(String str) { + LOG.error(str); + throw new RuntimeException(str); + } + + /** + * @return If the current thread is tracing, this function returns the Tracer that is + * being used; otherwise, it returns null. + */ + public static Tracer curThreadTracer() { + TraceScope traceScope = threadLocalScope.get(); + if (traceScope == null) { + return null; + } + return traceScope.tracer; + } + + Tracer(String tracerId, TracerPool tracerPool, Sampler[] curSamplers) { + this.tracerId = tracerId; + this.tracerPool = tracerPool; + this.threadContext = new ThreadLocalContext(); + this.nullScope = new NullScope(this); + this.curSamplers = curSamplers; + } + + public String getTracerId() { + return tracerId; + } + + private TraceScope newScopeImpl(ThreadContext context, String description) { + Span span = new MilliSpan.Builder(). + tracerId(tracerId). + begin(System.currentTimeMillis()). + description(description). + parents(EMPTY_PARENT_ARRAY). + spanId(SpanId.fromRandom()). + build(); + return context.pushNewScope(this, span, null); + } + + private TraceScope newScopeImpl(ThreadContext context, String description, + TraceScope parentScope) { + SpanId parentId = parentScope.getSpan().getSpanId(); + Span span = new MilliSpan.Builder(). + tracerId(tracerId). + begin(System.currentTimeMillis()). + description(description). + parents(new SpanId[] { parentId }). + spanId(parentId.newChildId()). + build(); + return context.pushNewScope(this, span, parentScope); + } + + private TraceScope newScopeImpl(ThreadContext context, String description, + SpanId parentId) { + Span span = new MilliSpan.Builder(). + tracerId(tracerId). + begin(System.currentTimeMillis()). + description(description). + parents(new SpanId[] { parentId }). + spanId(parentId.newChildId()). + build(); + return context.pushNewScope(this, span, null); + } + + private TraceScope newScopeImpl(ThreadContext context, String description, + TraceScope parentScope, SpanId secondParentId) { + SpanId parentId = parentScope.getSpan().getSpanId(); + Span span = new MilliSpan.Builder(). + tracerId(tracerId). + begin(System.currentTimeMillis()). + description(description). + parents(new SpanId[] { parentId, secondParentId }). + spanId(parentId.newChildId()). + build(); + return context.pushNewScope(this, span, parentScope); + } + + /** + * Create a new trace scope. + * + * If there are no scopes above the current scope, we will apply our + * configured samplers. Otherwise, we will create a trace Span only if this thread + * is already tracing, or if the passed parentID was valid. + * + * @param description The description of the new span to create. + * @param parentId If this is a valid span ID, it will be added to + * the parents of the new span we create. + * @return The new trace scope. + */ + public TraceScope newScope(String description, SpanId parentId) { + TraceScope parentScope = threadLocalScope.get(); + ThreadContext context = threadContext.get(); + if (parentScope != null) { + if (parentId.isValid() && + (!parentId.equals(parentScope.getSpan().getSpanId()))) { + return newScopeImpl(context, description, parentScope, parentId); + } else { + return newScopeImpl(context, description, parentScope); + } + } else if (parentId.isValid()) { + return newScopeImpl(context, description, parentId); + } + if (!context.isTopLevel()) { + context.pushScope(); + return nullScope; + } + if (!sample()) { + context.pushScope(); + return nullScope; + } + return newScopeImpl(context, description); + } + + /** + * Create a new trace scope. + * + * If there are no scopes above the current scope, we will apply our + * configured samplers. Otherwise, we will create a trace Span only if this thread + * is already tracing. + * @param description The description of the new span to create. + * @return The new trace scope. + */ + public TraceScope newScope(String description) { + TraceScope parentScope = threadLocalScope.get(); + ThreadContext context = threadContext.get(); + if (parentScope != null) { + return newScopeImpl(context, description, parentScope); + } + if (!context.isTopLevel()) { + context.pushScope(); + return nullScope; + } + if (!sample()) { + context.pushScope(); + return nullScope; + } + return newScopeImpl(context, description); + } + + /** + * Return a null trace scope. + */ + public TraceScope newNullScope() { + ThreadContext context = threadContext.get(); + context.pushScope(); + return nullScope; + } + + /** + * Wrap the callable in a TraceCallable, if tracing. + * + * @return The callable provided, wrapped if tracing, 'callable' if not. + */ + public <V> Callable<V> wrap(Callable<V> callable, String description) { + TraceScope parentScope = threadLocalScope.get(); + if (parentScope == null) { + return callable; + } + return new TraceCallable<V>(this, parentScope, callable, description); + } + + /** + * Wrap the runnable in a TraceRunnable, if tracing + * + * @return The runnable provided, wrapped if tracing, 'runnable' if not. + */ + public Runnable wrap(Runnable runnable, String description) { + TraceScope parentScope = threadLocalScope.get(); + if (parentScope == null) { + return runnable; + } + return new TraceRunnable(this, parentScope, runnable, description); + } + + public TraceExecutorService newTraceExecutorService(ExecutorService impl, + String scopeName) { + return new TraceExecutorService(this, scopeName, impl); + } + + public TracerPool getTracerPool() { + if (tracerPool == null) { + throwClientError(toString() + " is closed."); + } + return tracerPool; + } + + /** + * Returns an object that will trace all calls to itself. + */ + @SuppressWarnings("unchecked") + <T, V> T createProxy(final T instance) { + final Tracer tracer = this; + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object obj, Method method, Object[] args) + throws Throwable { + TraceScope scope = tracer.newScope(method.getName()); + try { + return method.invoke(instance, args); + } catch (Throwable ex) { + ex.printStackTrace(); + throw ex; + } finally { + scope.close(); + } + } + }; + return (T) Proxy.newProxyInstance(instance.getClass().getClassLoader(), + instance.getClass().getInterfaces(), handler); + } + + /** + * Return true if we should create a new top-level span. + * + * We will create the span if any configured sampler returns true. + */ + private boolean sample() { + Sampler[] samplers = curSamplers; + for (Sampler sampler : samplers) { + if (sampler.next()) { + return true; + } + } + return false; + } + + /** + * Returns an array of all the current Samplers. + * + * Note that if the current Samplers change, those changes will not be + * reflected in this array. In other words, this array may be stale. + */ + public Sampler[] getSamplers() { + return curSamplers; + } + + /** + * Add a new Sampler. + * + * @param sampler The new sampler to add. + * You cannot add a particular Sampler object more + * than once. You may add multiple Sampler objects + * of the same type, although this is not recommended. + * + * @return True if the sampler was added; false if it already had + * been added earlier. + */ + public synchronized boolean addSampler(Sampler sampler) { + if (tracerPool == null) { + throwClientError(toString() + " is closed."); + } + Sampler[] samplers = curSamplers; + for (int i = 0; i < samplers.length; i++) { + if (samplers[i] == sampler) { + return false; + } + } + Sampler[] newSamplers = + Arrays.copyOf(samplers, samplers.length + 1); + newSamplers[samplers.length] = sampler; + curSamplers = newSamplers; + return true; + } + + /** + * Remove a SpanReceiver. + * + * @param sampler The sampler to remove. + */ + public synchronized boolean removeSampler(Sampler sampler) { + if (tracerPool == null) { + throwClientError(toString() + " is closed."); + } + Sampler[] samplers = curSamplers; + for (int i = 0; i < samplers.length; i++) { + if (samplers[i] == sampler) { + Sampler[] newSamplers = new Sampler[samplers.length - 1]; + System.arraycopy(samplers, 0, newSamplers, 0, i); + System.arraycopy(samplers, i + 1, newSamplers, i, + samplers.length - i - 1); + curSamplers = newSamplers; + return true; + } + } + return false; + } + + void detachScope(TraceScope scope) { + TraceScope curScope = threadLocalScope.get(); + if (curScope != scope) { + throwClientError("Can't detach TraceScope for " + + scope.getSpan().toJson() + " because it is not the current " + + "TraceScope in thread " + Thread.currentThread().getName()); + } + ThreadContext context = threadContext.get(); + context.popScope(); + threadLocalScope.set(scope.getParent()); + } + + void reattachScope(TraceScope scope) { + TraceScope parent = threadLocalScope.get(); + Tracer.threadLocalScope.set(scope); + ThreadContext context = threadContext.get(); + context.pushScope(); + scope.setParent(parent); + } + + void closeScope(TraceScope scope) { + TraceScope curScope = threadLocalScope.get(); + if (curScope != scope) { + throwClientError("Can't close TraceScope for " + + scope.getSpan().toJson() + " because it is not the current " + + "TraceScope in thread " + Thread.currentThread().getName()); + } + if (tracerPool == null) { + throwClientError(toString() + " is closed."); + } + SpanReceiver[] receivers = tracerPool.getReceivers(); + if (receivers == null) { + throwClientError(toString() + " is closed."); + } + ThreadContext context = threadContext.get(); + context.popScope(); + threadLocalScope.set(scope.getParent()); + scope.setParent(null); + Span span = scope.getSpan(); + span.stop(); + for (SpanReceiver receiver : receivers) { + receiver.receiveSpan(span); + } + } + + void popNullScope() { + TraceScope curScope = threadLocalScope.get(); + if (curScope != null) { + throwClientError("Attempted to close an empty scope, but it was not " + + "the current thread scope in thread " + + Thread.currentThread().getName()); + } + ThreadContext context = threadContext.get(); + context.popScope(); + } + + public static Span getCurrentSpan() { + TraceScope curScope = threadLocalScope.get(); + if (curScope == null) { + return null; + } else { + return curScope.getSpan(); + } + } + + public static SpanId getCurrentSpanId() { + TraceScope curScope = threadLocalScope.get(); + if (curScope == null) { + return SpanId.INVALID; + } else { + return curScope.getSpan().getSpanId(); + } + } + + @Override + public synchronized void close() { + if (tracerPool == null) { + return; + } + curSamplers = new Sampler[0]; + tracerPool.removeTracer(this); + } + + /** + * Get the hash code of a Tracer object. + * + * This hash code is based on object identity. + * This is used in TracerPool to create a hash table of Tracers. + */ + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + /** + * Compare two tracer objects. + * + * Tracer objects are always compared by object equality. + * This is used in TracerPool to create a hash table of Tracers. + */ + @Override + public boolean equals(Object other) { + return (this == other); + } + + @Override + public String toString() { + return "Tracer(" + tracerId + ")"; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/main/java/org/apache/htrace/core/TracerId.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/main/java/org/apache/htrace/core/TracerId.java b/htrace-core4/src/main/java/org/apache/htrace/core/TracerId.java new file mode 100644 index 0000000..da482fe --- /dev/null +++ b/htrace-core4/src/main/java/org/apache/htrace/core/TracerId.java @@ -0,0 +1,294 @@ +/* + * 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.htrace.core; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; +import java.util.Locale; +import java.util.TreeSet; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The HTrace tracer ID.<p/> + * + * HTrace tracer IDs are created from format strings. + * Format strings contain variables which the TracerId class will + * replace with the correct values at runtime.<p/> + * + * <ul> + * <li>%{tname}: the tracer name supplied when creating the Tracer.</li> + * <li>%{pname}: the process name obtained from the JVM.</li> + * <li>%{ip}: will be replaced with an ip address.</li> + * <li>%{pid}: the numerical process ID from the operating system.</li> + * </ul><p/> + * + * For example, the string "%{pname}/%{ip}" will be replaced with something + * like: DataNode/192.168.0.1, assuming that the process' name is DataNode + * and its IP address is 192.168.0.1.<p/> + * + * ID strings can contain backslashes as escapes. + * For example, "\a" will map to "a". "\%{ip}" will map to the literal + * string "%{ip}", not the IP address. A backslash itself can be escaped by a + * preceding backslash. + */ +public final class TracerId { + private static final Log LOG = LogFactory.getLog(TracerId.class); + + /** + * The configuration key to use for process id + */ + public static final String TRACER_ID_KEY = "tracer.id"; + + /** + * The default tracer ID to use if no other ID is configured. + */ + private static final String DEFAULT_TRACER_ID = "%{tname}/%{ip}"; + + private final String tracerName; + + private final String tracerId; + + public TracerId(HTraceConfiguration conf, String tracerName) { + this.tracerName = tracerName; + String fmt = conf.get(TRACER_ID_KEY, DEFAULT_TRACER_ID); + StringBuilder bld = new StringBuilder(); + StringBuilder varBld = null; + boolean escaping = false; + int varSeen = 0; + for (int i = 0, len = fmt.length() ; i < len; i++) { + char c = fmt.charAt(i); + if (c == '\\') { + if (!escaping) { + escaping = true; + continue; + } + } + switch (varSeen) { + case 0: + if (c == '%') { + if (!escaping) { + varSeen = 1; + continue; + } + } + escaping = false; + varSeen = 0; + bld.append(c); + break; + case 1: + if (c == '{') { + if (!escaping) { + varSeen = 2; + varBld = new StringBuilder(); + continue; + } + } + escaping = false; + varSeen = 0; + bld.append("%").append(c); + break; + default: + if (c == '}') { + if (!escaping) { + String var = varBld.toString(); + bld.append(processShellVar(var)); + varBld = null; + varSeen = 0; + continue; + } + } + escaping = false; + varBld.append(c); + varSeen++; + break; + } + } + if (varSeen > 0) { + LOG.warn("Unterminated process ID substitution variable at the end " + + "of format string " + fmt); + } + this.tracerId = bld.toString(); + if (LOG.isTraceEnabled()) { + LOG.trace("ProcessID(fmt=" + fmt + "): computed process ID of \"" + + this.tracerId + "\""); + } + } + + private String processShellVar(String var) { + if (var.equals("tname")) { + return tracerName; + } else if (var.equals("pname")) { + return getProcessName(); + } else if (var.equals("ip")) { + return getBestIpString(); + } else if (var.equals("pid")) { + return Long.valueOf(getOsPid()).toString(); + } else { + LOG.warn("unknown ProcessID variable " + var); + return ""; + } + } + + static String getProcessName() { + String cmdLine = System.getProperty("sun.java.command"); + if (cmdLine != null && !cmdLine.isEmpty()) { + String fullClassName = cmdLine.split("\\s+")[0]; + String[] classParts = fullClassName.split("\\."); + cmdLine = classParts[classParts.length - 1]; + } + return (cmdLine == null || cmdLine.isEmpty()) ? "Unknown" : cmdLine; + } + + /** + * Get the best IP address that represents this node.<p/> + * + * This is complicated since nodes can have multiple network interfaces, + * and each network interface can have multiple IP addresses. What we're + * looking for here is an IP address that will serve to identify this node + * to HTrace. So we prefer site-local addresess (i.e. private ones on the + * LAN) to publicly routable interfaces. If there are multiple addresses + * to choose from, we select the one which comes first in textual sort + * order. This should ensure that we at least consistently call each node + * by a single name. + */ + static String getBestIpString() { + Enumeration<NetworkInterface> ifaces; + try { + ifaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + LOG.error("Error getting network interfaces", e); + return "127.0.0.1"; + } + TreeSet<String> siteLocalCandidates = new TreeSet<String>(); + TreeSet<String> candidates = new TreeSet<String>(); + while (ifaces.hasMoreElements()) { + NetworkInterface iface = ifaces.nextElement(); + for (Enumeration<InetAddress> addrs = + iface.getInetAddresses(); addrs.hasMoreElements();) { + InetAddress addr = addrs.nextElement(); + if (!addr.isLoopbackAddress()) { + if (addr.isSiteLocalAddress()) { + siteLocalCandidates.add(addr.getHostAddress()); + } else { + candidates.add(addr.getHostAddress()); + } + } + } + } + if (!siteLocalCandidates.isEmpty()) { + return siteLocalCandidates.first(); + } + if (!candidates.isEmpty()) { + return candidates.first(); + } + return "127.0.0.1"; + } + + /** + * Get the process id from the operating system.<p/> + * + * Unfortunately, there is no simple method to get the process id in Java. + * The approach we take here is to use the shell method (see + * {TracerId#getOsPidFromShellPpid}) unless we are on Windows, where the + * shell is not available. On Windows, we use + * {TracerId#getOsPidFromManagementFactory}, which depends on some + * undocumented features of the JVM, but which doesn't require a shell. + */ + static long getOsPid() { + if ((System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH)). + contains("windows")) { + return getOsPidFromManagementFactory(); + } else { + return getOsPidFromShellPpid(); + } + } + + /** + * Get the process ID by executing a shell and printing the PPID (parent + * process ID).<p/> + * + * This method of getting the process ID doesn't depend on any undocumented + * features of the virtual machine, and should work on almost any UNIX + * operating system. + */ + private static long getOsPidFromShellPpid() { + Process p = null; + StringBuilder sb = new StringBuilder(); + try { + p = new ProcessBuilder("/usr/bin/env", "sh", "-c", "echo $PPID"). + redirectErrorStream(true).start(); + BufferedReader reader = new BufferedReader( + new InputStreamReader(p.getInputStream())); + String line = ""; + while ((line = reader.readLine()) != null) { + sb.append(line.trim()); + } + int exitVal = p.waitFor(); + if (exitVal != 0) { + throw new IOException("Process exited with error code " + + Integer.valueOf(exitVal).toString()); + } + } catch (InterruptedException e) { + LOG.error("Interrupted while getting operating system pid from " + + "the shell.", e); + return 0L; + } catch (IOException e) { + LOG.error("Error getting operating system pid from the shell.", e); + return 0L; + } finally { + if (p != null) { + p.destroy(); + } + } + try { + return Long.parseLong(sb.toString()); + } catch (NumberFormatException e) { + LOG.error("Error parsing operating system pid from the shell.", e); + return 0L; + } + } + + /** + * Get the process ID by looking at the name of the managed bean for the + * runtime system of the Java virtual machine.<p/> + * + * Although this is undocumented, in the Oracle JVM this name is of the form + * [OS_PROCESS_ID]@[HOSTNAME]. + */ + private static long getOsPidFromManagementFactory() { + try { + return Long.parseLong(ManagementFactory.getRuntimeMXBean(). + getName().split("@")[0]); + } catch (NumberFormatException e) { + LOG.error("Failed to get the operating system process ID from the name " + + "of the managed bean for the JVM.", e); + return 0L; + } + } + + public String get() { + return tracerId; + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/main/java/org/apache/htrace/core/TracerPool.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/main/java/org/apache/htrace/core/TracerPool.java b/htrace-core4/src/main/java/org/apache/htrace/core/TracerPool.java new file mode 100644 index 0000000..26e39f5 --- /dev/null +++ b/htrace-core4/src/main/java/org/apache/htrace/core/TracerPool.java @@ -0,0 +1,285 @@ +/* + * 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.htrace.core; + +import java.util.Arrays; +import java.util.HashSet; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A pool of Tracer objects. + * + * There may be more than one {@link Tracer} running inside a single 'process'; for example, + * unit tests may spin up a DataNode, a NameNode, and HDFS clients all running in a single JVM + * instance, each with its own Tracer. TracerPool is where all Tracer instances register + * on creation so Tracers can coordinate around shared resources such as {@link SpanReceiver} + * instances. TracerPool takes care of properly cleaning up registered Tracer instances on shutdown. + */ +public class TracerPool { + private static final Log LOG = LogFactory.getLog(TracerPool.class); + + /** + * The global pool of tracer objects. + * + * This is the pool that new tracers get put into by default. + */ + static final TracerPool GLOBAL = new TracerPool("Global"); + + /** + * The shutdown hook which closes the Tracers in this pool when the process is + * shutting down. + */ + private class SpanReceiverShutdownHook extends Thread { + SpanReceiverShutdownHook() { + setName("SpanReceiverShutdownHook"); + setDaemon(false); + } + + @Override + public void run() { + removeAndCloseAllSpanReceivers(); + } + } + + /** + * The name of this TracerPool. + */ + private final String name; + + /** + * The current span receivers which these tracers are using. + * + * Can be read locklessly. Must be written under the lock. + * The array itself should never be modified. + */ + private volatile SpanReceiver[] curReceivers; + + /** + * The currently installed shutdown hook, or null if no hook has been + * installed. + */ + private SpanReceiverShutdownHook shutdownHook; + + /** + * The current Tracers. + */ + private final HashSet<Tracer> curTracers; + + /** + * Get the global tracer pool. + */ + public static TracerPool getGlobalTracerPool() { + return GLOBAL; + } + + public TracerPool(String name) { + this.name = name; + this.shutdownHook = null; + this.curTracers = new HashSet<Tracer>(); + this.curReceivers = new SpanReceiver[0]; + } + + /** + * Return the name of this TracerPool. + */ + public String getName() { + return name; + } + + /** + * Returns an array of all the current span receivers. + * + * Note that if the current span receivers change, those changes will not be + * reflected in this array. In other words, this array may be stale. + */ + public SpanReceiver[] getReceivers() { + return curReceivers; + } + + /** + * Add a new span receiver. + * + * @param receiver The new receiver to add. + * + * @return True if the new receiver was added; false if it + * already was there. + */ + public synchronized boolean addReceiver(SpanReceiver receiver) { + SpanReceiver[] receivers = curReceivers; + for (int i = 0; i < receivers.length; i++) { + if (receivers[i] == receiver) { + LOG.trace(toString() + ": can't add receiver " + receiver.toString() + + " since it is already in this pool."); + return false; + } + } + SpanReceiver[] newReceivers = + Arrays.copyOf(receivers, receivers.length + 1); + newReceivers[receivers.length] = receiver; + registerShutdownHookIfNeeded(); + curReceivers = newReceivers; + LOG.trace(toString() + ": added receiver " + receiver.toString()); + return true; + } + + /** + * Register the shutdown hook if needed. + */ + private synchronized void registerShutdownHookIfNeeded() { + if (shutdownHook != null) { + return; + } + shutdownHook = new SpanReceiverShutdownHook(); + Runtime.getRuntime().addShutdownHook(shutdownHook); + LOG.trace(toString() + ": registered shutdown hook."); + } + + /** + * Remove a span receiver. + * + * @param receiver The receiver to remove. + * + * @return True if the receiver was removed; false if it + * did not exist in this pool. + */ + public synchronized boolean removeReceiver(SpanReceiver receiver) { + SpanReceiver[] receivers = curReceivers; + for (int i = 0; i < receivers.length; i++) { + if (receivers[i] == receiver) { + SpanReceiver[] newReceivers = new SpanReceiver[receivers.length - 1]; + System.arraycopy(receivers, 0, newReceivers, 0, i); + System.arraycopy(receivers, i + 1, newReceivers, i, + receivers.length - i - 1); + curReceivers = newReceivers; + LOG.trace(toString() + ": removed receiver " + receiver.toString()); + return true; + } + } + LOG.trace(toString() + ": can't remove receiver " + receiver.toString() + + " since it's not currently in this pool."); + return false; + } + + /** + * Remove and close a span receiver. + * + * @param receiver The receiver to remove. + * + * @return True if the receiver was removed; false if it + * did not exist in this pool. + */ + public boolean removeAndCloseReceiver(SpanReceiver receiver) { + if (!removeReceiver(receiver)) { + return false; + } + try { + LOG.trace(toString() + ": closing receiver " + receiver.toString()); + receiver.close(); + } catch (Throwable t) { + LOG.error(toString() + ": error closing " + receiver.toString(), t); + } + return true; + } + + /** + * Remove and close all of the span receivers. + */ + private synchronized void removeAndCloseAllSpanReceivers() { + SpanReceiver[] receivers = curReceivers; + curReceivers = new SpanReceiver[0]; + for (SpanReceiver receiver : receivers) { + try { + LOG.trace(toString() + ": closing receiver " + receiver.toString()); + receiver.close(); + } catch (Throwable t) { + LOG.error(toString() + ": error closing " + receiver.toString(), t); + } + } + } + + /** + * Given a SpanReceiver class name, return the existing instance of that span + * receiver, if possible; otherwise, invoke the callable to create a new + * instance. + * + * @param className The span receiver class name. + * @param conf The HTrace configuration. + * @param classLoader The class loader to use. + * + * @return The SpanReceiver. + */ + public synchronized SpanReceiver loadReceiverType(String className, + HTraceConfiguration conf, ClassLoader classLoader) { + SpanReceiver[] receivers = curReceivers; + for (SpanReceiver receiver : receivers) { + if (receiver.getClass().getName().equals(className)) { + LOG.trace(toString() + ": returning a reference to receiver " + + receiver.toString()); + return receiver; + } + } + LOG.trace(toString() + ": creating a new SpanReceiver of type " + + className); + SpanReceiver receiver = new SpanReceiver.Builder(conf). + className(className). + classLoader(classLoader). + build(); + addReceiver(receiver); + return receiver; + } + + /** + * Returns an array of all the current Tracers. + * + * Note that if the current Tracers change, those changes will not be + * reflected in this array. In other words, this array may be stale. + */ + public synchronized Tracer[] getTracers() { + return curTracers.toArray(new Tracer[curTracers.size()]); + } + + /** + * Add a new Tracer. + */ + synchronized void addTracer(Tracer tracer) { + if (curTracers.add(tracer)) { + LOG.trace(toString() + ": adding tracer " + tracer.toString()); + } + } + + /** + * Remove a Tracer. + * + * If the Tracer removed was the last one, we will close all the SpanReceiver + * objects that we're managing. + */ + synchronized void removeTracer(Tracer tracer) { + if (curTracers.remove(tracer)) { + LOG.trace(toString() + ": removing tracer " + tracer.toString()); + if (curTracers.size() == 0) { + removeAndCloseAllSpanReceivers(); + } + } + } + + @Override + public String toString() { + return "TracerPool(" + name + ")"; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestBadClient.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestBadClient.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestBadClient.java new file mode 100644 index 0000000..87ae8e9 --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestBadClient.java @@ -0,0 +1,205 @@ +/* + * 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.htrace.core; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.hamcrest.CoreMatchers.containsString; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class TestBadClient { + @After + public void clearBadState() { + // Clear the bad trace state so that we don't disrupt other unit tests + // that run in this JVM. + Tracer.threadLocalScope.set(null); + } + + /** + * Test closing an outer scope when an inner one is still active. + */ + @Test + public void TestClosingOuterScope() throws Exception { + Tracer tracer = new Tracer.Builder(). + name("TestClosingOuterScopeTracer"). + tracerPool(new TracerPool("TestClosingOuterScope")). + conf(HTraceConfiguration. + fromKeyValuePairs("sampler.classes", "AlwaysSampler")).build(); + boolean gotException = false; + TraceScope outerScope = tracer.newScope("outer"); + TraceScope innerScope = tracer.newScope("inner"); + try { + outerScope.close(); + } catch (RuntimeException e) { + assertThat(e.getMessage(), + containsString("it is not the current TraceScope")); + gotException = true; + } + assertTrue("Expected to get exception because of improper " + + "scope closure.", gotException); + innerScope.close(); + tracer.close(); + } + + /** + * Test calling detach() two times on a scope object. + */ + @Test + public void TestDoubleDetachIsCaught() throws Exception { + Tracer tracer = new Tracer.Builder(). + name("TestDoubleDetach"). + tracerPool(new TracerPool("TestDoubleDetachIsCaught")). + conf(HTraceConfiguration. + fromKeyValuePairs("sampler.classes", "AlwaysSampler")).build(); + boolean gotException = false; + TraceScope myScope = tracer.newScope("myScope"); + myScope.detach(); + try { + myScope.detach(); + } catch (RuntimeException e) { + assertThat(e.getMessage(), + containsString("it is already detached.")); + gotException = true; + } + assertTrue("Expected to get exception because of double TraceScope " + + "detach.", gotException); + tracer.close(); + } + + /** + * Test calling detach() two times on a scope object. + */ + @Test + public void TestDoubleDetachOnNullScope() throws Exception { + Tracer tracer = new Tracer.Builder(). + name("TestDoubleDetachOnNullScope"). + tracerPool(new TracerPool("TestDoubleDetachOnNullScope")). + conf(HTraceConfiguration. + fromKeyValuePairs("sampler.classes", "NeverSampler")).build(); + boolean gotException = false; + TraceScope myScope = tracer.newScope("myScope"); + myScope.detach(); + try { + myScope.detach(); + } catch (RuntimeException e) { + assertThat(e.getMessage(), + containsString("it is already detached.")); + gotException = true; + } + assertTrue("Expected to get exception because of double TraceScope " + + "detach on NullScope.", gotException); + tracer.close(); + } + + /** + * Test calling reattach() two times on a scope object. + */ + @Test + public void TestDoubleReattachIsCaught() throws Exception { + Tracer tracer = new Tracer.Builder(). + name("TestDoubleReattach"). + tracerPool(new TracerPool("TestDoubleReattachIsCaught")). + conf(HTraceConfiguration. + fromKeyValuePairs("sampler.classes", "AlwaysSampler")).build(); + boolean gotException = false; + TraceScope myScope = tracer.newScope("myScope"); + myScope.detach(); + myScope.reattach(); + try { + myScope.reattach(); + } catch (RuntimeException e) { + assertThat(e.getMessage(), + containsString("it is not detached.")); + gotException = true; + } + assertTrue("Expected to get exception because of double TraceScope " + + "reattach.", gotException); + tracer.close(); + } + + private static class ScopeHolder { + TraceScope scope; + + void set(TraceScope scope) { + this.scope = scope; + } + } + + /** + * Test correctly passing spans between threads using detach(). + */ + @Test + public void TestPassingSpanBetweenThreads() throws Exception { + final Tracer tracer = new Tracer.Builder(). + name("TestPassingSpanBetweenThreads"). + tracerPool(new TracerPool("TestPassingSpanBetweenThreads")). + conf(HTraceConfiguration. + fromKeyValuePairs("sampler.classes", "AlwaysSampler")).build(); + POJOSpanReceiver receiver = + new POJOSpanReceiver(HTraceConfiguration.EMPTY); + tracer.getTracerPool().addReceiver(receiver); + final ScopeHolder scopeHolder = new ScopeHolder(); + Thread th = new Thread(new Runnable() { + @Override + public void run() { + TraceScope workerScope = tracer.newScope("workerSpan"); + workerScope.detach(); + scopeHolder.set(workerScope); + } + }); + th.start(); + th.join(); + TraceScope workerScope = scopeHolder.scope; + SpanId workerScopeId = workerScope.getSpan().getSpanId(); + + // Create new scope whose parent is the worker thread's span. + workerScope.reattach(); + TraceScope nested = tracer.newScope("nested"); + nested.close(); + // Create another span which also descends from the worker thread's span. + TraceScope nested2 = tracer.newScope("nested2"); + nested2.close(); + + // Close the worker thread's span. + workerScope.close(); + + // We can create another descendant, even though the worker thread's span + // has been stopped. + TraceScope lateChildScope = tracer.newScope("lateChild", workerScopeId); + lateChildScope.close(); + tracer.close(); + + TraceGraph traceGraph = new TraceGraph(receiver.getSpans()); + Collection<Span> rootSpans = + traceGraph.getSpansByParent().find(SpanId.INVALID); + Assert.assertEquals(1, rootSpans.size()); + Assert.assertEquals(workerScopeId, + rootSpans.iterator().next().getSpanId()); + Collection<Span> childSpans = + traceGraph.getSpansByParent().find(workerScopeId); + Assert.assertEquals(3, childSpans.size()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestCountSampler.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestCountSampler.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestCountSampler.java new file mode 100644 index 0000000..e26115d --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestCountSampler.java @@ -0,0 +1,41 @@ +/* + * 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.htrace.core; + +import org.junit.Assert; +import org.junit.Test; + +public class TestCountSampler { + + @Test + public void testNext() { + CountSampler half = new CountSampler(HTraceConfiguration. + fromKeyValuePairs("sampler.frequency", "2")); + CountSampler hundred = new CountSampler(HTraceConfiguration. + fromKeyValuePairs("sampler.frequency", "100")); + int halfCount = 0; + int hundredCount = 0; + for (int i = 0; i < 200; i++) { + if (half.next()) + halfCount++; + if (hundred.next()) + hundredCount++; + } + Assert.assertEquals(2, hundredCount); + Assert.assertEquals(100, halfCount); + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestHTrace.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestHTrace.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestHTrace.java new file mode 100644 index 0000000..06ca189 --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestHTrace.java @@ -0,0 +1,130 @@ +/* + * 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.htrace.core; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.htrace.core.TraceGraph.SpansByParent; + +import org.junit.Assert; +import org.junit.Test; + +public class TestHTrace { + @Test + public void TestTracerCreateAndClose() throws Exception { + Tracer tracer = new Tracer.Builder(). + name("TestSimpleScope"). + tracerPool(new TracerPool("TestTracerCreateAndClose")). + conf(HTraceConfiguration.fromKeyValuePairs( + "sampler.classes", "AlwaysSampler")). + build(); + POJOSpanReceiver receiver = + new POJOSpanReceiver(HTraceConfiguration.EMPTY); + tracer.getTracerPool().addReceiver(receiver); + tracer.close(); + Assert.assertTrue(receiver.getSpans().isEmpty()); + } + + @Test + public void TestSimpleScope() throws Exception { + Tracer tracer = new Tracer.Builder(). + name("TestSimpleScope"). + tracerPool(new TracerPool("TestSimpleScope")). + conf(HTraceConfiguration.fromKeyValuePairs( + "sampler.classes", "AlwaysSampler")). + build(); + POJOSpanReceiver receiver = + new POJOSpanReceiver(HTraceConfiguration.EMPTY); + tracer.getTracerPool().addReceiver(receiver); + TraceScope scope = tracer.newScope("Foo"); + scope.close(); + tracer.close(); + Assert.assertEquals(1, receiver.getSpans().size()); + Span span = receiver.getSpans().iterator().next(); + Assert.assertEquals(0, span.getParents().length); + } + + @Test + public void TestCreateSpans() throws Exception { + Tracer tracer = new Tracer.Builder(). + name("TestCreateSpans"). + tracerPool(new TracerPool("TestCreateSpans")). + conf(HTraceConfiguration.fromKeyValuePairs( + "sampler.classes", "AlwaysSampler")). + build(); + POJOSpanReceiver receiver = + new POJOSpanReceiver(HTraceConfiguration.EMPTY); + tracer.getTracerPool().addReceiver(receiver); + TraceCreator traceCreator = new TraceCreator(tracer); + traceCreator.createSampleRpcTrace(); + traceCreator.createSimpleTrace(); + traceCreator.createThreadedTrace(); + tracer.close(); + TraceGraph traceGraph = new TraceGraph(receiver.getSpans()); + Collection<Span> roots = traceGraph.getSpansByParent().find(SpanId.INVALID); + Assert.assertTrue("Trace tree must have roots", !roots.isEmpty()); + Assert.assertEquals(3, roots.size()); + + Map<String, Span> descriptionToRootSpan = new HashMap<String, Span>(); + for (Span root : roots) { + descriptionToRootSpan.put(root.getDescription(), root); + } + + Assert.assertTrue(descriptionToRootSpan.keySet().contains( + TraceCreator.RPC_TRACE_ROOT)); + Assert.assertTrue(descriptionToRootSpan.keySet().contains( + TraceCreator.SIMPLE_TRACE_ROOT)); + Assert.assertTrue(descriptionToRootSpan.keySet().contains( + TraceCreator.THREADED_TRACE_ROOT)); + + SpansByParent spansByParentId = traceGraph.getSpansByParent(); + + Span rpcTraceRoot = descriptionToRootSpan.get(TraceCreator.RPC_TRACE_ROOT); + Assert.assertEquals(1, spansByParentId.find(rpcTraceRoot.getSpanId()).size()); + + Span rpcTraceChild1 = spansByParentId.find(rpcTraceRoot.getSpanId()) + .iterator().next(); + Assert.assertEquals(1, spansByParentId.find(rpcTraceChild1.getSpanId()).size()); + + Span rpcTraceChild2 = spansByParentId.find(rpcTraceChild1.getSpanId()) + .iterator().next(); + Assert.assertEquals(1, spansByParentId.find(rpcTraceChild2.getSpanId()).size()); + + Span rpcTraceChild3 = spansByParentId.find(rpcTraceChild2.getSpanId()) + .iterator().next(); + Assert.assertEquals(0, spansByParentId.find(rpcTraceChild3.getSpanId()).size()); + } + + @Test(timeout=60000) + public void testRootSpansHaveNonZeroSpanId() throws Exception { + Tracer tracer = new Tracer.Builder(). + name("testRootSpansHaveNonZeroSpanId"). + tracerPool(new TracerPool("testRootSpansHaveNonZeroSpanId")). + conf(HTraceConfiguration.fromKeyValuePairs( + "sampler.classes", "AlwaysSampler")).build(); + TraceScope scope = tracer. + newScope("myRootSpan", new SpanId(100L, 200L)); + Assert.assertNotNull(scope); + Assert.assertEquals("myRootSpan", scope.getSpan().getDescription()); + Assert.assertTrue(scope.getSpan().getSpanId().isValid()); + Assert.assertEquals(100L, scope.getSpan().getSpanId().getHigh()); + Assert.assertNotEquals(0L, scope.getSpan().getSpanId().getLow()); + scope.close(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestHTraceConfiguration.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestHTraceConfiguration.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestHTraceConfiguration.java new file mode 100644 index 0000000..7ca897f --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestHTraceConfiguration.java @@ -0,0 +1,62 @@ +/* + * 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.htrace.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +public class TestHTraceConfiguration { + @Test + public void testGetBoolean() throws Exception { + + Map<String, String> m = new HashMap<String, String>(); + m.put("testTrue", " True"); + m.put("testFalse", "falsE "); + HTraceConfiguration configuration = HTraceConfiguration.fromMap(m); + + // Tests for value being there + assertTrue(configuration.getBoolean("testTrue", false)); + assertFalse(configuration.getBoolean("testFalse", true)); + + // Test for absent + assertTrue(configuration.getBoolean("absent", true)); + assertFalse(configuration.getBoolean("absent", false)); + } + + @Test + public void testGetInt() throws Exception { + Map<String, String> m = new HashMap<String, String>(); + m.put("a", "100"); + m.put("b", "0"); + m.put("c", "-100"); + m.put("d", "5"); + + HTraceConfiguration configuration = HTraceConfiguration.fromMap(m); + assertEquals(100, configuration.getInt("a", -999)); + assertEquals(0, configuration.getInt("b", -999)); + assertEquals(-100, configuration.getInt("c", -999)); + assertEquals(5, configuration.getInt("d", -999)); + assertEquals(-999, configuration.getInt("absent", -999)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestLocalFileSpanReceiver.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestLocalFileSpanReceiver.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestLocalFileSpanReceiver.java new file mode 100644 index 0000000..9388707 --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestLocalFileSpanReceiver.java @@ -0,0 +1,65 @@ +/* + * 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.htrace.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; + +public class TestLocalFileSpanReceiver { + @Test + public void testUniqueLocalTraceFileName() { + String filename1 = LocalFileSpanReceiver.getUniqueLocalTraceFileName(); + String filename2 = LocalFileSpanReceiver.getUniqueLocalTraceFileName(); + boolean eq = filename1.equals(filename2); + if (System.getProperty("os.name").startsWith("Linux")) { + // ${java.io.tmpdir}/[pid] + assertTrue(eq); + } else { + // ${java.io.tmpdir}/[random UUID] + assertFalse(eq); + } + } + + @Test + public void testWriteToLocalFile() throws IOException { + String traceFileName = LocalFileSpanReceiver.getUniqueLocalTraceFileName(); + Tracer tracer = new Tracer.Builder(). + name("testWriteToLocalFileTracer"). + tracerPool(new TracerPool("testWriteToLocalFile")). + conf(HTraceConfiguration.fromKeyValuePairs( + "sampler.classes", "AlwaysSampler", + "span.receiver.classes", LocalFileSpanReceiver.class.getName(), + "local.file.span.receiver.path", traceFileName, + "tracer.id", "%{tname}")). + build(); + TraceScope scope = tracer.newScope("testWriteToLocalFile"); + scope.close(); + tracer.close(); + + ObjectMapper mapper = new ObjectMapper(); + MilliSpan span = mapper.readValue(new File(traceFileName), MilliSpan.class); + assertEquals("testWriteToLocalFile", span.getDescription()); + assertEquals("testWriteToLocalFileTracer", span.getTracerId()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestMilliSpan.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestMilliSpan.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestMilliSpan.java new file mode 100644 index 0000000..7ce1fdb --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestMilliSpan.java @@ -0,0 +1,145 @@ +/* + * 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.htrace.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class TestMilliSpan { + private void compareSpans(Span expected, Span got) throws Exception { + assertEquals(expected.getStartTimeMillis(), got.getStartTimeMillis()); + assertEquals(expected.getStopTimeMillis(), got.getStopTimeMillis()); + assertEquals(expected.getDescription(), got.getDescription()); + assertEquals(expected.getSpanId(), got.getSpanId()); + assertEquals(expected.getTracerId(), got.getTracerId()); + assertTrue(Arrays.equals(expected.getParents(), got.getParents())); + Map<String, String> expectedT = expected.getKVAnnotations(); + Map<String, String> gotT = got.getKVAnnotations(); + if (expectedT == null) { + assertEquals(null, gotT); + } else { + assertEquals(expectedT.size(), gotT.size()); + for (String key : expectedT.keySet()) { + assertEquals(expectedT.get(key), gotT.get(key)); + } + } + List<TimelineAnnotation> expectedTimeline = + expected.getTimelineAnnotations(); + List<TimelineAnnotation> gotTimeline = + got.getTimelineAnnotations(); + if (expectedTimeline == null) { + assertEquals(null, gotTimeline); + } else { + assertEquals(expectedTimeline.size(), gotTimeline.size()); + Iterator<TimelineAnnotation> iter = gotTimeline.iterator(); + for (TimelineAnnotation expectedAnn : expectedTimeline) { + TimelineAnnotation gotAnn = iter.next(); + assertEquals(expectedAnn.getMessage(), gotAnn.getMessage()); + assertEquals(expectedAnn.getTime(), gotAnn.getTime()); + } + } + } + + @Test + public void testJsonSerialization() throws Exception { + MilliSpan span = new MilliSpan.Builder(). + description("foospan"). + begin(123L). + end(456L). + parents(new SpanId[] { new SpanId(7L, 7L) }). + tracerId("b2404.halxg.com:8080"). + spanId(new SpanId(7L, 8L)). + build(); + String json = span.toJson(); + MilliSpan dspan = MilliSpan.fromJson(json); + compareSpans(span, dspan); + } + + @Test + public void testJsonSerializationWithNegativeLongValue() throws Exception { + MilliSpan span = new MilliSpan.Builder(). + description("foospan"). + begin(-1L). + end(-1L). + parents(new SpanId[] { new SpanId(-1L, -1L) }). + tracerId("b2404.halxg.com:8080"). + spanId(new SpanId(-1L, -2L)). + build(); + String json = span.toJson(); + MilliSpan dspan = MilliSpan.fromJson(json); + compareSpans(span, dspan); + } + + @Test + public void testJsonSerializationWithRandomLongValue() throws Exception { + SpanId parentId = SpanId.fromRandom(); + MilliSpan span = new MilliSpan.Builder(). + description("foospan"). + begin(ThreadLocalRandom.current().nextLong()). + end(ThreadLocalRandom.current().nextLong()). + parents(new SpanId[] { parentId }). + tracerId("b2404.halxg.com:8080"). + spanId(parentId.newChildId()). + build(); + String json = span.toJson(); + MilliSpan dspan = MilliSpan.fromJson(json); + compareSpans(span, dspan); + } + + @Test + public void testJsonSerializationWithOptionalFields() throws Exception { + MilliSpan.Builder builder = new MilliSpan.Builder(). + description("foospan"). + begin(300). + end(400). + parents(new SpanId[] { }). + tracerId("b2408.halxg.com:8080"). + spanId(new SpanId(111111111L, 111111111L)); + Map<String, String> traceInfo = new HashMap<String, String>(); + traceInfo.put("abc", "123"); + traceInfo.put("def", "456"); + builder.traceInfo(traceInfo); + List<TimelineAnnotation> timeline = new LinkedList<TimelineAnnotation>(); + timeline.add(new TimelineAnnotation(310L, "something happened")); + timeline.add(new TimelineAnnotation(380L, "something else happened")); + timeline.add(new TimelineAnnotation(390L, "more things")); + builder.timeline(timeline); + MilliSpan span = builder.build(); + String json = span.toJson(); + MilliSpan dspan = MilliSpan.fromJson(json); + compareSpans(span, dspan); + } + + @Test + public void testJsonSerializationWithFieldsNotSet() throws Exception { + MilliSpan span = new MilliSpan.Builder().build(); + String json = span.toJson(); + MilliSpan dspan = MilliSpan.fromJson(json); + compareSpans(span, dspan); + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestNullScope.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestNullScope.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestNullScope.java new file mode 100644 index 0000000..c8ed7f1 --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestNullScope.java @@ -0,0 +1,43 @@ +/* + * 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.htrace.core; + +import org.junit.Assert; +import org.junit.Test; + +public class TestNullScope { + private void verifyNullScope(TraceScope scope) { + Assert.assertTrue(null == scope.getSpan()); + Assert.assertFalse(scope.detached); + scope.detach(); + Assert.assertTrue(scope.detached); + scope.reattach(); + Assert.assertFalse(scope.detached); + scope.close(); + } + + @Test + public void testNullScope() { + Tracer tracer = new Tracer.Builder(). + name("testNullScope"). + tracerPool(new TracerPool("testNullScope")). + conf(HTraceConfiguration.EMPTY). + build(); + verifyNullScope(tracer.newScope("testNullScope")); + verifyNullScope(tracer.newNullScope()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestSampler.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestSampler.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestSampler.java new file mode 100644 index 0000000..2305d9f --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestSampler.java @@ -0,0 +1,100 @@ +/* + * 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.htrace.core; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +public class TestSampler { + private Sampler[] getSamplersFromConf(HTraceConfiguration conf) { + Tracer tracer = new Tracer.Builder(). + name("MyTracer"). + tracerPool(new TracerPool("getSamplersFromConf")). + conf(conf). + build(); + Sampler[] samplers = tracer.getSamplers(); + tracer.close(); + return samplers; + } + + private void checkArrayContains(List<Class<? extends Sampler>> expected, + Sampler[] samplers) { + for (Iterator<Class<? extends Sampler>> iter = expected.iterator(); + iter.hasNext(); ) { + Class<? extends Sampler> samplerClass = iter.next(); + boolean found = false; + for (int i = 0; i < samplers.length; i++) { + if (samplers[i] != null) { + if (samplers[i].getClass().equals(samplerClass)) { + samplers[i] = null; + found = true; + break; + } + } + } + Assert.assertTrue("Failed to find sampler class " + + samplerClass.getName(), found); + } + for (int i = 0; i < samplers.length; i++) { + if (samplers[i] != null) { + Assert.fail("Got extra sampler of type " + + samplers.getClass().getName()); + } + } + } + + private void checkArrayContains(Class<? extends Sampler> expected, Sampler[] samplers) { + LinkedList<Class<? extends Sampler>> expectedList = + new LinkedList<Class<? extends Sampler>>(); + expectedList.add(expected); + checkArrayContains(expectedList, samplers); + } + + @Test + public void testTracerBuilderCreatesCorrectSamplers() { + Sampler[] samplers = getSamplersFromConf(HTraceConfiguration. + fromKeyValuePairs("sampler.classes", "AlwaysSampler")); + checkArrayContains(AlwaysSampler.class, samplers); + + samplers = getSamplersFromConf(HTraceConfiguration. + fromKeyValuePairs("sampler.classes", "NeverSampler")); + checkArrayContains(NeverSampler.class, samplers); + + samplers = getSamplersFromConf(HTraceConfiguration. + fromKeyValuePairs("sampler.classes", "NonExistentSampler")); + Assert.assertEquals(0, samplers.length); + + samplers = getSamplersFromConf(HTraceConfiguration.EMPTY); + Assert.assertEquals(0, samplers.length); + } + + @Test + public void testAlwaysSampler() { + AlwaysSampler sampler = new AlwaysSampler(HTraceConfiguration.EMPTY); + Assert.assertTrue(sampler.next()); + } + + @Test + public void testNeverSampler() { + NeverSampler sampler = new NeverSampler(HTraceConfiguration.EMPTY); + Assert.assertTrue(!sampler.next()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestSpanId.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestSpanId.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestSpanId.java new file mode 100644 index 0000000..bb57368 --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestSpanId.java @@ -0,0 +1,72 @@ +/* + * 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.htrace.core; + +import java.util.Random; + +import org.junit.Assert; +import org.junit.Test; + +public class TestSpanId { + private void testRoundTrip(SpanId id) throws Exception { + String str = id.toString(); + SpanId id2 = SpanId.fromString(str); + Assert.assertEquals(id, id2); + } + + @Test + public void testToStringAndFromString() throws Exception { + testRoundTrip(SpanId.INVALID); + testRoundTrip(new SpanId(0x1234567812345678L, 0x1234567812345678L)); + testRoundTrip(new SpanId(0xf234567812345678L, 0xf234567812345678L)); + testRoundTrip(new SpanId(0xffffffffffffffffL, 0xffffffffffffffffL)); + Random rand = new Random(12345); + for (int i = 0; i < 100; i++) { + testRoundTrip(new SpanId(rand.nextLong(), rand.nextLong())); + } + } + + @Test + public void testValidAndInvalidIds() throws Exception { + Assert.assertFalse(SpanId.INVALID.isValid()); + Assert.assertTrue( + new SpanId(0x1234567812345678L, 0x1234567812345678L).isValid()); + Assert.assertTrue( + new SpanId(0xf234567812345678L, 0xf234567812345678L).isValid()); + } + + private void expectLessThan(SpanId a, SpanId b) throws Exception { + int cmp = a.compareTo(b); + Assert.assertTrue("Expected " + a + " to be less than " + b, + (cmp < 0)); + int cmp2 = b.compareTo(a); + Assert.assertTrue("Expected " + b + " to be greater than " + a, + (cmp2 > 0)); + } + + @Test + public void testIdComparisons() throws Exception { + expectLessThan(new SpanId(0x0000000000000001L, 0x0000000000000001L), + new SpanId(0x0000000000000001L, 0x0000000000000002L)); + expectLessThan(new SpanId(0x0000000000000001L, 0x0000000000000001L), + new SpanId(0x0000000000000002L, 0x0000000000000000L)); + expectLessThan(SpanId.INVALID, + new SpanId(0xffffffffffffffffL, 0xffffffffffffffffL)); + expectLessThan(new SpanId(0x1234567812345678L, 0x1234567812345678L), + new SpanId(0x1234567812345678L, 0xf234567812345678L)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/3c20489f/htrace-core4/src/test/java/org/apache/htrace/core/TestSpanReceiverBuilder.java ---------------------------------------------------------------------- diff --git a/htrace-core4/src/test/java/org/apache/htrace/core/TestSpanReceiverBuilder.java b/htrace-core4/src/test/java/org/apache/htrace/core/TestSpanReceiverBuilder.java new file mode 100644 index 0000000..b97d624 --- /dev/null +++ b/htrace-core4/src/test/java/org/apache/htrace/core/TestSpanReceiverBuilder.java @@ -0,0 +1,127 @@ +/* + * 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.htrace.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class TestSpanReceiverBuilder { + private static final Log LOG = + LogFactory.getLog(TestSpanReceiverBuilder.class); + + private List<SpanReceiver> createSpanReceivers(String classes) { + Tracer tracer = new Tracer.Builder(). + name("MyTracer"). + tracerPool(new TracerPool("createSpanReceivers")). + conf(HTraceConfiguration.fromKeyValuePairs( + "span.receiver.classes", classes)). + build(); + SpanReceiver[] receivers = tracer.getTracerPool().getReceivers(); + tracer.close(); + LinkedList<SpanReceiver> receiverList = new LinkedList<SpanReceiver>(); + for (SpanReceiver item: receivers) { + receiverList.add(item); + } + return receiverList; + } + + @Test + public void TestCreateStandardSpanReceivers() { + List<SpanReceiver> receivers; + receivers = createSpanReceivers(""); + Assert.assertTrue(receivers.isEmpty()); + receivers = createSpanReceivers("POJOSpanReceiver"); + Assert.assertTrue(receivers.get(0).getClass().getName(). + equals("org.apache.htrace.core.POJOSpanReceiver")); + receivers = createSpanReceivers( + "org.apache.htrace.core.StandardOutSpanReceiver"); + Assert.assertTrue(receivers.get(0).getClass().getName(). + equals("org.apache.htrace.core.StandardOutSpanReceiver")); + receivers = createSpanReceivers( + "POJOSpanReceiver;StandardOutSpanReceiver"); + Assert.assertEquals(2, receivers.size()); + for (Iterator<SpanReceiver> iter = receivers.iterator(); iter.hasNext();) { + SpanReceiver receiver = iter.next(); + if (receiver.getClass().getName().equals( + "org.apache.htrace.core.POJOSpanReceiver")) { + iter.remove(); + break; + } + } + for (Iterator<SpanReceiver> iter = receivers.iterator(); iter.hasNext();) { + SpanReceiver receiver = iter.next(); + if (receiver.getClass().getName().equals( + "org.apache.htrace.core.StandardOutSpanReceiver")) { + iter.remove(); + break; + } + } + Assert.assertEquals(0, receivers.size()); + } + + public static class GoodSpanReceiver extends SpanReceiver { + public GoodSpanReceiver(HTraceConfiguration conf) { + } + + @Override + public void receiveSpan(Span span) { + } + + @Override + public void close() throws IOException { + } + } + + public static class BadSpanReceiver extends SpanReceiver { + public BadSpanReceiver(HTraceConfiguration conf) { + throw new RuntimeException("Can't create BadSpanReceiver"); + } + + @Override + public void receiveSpan(Span span) { + } + + @Override + public void close() throws IOException { + } + } + + /** + * Test trying to create a SpanReceiver that experiences an error in the + * constructor. + */ + @Test + public void testGetSpanReceiverWithConstructorError() throws Exception { + List<SpanReceiver> receivers; + receivers = createSpanReceivers( + GoodSpanReceiver.class.getName()); + Assert.assertEquals(1, receivers.size()); + Assert.assertTrue(receivers.get(0).getClass().getName(). + contains("GoodSpanReceiver")); + receivers = createSpanReceivers( + BadSpanReceiver.class.getName()); + Assert.assertEquals(0, receivers.size()); + } +}
