You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@htrace.apache.org by cm...@apache.org on 2015/09/20 06:12:06 UTC
[2/5] incubator-htrace git commit: HTRACE-259. Rename htrace-core
module to htrace-core4 to match the artifactId (stack via cmccabe)
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());
+ }
+}