diff --git a/api/src/main/java/org/teiid/logging/LogManager.java b/api/src/main/java/org/teiid/logging/LogManager.java index 621194bcf3..1044476adb 100644 --- a/api/src/main/java/org/teiid/logging/LogManager.java +++ b/api/src/main/java/org/teiid/logging/LogManager.java @@ -374,14 +374,16 @@ public static void log(int msgLevel, String context, Throwable e, Object... mess logListener.log(msgLevel, context, e, message); } - public static void setLogListener(Logger listener) { - logListener.shutdown(); + public static Logger setLogListener(Logger listener) { + Logger old = logListener; + logListener.shutdown(); if (listener != null) { logListener = listener; } else { logListener = new JavaLogger(); } + return old; } /** diff --git a/client/pom.xml b/client/pom.xml index e1c2d84b8e..632ffacfb7 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -67,6 +67,30 @@ org.jboss.modules jboss-modules + + + io.opentracing.contrib + opentracing-concurrent + + + + io.opentracing + opentracing-api + + + + io.opentracing + opentracing-noop + + + + io.opentracing + opentracing-util + + + io.opentracing + opentracing-mock + test \ No newline at end of file diff --git a/client/src/main/java/org/teiid/client/RequestMessage.java b/client/src/main/java/org/teiid/client/RequestMessage.java index 6832881357..0491e82950 100644 --- a/client/src/main/java/org/teiid/client/RequestMessage.java +++ b/client/src/main/java/org/teiid/client/RequestMessage.java @@ -27,7 +27,9 @@ import java.io.Serializable; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.teiid.core.TeiidProcessingException; import org.teiid.core.util.ExternalizeUtil; @@ -40,6 +42,8 @@ */ public class RequestMessage implements Externalizable { + private static final String SPAN_CONTEXT = "spanContext"; //$NON-NLS-1$ + private static final RequestOptions DEFAULT_REQUEST_OPTIONS = new RequestOptions(); public static final int DEFAULT_FETCH_SIZE = 2048; @@ -98,6 +102,8 @@ public enum ShowPlan { private boolean sync; private RequestOptions requestOptions; private Object command; + + private Map properties; public RequestMessage() { } @@ -370,6 +376,7 @@ public void readExternal(ObjectInput in) throws IOException, this.autoGeneratedKeys = (options & 1) == 1; //8.4 property this.delaySerialization = (options & 2) == 2; + this.properties = ExternalizeUtil.readMap(in); } catch (OptionalDataException e) { } catch (EOFException e) { } @@ -405,6 +412,7 @@ public void writeExternal(ObjectOutput out) throws IOException { options |= 2; } out.writeByte(options); + ExternalizeUtil.writeMap(out, properties); } public RequestOptions getRequestOptions() { @@ -433,5 +441,23 @@ public boolean isDelaySerialization() { public void setDelaySerialization(boolean delaySerialization) { this.delaySerialization = delaySerialization; } + + public void setSpanContext(String string) { + if (this.properties == null) { + this.properties = new HashMap(); + } + if (string == null) { + this.properties.remove(SPAN_CONTEXT); + } else { + properties.put(SPAN_CONTEXT, string); + } + } + + public String getSpanContext() { + if (this.properties == null) { + return null; + } + return this.properties.get(SPAN_CONTEXT); + } } diff --git a/client/src/main/java/org/teiid/jdbc/StatementImpl.java b/client/src/main/java/org/teiid/jdbc/StatementImpl.java index 8be8d378cd..b5fc22792b 100644 --- a/client/src/main/java/org/teiid/jdbc/StatementImpl.java +++ b/client/src/main/java/org/teiid/jdbc/StatementImpl.java @@ -55,6 +55,7 @@ import org.teiid.core.util.SqlUtil; import org.teiid.core.util.StringUtil; import org.teiid.jdbc.EnhancedTimer.Task; +import org.teiid.jdbc.tracing.TracingHelper; import org.teiid.net.TeiidURL; @@ -548,6 +549,12 @@ public void onCompletion(ResultsFuture future) { final RequestMessage reqMessage = createRequestMessage(commands, isBatchedCommand, resultsMode); reqMessage.setReturnAutoGeneratedKeys(autoGenerateKeys); reqMessage.setRequestOptions(options); + if (this.driverConnection.getServerConnection() != null && !this.driverConnection.getServerConnection().isLocal()) { + String spanContext = TracingHelper.getSpanContext(); + if (spanContext != null) { + reqMessage.setSpanContext(spanContext); + } + } ResultsFuture pendingResult = execute(reqMessage, synch); final ResultsFuture result = new ResultsFuture(); pendingResult.addCompletionListener(new ResultsFuture.CompletionListener() { diff --git a/client/src/main/java/org/teiid/jdbc/tracing/GlobalTracerInjector.java b/client/src/main/java/org/teiid/jdbc/tracing/GlobalTracerInjector.java new file mode 100644 index 0000000000..410b585ae5 --- /dev/null +++ b/client/src/main/java/org/teiid/jdbc/tracing/GlobalTracerInjector.java @@ -0,0 +1,83 @@ +/* + * Copyright Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags and + * the COPYRIGHT.txt file distributed with this work. + * + * Licensed 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.teiid.jdbc.tracing; + +import java.util.HashMap; +import java.util.Map; + +import org.teiid.jdbc.tracing.TracingHelper.Injector; + +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.propagation.Format.Builtin; +import io.opentracing.propagation.TextMapInjectAdapter; +import io.opentracing.util.GlobalTracer; + +/** + * Uses the opentracing library to create a json string representation of the span context + * and provides a way to manipulate a static tracer without using the GlobalTracer registration + */ +public class GlobalTracerInjector implements Injector { + + private static Tracer TRACER = GlobalTracer.get(); + + @Override + public String getSpanContext() { + return getSpanContext(TRACER); + } + + protected static String getSpanContext(Tracer tracer) { + Span span = tracer.activeSpan(); + if (span == null) { + return null; + } + Map spanMap = new HashMap(); + tracer.inject(span.context(), Builtin.TEXT_MAP, new TextMapInjectAdapter(spanMap)); + + //simple json creation + StringBuilder json = new StringBuilder(); + json.append('{'); + boolean first = true; + for (Map.Entry entry : spanMap.entrySet()) { + if (!first) { + json.append(','); + } else { + first = false; + } + json.append('"').append(entry.getKey().replace("\"", "\\\"")) //$NON-NLS-1$ //$NON-NLS-2$ + .append("\":\"") //$NON-NLS-1$ + .append(entry.getValue().replace("\"", "\\\"")).append('"'); //$NON-NLS-1$ //$NON-NLS-2$ + } + json.append('}'); + return json.toString(); + } + + /* + * Used to workaround that the GlobalTracer can only be registered once. + */ + + public static Tracer getTracer() { + return TRACER; + } + + public static void setTracer(Tracer tracer) { + TRACER = tracer; + } + +} diff --git a/client/src/main/java/org/teiid/jdbc/tracing/TracingHelper.java b/client/src/main/java/org/teiid/jdbc/tracing/TracingHelper.java new file mode 100644 index 0000000000..a79a1d00be --- /dev/null +++ b/client/src/main/java/org/teiid/jdbc/tracing/TracingHelper.java @@ -0,0 +1,59 @@ +/* + * Copyright Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags and + * the COPYRIGHT.txt file distributed with this work. + * + * Licensed 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.teiid.jdbc.tracing; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.teiid.core.util.ReflectionHelper; + +/** + * Used to reflection load logic that is dependent upon the opentracing library, or provide a dummy implementation + */ +public class TracingHelper { + + public interface Injector { + String getSpanContext(); + } + + private static Logger logger = Logger.getLogger("org.teiid.jdbc"); //$NON-NLS-1$ + + private static Injector INJECTOR; + + public static String getSpanContext() { + if (INJECTOR == null) { + try { + INJECTOR = (Injector) ReflectionHelper.create("org.teiid.jdbc.tracing.GlobalTracerInjector", null, TracingHelper.class.getClassLoader()); //$NON-NLS-1$ + } catch (Throwable e) { //must catch both Error and Exception + logger.log(Level.FINE, "Unable to load opentracing libraries, propagation will not be used", e); //$NON-NLS-1$ + } + if (INJECTOR == null) { + INJECTOR = new Injector() { + + @Override + public String getSpanContext() { + return null; + } + }; + } + } + return INJECTOR.getSpanContext(); + } + +} diff --git a/client/src/test/java/org/teiid/client/TestRequestMessage.java b/client/src/test/java/org/teiid/client/TestRequestMessage.java index 929ac351e6..2f141b9fa4 100644 --- a/client/src/test/java/org/teiid/client/TestRequestMessage.java +++ b/client/src/test/java/org/teiid/client/TestRequestMessage.java @@ -60,6 +60,7 @@ public static RequestMessage example() { message.setRowLimit(1313); message.setReturnAutoGeneratedKeys(true); message.setDelaySerialization(true); + message.setSpanContext("foo"); return message; } @@ -83,6 +84,7 @@ public static RequestMessage example() { assertEquals(1313, copy.getRowLimit()); assertTrue(copy.isReturnAutoGeneratedKeys()); assertTrue(copy.isDelaySerialization()); + assertEquals("foo", copy.getSpanContext()); } @Test public void testInvalidTxnAutoWrap() { diff --git a/client/src/test/java/org/teiid/jdbc/tracing/TestTracing.java b/client/src/test/java/org/teiid/jdbc/tracing/TestTracing.java new file mode 100644 index 0000000000..17d410a058 --- /dev/null +++ b/client/src/test/java/org/teiid/jdbc/tracing/TestTracing.java @@ -0,0 +1,42 @@ +/* + * Copyright Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags and + * the COPYRIGHT.txt file distributed with this work. + * + * Licensed 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.teiid.jdbc.tracing; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.opentracing.Scope; +import io.opentracing.mock.MockTracer; + +@SuppressWarnings("nls") +public class TestTracing { + + @Test public void testSpanContextInjection() { + MockTracer tracer = new MockTracer(); + assertNull(GlobalTracerInjector.getSpanContext(tracer)); + Scope ignored = tracer.buildSpan("x").startActive(true); + try { + assertEquals("{\"spanid\":\"2\",\"traceid\":\"1\"}", GlobalTracerInjector.getSpanContext(tracer)); + } finally { + ignored.close(); + } + } + +} diff --git a/engine/pom.xml b/engine/pom.xml index 4ed81b9b72..64afd6da8a 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -145,7 +145,28 @@ com.io7m.xom xom - + + + io.opentracing.contrib + opentracing-concurrent + + + + io.opentracing + opentracing-api + + + + io.opentracing + opentracing-util + + + + io.opentracing + opentracing-mock + test + + diff --git a/engine/src/main/java/org/teiid/dqp/internal/datamgr/ConnectorManager.java b/engine/src/main/java/org/teiid/dqp/internal/datamgr/ConnectorManager.java index 083467f17b..3960724a3f 100644 --- a/engine/src/main/java/org/teiid/dqp/internal/datamgr/ConnectorManager.java +++ b/engine/src/main/java/org/teiid/dqp/internal/datamgr/ConnectorManager.java @@ -23,7 +23,9 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.naming.InitialContext; @@ -45,12 +47,18 @@ import org.teiid.query.optimizer.capabilities.BasicSourceCapabilities; import org.teiid.query.optimizer.capabilities.SourceCapabilities; import org.teiid.query.sql.lang.Command; +import org.teiid.query.util.TeiidTracingUtil; import org.teiid.query.validator.ValidatorReport; import org.teiid.resource.spi.WrappedConnection; import org.teiid.translator.ExecutionContext; import org.teiid.translator.ExecutionFactory; +import org.teiid.translator.Translator; import org.teiid.translator.TranslatorException; +import io.opentracing.Span; +import io.opentracing.log.Fields; +import io.opentracing.tag.Tags; + /** * The ConnectorManager manages an {@link ExecutionFactory} @@ -61,6 +69,7 @@ public class ConnectorManager { private static final String JAVA_CONTEXT = "java:/"; //$NON-NLS-1$ private final String translatorName; + private String translatorType; private final String connectionName; private final String jndiName; private final List id; @@ -94,6 +103,10 @@ public ConnectorManager(String translatorName, String connectionName, ExecutionF this.executionFactory = ef; this.id = Arrays.asList(translatorName, connectionName); if (ef != null) { + Translator annotation = ef.getClass().getAnnotation(Translator.class); + if (annotation != null) { + this.translatorType = annotation.name(); + } ClassLoader originalCL = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(ef.getClass().getClassLoader()); @@ -262,15 +275,15 @@ public void stop() { } } - void logSRCCommand(AtomicRequestMessage qr, ExecutionContext context, Event cmdStatus, Long finalRowCnt, Long cpuTime) { - logSRCCommand(qr, context, cmdStatus, finalRowCnt, cpuTime, null); + void logSRCCommand(ConnectorWorkItem cwi, AtomicRequestMessage qr, ExecutionContext context, Event cmdStatus, Long finalRowCnt, Long cpuTime) { + logSRCCommand(cwi, qr, context, cmdStatus, finalRowCnt, cpuTime, null); } /** * Add begin point to transaction monitoring table. * @param qr Request that contains the MetaMatrix command information in the transaction. */ - void logSRCCommand(AtomicRequestMessage qr, ExecutionContext context, Event cmdStatus, Long finalRowCnt, Long cpuTime, Object[] command) { + void logSRCCommand(ConnectorWorkItem cwi, AtomicRequestMessage qr, ExecutionContext context, Event cmdStatus, Long finalRowCnt, Long cpuTime, Object[] command) { if (!LogManager.isMessageToBeRecorded(LogConstants.CTX_COMMANDLOGGING_SOURCE, MessageLevel.DETAIL)) { return; } @@ -293,12 +306,40 @@ void logSRCCommand(AtomicRequestMessage qr, ExecutionContext context, Event cmdS CommandLogMessage message = null; if (cmdStatus == Event.NEW) { message = new CommandLogMessage(System.currentTimeMillis(), qr.getRequestID().toString(), sid.getNodeID(), transactionID, modelName, translatorName, qr.getWorkContext().getSessionId(), principal, sqlStr, context); + Span span = TeiidTracingUtil.getInstance().buildSourceSpan(message, translatorType); + cwi.setTracingSpan(span); } else { message = new CommandLogMessage(System.currentTimeMillis(), qr.getRequestID().toString(), sid.getNodeID(), transactionID, modelName, translatorName, qr.getWorkContext().getSessionId(), principal, finalRowCnt, cmdStatus, context, cpuTime); if (cmdStatus == Event.SOURCE) { message.setSourceCommand(command); } + Span span = cwi.getTracingSpan(); + if (span != null) { + switch (cmdStatus) { + case SOURCE: + if (command != null) { + Map map = new HashMap(); + map.put("source-command", Arrays.toString(command)); //$NON-NLS-1$ + span.log(map); + } + break; + case CANCEL: + span.log("cancel"); //$NON-NLS-1$ + break; + case END: + span.finish(); + break; + case ERROR: + Tags.ERROR.set(span, true); + Map map = new HashMap(); + map.put(Fields.EVENT, "error"); //$NON-NLS-1$ + span.log(map); + break; + default: + break; + } + } } LogManager.log(MessageLevel.DETAIL, LogConstants.CTX_COMMANDLOGGING_SOURCE, message); } diff --git a/engine/src/main/java/org/teiid/dqp/internal/datamgr/ConnectorWorkItem.java b/engine/src/main/java/org/teiid/dqp/internal/datamgr/ConnectorWorkItem.java index 6fd0253d87..84bc0dc45f 100644 --- a/engine/src/main/java/org/teiid/dqp/internal/datamgr/ConnectorWorkItem.java +++ b/engine/src/main/java/org/teiid/dqp/internal/datamgr/ConnectorWorkItem.java @@ -74,11 +74,15 @@ import org.teiid.query.sql.lang.StoredProcedure; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.util.CommandContext; +import org.teiid.query.util.TeiidTracingUtil; import org.teiid.resource.spi.WrappedConnection; import org.teiid.translator.*; import org.teiid.translator.ExecutionFactory.TransactionSupport; import org.teiid.util.XMLInputStream; +import io.opentracing.Scope; +import io.opentracing.Span; + public class ConnectorWorkItem implements ConnectorWork { /* Permanent state members */ @@ -123,6 +127,8 @@ public class ConnectorWorkItem implements ConnectorWork { private boolean unmodifiableList; + private Span span; + ConnectorWorkItem(AtomicRequestMessage message, ConnectorManager manager) throws TeiidComponentException { this.id = message.getAtomicRequestID(); this.requestMsg = message; @@ -201,7 +207,7 @@ public void cancel(boolean abnormal) { Execution ex = this.execution; if(ex != null) { if (abnormal) { - this.manager.logSRCCommand(this.requestMsg, this.securityContext, Event.CANCEL, -1l, null); + this.manager.logSRCCommand(this, this.requestMsg, this.securityContext, Event.CANCEL, -1l, null); } ex.cancel(); LogManager.logDetail(LogConstants.CTX_CONNECTOR, QueryPlugin.Util.getString("DQPCore.The_atomic_request_has_been_cancelled", this.id)); //$NON-NLS-1$ @@ -226,13 +232,20 @@ public synchronized AtomicResultsMessage more() throws TranslatorException { throw handleError(this.conversionError); } LogManager.logDetail(LogConstants.CTX_CONNECTOR, new Object[] {this.id, "Processing MORE request"}); //$NON-NLS-1$ + Scope scope = null; try { timer.start(); + if (span != null) { + scope = TeiidTracingUtil.getInstance().activateSpan(span); + } return handleBatch(); } catch (Throwable t) { throw handleError(t); } finally { timer.stop(); + if (scope != null) { + scope.close(); + } } } @@ -246,8 +259,12 @@ public synchronized void close() { return; //already closed } LogManager.logDetail(LogConstants.CTX_CONNECTOR, new Object[] {this.id, "Processing Close :", this.requestMsg.getCommand()}); //$NON-NLS-1$ + Scope scope = null; try { timer.start(); + if (this.span != null) { + scope = TeiidTracingUtil.getInstance().activateSpan(this.span); + } if (execution != null) { execution.close(); LogManager.logDetail(LogConstants.CTX_CONNECTOR, new Object[] {this.id, "Closed execution"}); //$NON-NLS-1$ @@ -268,7 +285,10 @@ public synchronized void close() { LogManager.logDetail(LogConstants.CTX_CONNECTOR, new Object[] {this.id, "Closed connection"}); //$NON-NLS-1$ } Long time = timer.stop(); - manager.logSRCCommand(this.requestMsg, this.securityContext, Event.END, this.rowCount, time); + manager.logSRCCommand(this, this.requestMsg, this.securityContext, Event.END, this.rowCount, time); + if (scope != null) { + scope.close(); + } } } @@ -284,7 +304,7 @@ private TranslatorException handleError(Throwable t) { if (isCancelled.get()) { LogManager.logDetail(LogConstants.CTX_CONNECTOR, msg); } else { - manager.logSRCCommand(this.requestMsg, this.securityContext, Event.ERROR, null, null); + manager.logSRCCommand(this, this.requestMsg, this.securityContext, Event.ERROR, null, null); Throwable toLog = t; if (this.requestMsg.getCommandContext().getOptions().isSanitizeMessages() && !LogManager.isMessageToBeRecorded(LogConstants.CTX_CONNECTOR, MessageLevel.DETAIL)) { @@ -306,6 +326,7 @@ public synchronized void execute() throws TranslatorException { if(isCancelled()) { throw new TranslatorException(QueryPlugin.Event.TEIID30476, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30476)); } + Scope scope = null; timer.start(); try { if (this.execution == null) { @@ -352,7 +373,10 @@ public synchronized void execute() throws TranslatorException { LogManager.logDetail(LogConstants.CTX_CONNECTOR, new Object[] {this.requestMsg.getAtomicRequestID(), "Obtained execution"}); //$NON-NLS-1$ //Log the Source Command (Must be after obtaining the execution context) - manager.logSRCCommand(this.requestMsg, this.securityContext, Event.NEW, null, null); + manager.logSRCCommand(this, this.requestMsg, this.securityContext, Event.NEW, null, null); + if (this.span != null) { + scope = TeiidTracingUtil.getInstance().activateSpan(this.span); + } } // Execute query this.execution.execute(); @@ -361,6 +385,9 @@ public synchronized void execute() throws TranslatorException { throw handleError(t); } finally { timer.stop(); + if (scope != null) { + scope.close(); + } } } @@ -770,7 +797,15 @@ public boolean equals(Object obj) { } public void logCommand(Object... command) { - this.manager.logSRCCommand(this.requestMsg, securityContext, Event.SOURCE, null, null, command); + this.manager.logSRCCommand(this, this.requestMsg, securityContext, Event.SOURCE, null, null, command); } + public void setTracingSpan(Span span) { + this.span = span; + } + + public Span getTracingSpan() { + return span; + } + } \ No newline at end of file diff --git a/engine/src/main/java/org/teiid/dqp/internal/process/DQPConfiguration.java b/engine/src/main/java/org/teiid/dqp/internal/process/DQPConfiguration.java index a0ead196ac..bd602e6cc4 100644 --- a/engine/src/main/java/org/teiid/dqp/internal/process/DQPConfiguration.java +++ b/engine/src/main/java/org/teiid/dqp/internal/process/DQPConfiguration.java @@ -22,8 +22,11 @@ import org.teiid.PreParser; import org.teiid.client.RequestMessage; import org.teiid.core.util.PropertiesUtils; +import org.teiid.jdbc.tracing.GlobalTracerInjector; import org.teiid.query.util.Options; +import io.opentracing.Tracer; +import io.opentracing.contrib.concurrent.TracedRunnable; public class DQPConfiguration{ @@ -176,7 +179,14 @@ public long getQueryTimeout() { } public TeiidExecutor getTeiidExecutor() { - return new ThreadReuseExecutor(DQPConfiguration.PROCESS_PLAN_QUEUE_NAME, getMaxThreads()); + return new ThreadReuseExecutor(DQPConfiguration.PROCESS_PLAN_QUEUE_NAME, getMaxThreads()) { + Tracer tracer = GlobalTracerInjector.getTracer(); + @Override + public void execute(Runnable command) { + super.execute(tracer.activeSpan() == null ? command : + new TracedRunnable(command, tracer)); + } + }; } public void setPreParser(PreParser preParser) { diff --git a/engine/src/main/java/org/teiid/dqp/internal/process/DQPCore.java b/engine/src/main/java/org/teiid/dqp/internal/process/DQPCore.java index 47dfdc8076..f4ecccd4d8 100644 --- a/engine/src/main/java/org/teiid/dqp/internal/process/DQPCore.java +++ b/engine/src/main/java/org/teiid/dqp/internal/process/DQPCore.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -72,6 +73,7 @@ import org.teiid.events.EventDistributor; import org.teiid.jdbc.EnhancedTimer; import org.teiid.jdbc.LocalProfile; +import org.teiid.jdbc.tracing.GlobalTracerInjector; import org.teiid.logging.CommandLogMessage; import org.teiid.logging.CommandLogMessage.Event; import org.teiid.logging.LogConstants; @@ -85,6 +87,12 @@ import org.teiid.query.tempdata.TempTableStore.TransactionMode; import org.teiid.query.util.CommandContext; import org.teiid.query.util.Options; +import org.teiid.query.util.TeiidTracingUtil; + +import io.opentracing.Span; +import io.opentracing.contrib.concurrent.TracedExecutorService; +import io.opentracing.log.Fields; +import io.opentracing.tag.Tags; /** * Implements the core DQP processing. @@ -602,6 +610,7 @@ void logMMCommand(RequestWorkItem workItem, Event status, Long rowCount, Long cp CommandLogMessage message = null; if (status == Event.NEW) { message = new CommandLogMessage(System.currentTimeMillis(), rID.toString(), txnID, workContext.getSessionId(), appName, workContext.getUserName(), workContext.getVdbName(), workContext.getVdbVersion(), msg.getCommandString(), cpuTime); + workItem.setTracingSpan(TeiidTracingUtil.getInstance().buildSpan(message, msg.getSpanContext())); } else { QueryProcessor qp = workItem.getProcessor(); PlanNode plan = null; @@ -609,6 +618,29 @@ void logMMCommand(RequestWorkItem workItem, Event status, Long rowCount, Long cp plan = qp.getProcessorPlan().getDescriptionProperties(); } message = new CommandLogMessage(System.currentTimeMillis(), rID.toString(), txnID, workContext.getSessionId(), workContext.getUserName(), workContext.getVdbName(), workContext.getVdbVersion(), rowCount, status, plan); + Span span = workItem.getTracingSpan(); + if (span != null) { + switch (status) { + case PLAN: + span.log("planning complete"); //$NON-NLS-1$ + break; + case CANCEL: + span.log("cancel"); //$NON-NLS-1$ + break; + case END: + span.finish(); + break; + case ERROR: + Tags.ERROR.set(span, true); + Map map = new HashMap(); + map.put(Fields.EVENT, "error"); //$NON-NLS-1$ + span.log(map); + break; + default: + //nothing + break; + } + } } LogManager.log(status == Event.PLAN?MessageLevel.TRACE:MessageLevel.DETAIL, LogConstants.CTX_COMMANDLOGGING, message); } @@ -645,7 +677,7 @@ public void start(DQPConfiguration theConfig) { this.processWorkerPool = config.getTeiidExecutor(); //we don't want cancellations waiting on normal processing, so they get a small dedicated pool //TODO: overflow to the worker pool - timeoutExecutor = ExecutorUtils.newFixedThreadPool(3, "Server Side Timeout"); //$NON-NLS-1$ + timeoutExecutor = new TracedExecutorService(ExecutorUtils.newFixedThreadPool(3, "Server Side Timeout"), GlobalTracerInjector.getTracer()); //$NON-NLS-1$ this.cancellationTimer = new EnhancedTimer(timeoutExecutor, timeoutExecutor); this.maxActivePlans = config.getMaxActivePlans(); diff --git a/engine/src/main/java/org/teiid/dqp/internal/process/RequestWorkItem.java b/engine/src/main/java/org/teiid/dqp/internal/process/RequestWorkItem.java index 0dfe649be1..dcb1a4f67c 100644 --- a/engine/src/main/java/org/teiid/dqp/internal/process/RequestWorkItem.java +++ b/engine/src/main/java/org/teiid/dqp/internal/process/RequestWorkItem.java @@ -90,6 +90,9 @@ import org.teiid.query.util.CommandContext; import org.teiid.query.util.GeneratedKeysImpl; import org.teiid.query.util.Options; +import org.teiid.query.util.TeiidTracingUtil; + +import io.opentracing.Span; /** * Compiles results and other information for the client. There is quite a bit of logic @@ -228,6 +231,8 @@ private enum TransactionState {NONE, ACTIVE, DONE} private long planningEnd; private ThreadCpuTimer timer = new ThreadCpuTimer(); + + private Span span; public RequestWorkItem(DQPCore dqpCore, RequestMessage requestMsg, Request request, ResultsReceiver receiver, RequestID requestID, DQPWorkContext workContext) { this.requestMsg = requestMsg; @@ -268,6 +273,10 @@ protected boolean isDoneProcessing() { @Override public void run() { + io.opentracing.Scope scope = null; + if (this.span != null) { + scope = TeiidTracingUtil.getInstance().activateSpan(this.span); + } hasThread = true; timer.start(); LogManager.putMdc(REQUEST_KEY, requestID.toString()); @@ -300,6 +309,9 @@ public void run() { timer.stop(); LogManager.removeMdc(REQUEST_KEY); hasThread = false; + if (scope != null) { + scope.close(); + } } } @@ -1256,6 +1268,9 @@ public boolean isCloseRequested() { } public void requestMore(int batchFirst, int batchLast, ResultsReceiver receiver) { + if (span != null) { + span.log("requested more results"); //$NON-NLS-1$ + } this.requestResults(batchFirst, batchLast, receiver); this.doMoreWork(); } @@ -1401,4 +1416,12 @@ public RequestID getRequestID() { return requestID; } + public void setTracingSpan(Span span) { + this.span = span; + } + + public Span getTracingSpan() { + return this.span; + } + } \ No newline at end of file diff --git a/engine/src/main/java/org/teiid/query/util/TeiidTracingUtil.java b/engine/src/main/java/org/teiid/query/util/TeiidTracingUtil.java new file mode 100644 index 0000000000..c0cd41ed82 --- /dev/null +++ b/engine/src/main/java/org/teiid/query/util/TeiidTracingUtil.java @@ -0,0 +1,148 @@ +/* + * Copyright 2017-2018 The OpenTracing Authors + * Copyright Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags and + * the COPYRIGHT.txt file distributed with this work. + * + * Licensed 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.teiid.query.util; + +import java.util.Map; + +import org.teiid.jdbc.tracing.GlobalTracerInjector; +import org.teiid.json.simple.JSONParser; +import org.teiid.json.simple.ParseException; +import org.teiid.json.simple.SimpleContentHandler; +import org.teiid.logging.CommandLogMessage; +import org.teiid.logging.LogConstants; +import org.teiid.logging.LogManager; + +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.SpanContext; +import io.opentracing.Tracer; +import io.opentracing.propagation.Format.Builtin; +import io.opentracing.propagation.TextMapExtractAdapter; +import io.opentracing.tag.Tags; + +public class TeiidTracingUtil { + + private boolean withActiveSpanOnly; + private Tracer tracer = GlobalTracerInjector.getTracer(); + + private static TeiidTracingUtil INSTANCE = new TeiidTracingUtil(); + + public static TeiidTracingUtil getInstance() { + INSTANCE.tracer = GlobalTracerInjector.getTracer(); + return INSTANCE; + } + + /** + * For use by tests - GlobalTracer is not directly test friendly as the registration can only happen once. + */ + void setTracer(Tracer tracer) { + this.tracer = tracer; + } + + public void setWithActiveSpanOnly(boolean withActiveSpanOnly) { + this.withActiveSpanOnly = withActiveSpanOnly; + } + + /** + * Build a {@link Span} from the {@link CommandLogMessage} and incoming span context + * @param message + * @param spanContextJson + * @return + */ + public Span buildSpan(CommandLogMessage msg, String spanContextJson) { + if (withActiveSpanOnly && tracer.activeSpan() == null && spanContextJson == null) { + return null; + } + + Tracer.SpanBuilder spanBuilder = tracer + .buildSpan("USER COMMAND") //$NON-NLS-1$ + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER); + + if (spanContextJson != null) { + SpanContext parent = extractSpanContext(spanContextJson); + if (parent != null) { + spanBuilder.asChildOf(parent); + } else if (withActiveSpanOnly) { + return null; + } + } + + Span span = spanBuilder.start(); + + Tags.COMPONENT.set(span, "java-teiid"); //$NON-NLS-1$ + + Tags.DB_STATEMENT.set(span, msg.getSql()); + Tags.DB_TYPE.set(span, "teiid"); //$NON-NLS-1$ + Tags.DB_INSTANCE.set(span, msg.getVdbName()); + Tags.DB_USER.set(span, msg.getPrincipal()); + + span.setTag("teiid-session", msg.getSessionID()); //$NON-NLS-1$ + span.setTag("teiid-request", msg.getRequestID()); //$NON-NLS-1$ + + return span; + } + + /** + * Build a {@link Span} from the {@link CommandLogMessage} and translator type + * @param msg + * @param translatorType + * @return + */ + public Span buildSourceSpan(CommandLogMessage msg, String translatorType) { + if (tracer.activeSpan() == null) { + return null; + } + + Tracer.SpanBuilder spanBuilder = tracer + .buildSpan("SRC COMMAND") //$NON-NLS-1$ + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT); + + Span span = spanBuilder.start(); + + Tags.COMPONENT.set(span, "java-teiid-connector"); //$NON-NLS-1$ + + Tags.DB_STATEMENT.set(span, msg.getSql()); + Tags.DB_TYPE.set(span, translatorType); + Tags.DB_USER.set(span, msg.getPrincipal()); + + span.setTag("teiid-source-request", msg.getSourceCommandID()); //$NON-NLS-1$ + + return span; + } + + public Scope activateSpan(Span span) { + if (tracer.activeSpan() == span) { + //when a workitem adds itself to a queue the span will already be active + return null; + } + return tracer.scopeManager().activate(span, false); + } + + protected SpanContext extractSpanContext(String spanContextJson) { + try { + JSONParser parser = new JSONParser(); + SimpleContentHandler sch = new SimpleContentHandler(); + parser.parse(spanContextJson, sch); + Map result = (Map) sch.getResult(); + return tracer.extract(Builtin.TEXT_MAP, new TextMapExtractAdapter(result)); + } catch (IllegalArgumentException | ClassCastException | ParseException e) { + LogManager.logDetail(LogConstants.CTX_DQP, e, "Could not extract the span context"); //$NON-NLS-1$ + return null; + } + } + +} \ No newline at end of file diff --git a/engine/src/test/java/org/teiid/query/util/TestTeiidTracingUtil.java b/engine/src/test/java/org/teiid/query/util/TestTeiidTracingUtil.java new file mode 100644 index 0000000000..587a257956 --- /dev/null +++ b/engine/src/test/java/org/teiid/query/util/TestTeiidTracingUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags and + * the COPYRIGHT.txt file distributed with this work. + * + * Licensed 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.teiid.query.util; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.teiid.logging.CommandLogMessage; + +import io.opentracing.Span; +import io.opentracing.SpanContext; +import io.opentracing.mock.MockTracer; + +@SuppressWarnings("nls") +public class TestTeiidTracingUtil { + + @Test public void testTracing() { + TeiidTracingUtil ttu = new TeiidTracingUtil(); + MockTracer tracer = new MockTracer(); + ttu.setTracer(tracer); + assertNotNull(ttu.extractSpanContext("{\"spanid\":\"1\",\"traceid\":\"2\"}")); + SpanContext spanContext = ttu.extractSpanContext("corrupted"); + assertNull(spanContext); + assertNull(tracer.activeSpan()); //should be null, no side effect just from extract + + Span span = ttu.buildSpan(new CommandLogMessage(0, "", null, null, null, null, null, null, "", null), null); + assertNotNull(span); + + span = ttu.buildSpan(new CommandLogMessage(0, "", null, null, null, null, null, null, "", null), "{\"spanid\":\"1\",\"traceid\":\"2\"}"); + assertNotNull(span); + } + +} diff --git a/pom.xml b/pom.xml index 85f6e0288c..10c66aa861 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,9 @@ into a new release of the bom. --> + 0.1.0 + 0.31.0 + scm:git:git://github.com/teiid/teiid.git @@ -612,6 +615,38 @@ teiid-data-quality ${project.version} + + + io.opentracing.contrib + opentracing-concurrent + ${version.opentracing.concurrent} + + + + io.opentracing + opentracing-api + ${version.opentracing} + + + + io.opentracing + opentracing-noop + ${version.opentracing} + + + + io.opentracing + opentracing-util + ${version.opentracing} + + + + io.opentracing + opentracing-mock + ${version.opentracing} + test + + diff --git a/runtime/pom.xml b/runtime/pom.xml index 1662a576c5..f5ad89f4c6 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -137,16 +137,10 @@ picketbox test - diff --git a/runtime/src/test/java/org/teiid/runtime/TestEmbeddedServer.java b/runtime/src/test/java/org/teiid/runtime/TestEmbeddedServer.java index 7d82e164b7..353f7ffd94 100644 --- a/runtime/src/test/java/org/teiid/runtime/TestEmbeddedServer.java +++ b/runtime/src/test/java/org/teiid/runtime/TestEmbeddedServer.java @@ -76,10 +76,14 @@ import org.teiid.jdbc.SQLStates; import org.teiid.jdbc.TeiidDriver; import org.teiid.jdbc.TeiidSQLException; +import org.teiid.jdbc.tracing.GlobalTracerInjector; import org.teiid.language.Command; import org.teiid.language.Literal; import org.teiid.language.QueryExpression; import org.teiid.language.visitor.CollectorVisitor; +import org.teiid.logging.LogConstants; +import org.teiid.logging.Logger; +import org.teiid.logging.MessageLevel; import org.teiid.metadata.Column; import org.teiid.metadata.MetadataException; import org.teiid.metadata.MetadataFactory; @@ -87,6 +91,7 @@ import org.teiid.metadata.RuntimeMetadata; import org.teiid.metadata.Table; import org.teiid.query.sql.symbol.Reference; +import org.teiid.query.util.TeiidTracingUtil; import org.teiid.runtime.EmbeddedServer.ConnectionFactoryProvider; import org.teiid.translator.DataNotAvailableException; import org.teiid.translator.ExecutionContext; @@ -101,6 +106,11 @@ import org.teiid.transport.SocketConfiguration; import org.teiid.transport.WireProtocol; +import io.opentracing.Scope; +import io.opentracing.mock.MockSpan; +import io.opentracing.mock.MockTracer; +import io.opentracing.util.GlobalTracer; + @SuppressWarnings("nls") public class TestEmbeddedServer { @Translator (name="y") @@ -2470,5 +2480,95 @@ public void loadMetadata(MetadataFactory factory, rs.close(); assertEquals(100, count); } + + @Test public void testOpenTracing() throws Exception { + MockTracer tracer = new MockTracer(); + GlobalTracerInjector.setTracer(tracer); + TeiidTracingUtil.getInstance().setWithActiveSpanOnly(true); + Logger logger = Mockito.mock(Logger.class); + Mockito.stub(logger.isEnabled(LogConstants.CTX_COMMANDLOGGING, MessageLevel.DETAIL)).toReturn(true); + Mockito.stub(logger.isEnabled(LogConstants.CTX_COMMANDLOGGING_SOURCE, MessageLevel.DETAIL)).toReturn(true); + Logger old = org.teiid.logging.LogManager.setLogListener(logger); + try { + SocketConfiguration s = new SocketConfiguration(); + InetSocketAddress addr = new InetSocketAddress(0); + s.setBindAddress(addr.getHostName()); + s.setPortNumber(addr.getPort()); + s.setProtocol(WireProtocol.teiid); + EmbeddedConfiguration config = new EmbeddedConfiguration(); + config.addTransport(s); + es.start(config); + + HardCodedExecutionFactory hcef = new HardCodedExecutionFactory(); + hcef.addData("SELECT t1.col_t1 FROM t1", Arrays.asList(Arrays.asList("a"))); + hcef.addData("SELECT t2.col_t2 FROM t2", Arrays.asList(Arrays.asList("b"))); + es.addTranslator("y", hcef); + + ModelMetaData mmd = new ModelMetaData(); + mmd.setName("y"); + mmd.addSourceMetadata("ddl", "create foreign table t1(col_t1 varchar) options (cardinality 20); " + + "create foreign table t2(col_t2 varchar) options (cardinality 20);"); + mmd.addSourceMapping("y", "y", null); + es.deployVDB("x", mmd); + Connection c = es.getDriver().connect("jdbc:teiid:x;", null); + Statement stmt = c.createStatement(); + + ResultSet rs = stmt.executeQuery("select * from t1 union all select * from t2"); + while (rs.next()) { + + } + stmt.close(); + + List spans = tracer.finishedSpans(); + assertEquals(0, spans.size()); + + try (Scope ignored = tracer.buildSpan("some operation").startActive(true)) { + assertNotNull(tracer.activeSpan()); + stmt = c.createStatement(); + //execute with an active span + rs = stmt.executeQuery("select * from t1 union all select * from t2"); + while (rs.next()) { + + } + stmt.close(); + } + + spans = tracer.finishedSpans(); + + //parent span started here, and a child span for the query execution, 2 source queries + assertEquals(spans.toString(), 4, spans.size()); + + tracer.reset(); + + //remote propagation + Connection remote = TeiidDriver.getInstance().connect("jdbc:teiid:x@mm://"+addr.getHostName()+":"+es.transports.get(0).getPort(), null); + + try (Scope ignored = tracer.buildSpan("some remote operation").startActive(true)) { + assertNotNull(tracer.activeSpan()); + stmt = remote.createStatement(); + //execute with an active span + rs = stmt.executeQuery("select * from t1 union all select * from t2"); + while (rs.next()) { + + } + stmt.close(); + } + + //this isn't ideal, but close is an async event + for (int i = 0; i < 1000; i++) { + spans = tracer.finishedSpans(); + if (spans.size() == 2) { + break; + } + Thread.sleep(10); + } + + //parent span started here, and a child span for the query execution, 2 source queries + assertEquals(4, spans.size()); + } finally { + GlobalTracerInjector.setTracer(GlobalTracer.get()); + org.teiid.logging.LogManager.setLogListener(old); + } + } } diff --git a/settings.xml b/settings.xml index 193e82e2d8..8c29eee191 100644 --- a/settings.xml +++ b/settings.xml @@ -6,6 +6,15 @@ http://maven.apache.org/xsd/settings-1.0.0.xsd"> jboss-public-repository + + central + Maven Central + http://repo.maven.apache.org/maven2/ + + false + never + + jboss-public-repository-group JBoss Public Maven Repository Group diff --git a/teiid-feature-pack/client-feature-pack/src/main/resources/modules/system/layers/dv/io/opentracing/main/module.xml b/teiid-feature-pack/client-feature-pack/src/main/resources/modules/system/layers/dv/io/opentracing/main/module.xml new file mode 100644 index 0000000000..478e938f2a --- /dev/null +++ b/teiid-feature-pack/client-feature-pack/src/main/resources/modules/system/layers/dv/io/opentracing/main/module.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/teiid-feature-pack/client-feature-pack/src/main/resources/modules/system/layers/dv/org/jboss/teiid/client/main/module.xml b/teiid-feature-pack/client-feature-pack/src/main/resources/modules/system/layers/dv/org/jboss/teiid/client/main/module.xml index 5b455a22a7..b939fa2f83 100644 --- a/teiid-feature-pack/client-feature-pack/src/main/resources/modules/system/layers/dv/org/jboss/teiid/client/main/module.xml +++ b/teiid-feature-pack/client-feature-pack/src/main/resources/modules/system/layers/dv/org/jboss/teiid/client/main/module.xml @@ -16,5 +16,6 @@ + \ No newline at end of file diff --git a/teiid-feature-pack/wildfly-integration-feature-pack/src/main/resources/content/docs/teiid/teiid-releasenotes.html b/teiid-feature-pack/wildfly-integration-feature-pack/src/main/resources/content/docs/teiid/teiid-releasenotes.html index 73505dd74d..35bd98c827 100644 --- a/teiid-feature-pack/wildfly-integration-feature-pack/src/main/resources/content/docs/teiid/teiid-releasenotes.html +++ b/teiid-feature-pack/wildfly-integration-feature-pack/src/main/resources/content/docs/teiid/teiid-releasenotes.html @@ -28,6 +28,7 @@

Highlights

  • TEIID-5356 Improved column metadata with DDL views - and other metadata fixes such as TEIID-5361 and TEIID-5359
  • TEIID-3439 Added support for table aliases to contain a period '.' character
  • Various SQL compatibility issues to support JOQL
  • +
  • TEIIDTOOLS-437 OpenTracing integration for remote JDBC clients. Further automatic integration will be provided via Thorntail.
  • Compatibility Issues

    diff --git a/teiid-feature-pack/wildfly-integration-feature-pack/src/main/resources/modules/system/layers/dv/org/jboss/teiid/main/module.xml b/teiid-feature-pack/wildfly-integration-feature-pack/src/main/resources/modules/system/layers/dv/org/jboss/teiid/main/module.xml index c98644bb68..39ba9a734c 100644 --- a/teiid-feature-pack/wildfly-integration-feature-pack/src/main/resources/modules/system/layers/dv/org/jboss/teiid/main/module.xml +++ b/teiid-feature-pack/wildfly-integration-feature-pack/src/main/resources/modules/system/layers/dv/org/jboss/teiid/main/module.xml @@ -53,7 +53,6 @@ - @@ -67,5 +66,7 @@ + +