Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix automatic APM instrumentation for JS tracer. Add diagnostic logging to APM configurators. Update APM instrumentation docs #388

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ From a job specific configuration page:

### Test Visibility Configuration

The plugin can automatically configure Datadog [Test Visibility][19] for a job or a pipeline (see the Test Visibility [documentation for your language][20] to make sure that the testing framework that you use is supported).
The plugin can automatically configure Datadog [Test Visibility][19] for a job or a pipeline (see the Test Visibility [documentation for your language][20] to make sure that the testing framework that you use is supported; also note that automatic configuration is not supported for tests that are executed inside containers - follow [manual instrumentation steps][20] to enable Test Visibility for containerized test runs).

Before enabling Test Visibility, be sure to properly configure the plugin to submit data to Datadog.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,42 @@ public static boolean isMainNode(String nodeName) {
return "master".equalsIgnoreCase(nodeName) || "built-in".equalsIgnoreCase(nodeName);
}

public static String getNodeHostname(@Nullable EnvVars envVars, @Nullable Computer computer) {
if (envVars != null) {
String ddHostname = envVars.get(DatadogGlobalConfiguration.DD_CI_HOSTNAME);
if (DatadogUtilities.isValidHostname(ddHostname)) {
return ddHostname;
}
String hostname = envVars.get("HOSTNAME");
if (DatadogUtilities.isValidHostname(hostname)) {
return hostname;
}
}

try {
if(computer != null) {
String computerNodeName = DatadogUtilities.getNodeName(computer);
if (DatadogUtilities.isMainNode(computerNodeName)) {
String masterHostname = DatadogUtilities.getHostname(null);
if (DatadogUtilities.isValidHostname(masterHostname)) {
return masterHostname;
}
}

String computerHostName = computer.getHostName();
if (DatadogUtilities.isValidHostname(computerHostName)) {
return computerHostName;
}
}
} catch (InterruptedException e){
Thread.currentThread().interrupt();
logger.fine("Interrupted while trying to extract hostname from StepContext.");

} catch (IOException e){
logger.fine("Unable to extract hostname from StepContext.");
}
return null;
}

@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public static Set<String> getNodeLabels(Computer computer) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package org.datadog.jenkins.plugins.datadog.tracer;
package org.datadog.jenkins.plugins.datadog.apm;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.InvisibleAction;
import hudson.model.Computer;
import hudson.model.Job;
import hudson.model.Node;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import hudson.util.Secret;
import java.net.MalformedURLException;
Expand All @@ -18,16 +20,15 @@
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.datadog.jenkins.plugins.datadog.DatadogClient;
import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration;
import org.datadog.jenkins.plugins.datadog.DatadogUtilities;
import org.datadog.jenkins.plugins.datadog.model.DatadogPluginAction;

public class DatadogTracerConfigurator {

private static final Logger LOGGER = Logger.getLogger(DatadogTracerConfigurator.class.getName());

private final Map<TracerLanguage, TracerConfigurator> configurators;

public static final DatadogTracerConfigurator INSTANCE = new DatadogTracerConfigurator();
Expand All @@ -39,49 +40,51 @@ public DatadogTracerConfigurator() {
configurators.put(TracerLanguage.PYTHON, new PythonConfigurator());
}

public Map<String, String> configure(Run<?, ?> run, Node node, Map<String, String> envs) {
public Map<String, String> configure(Run<?, ?> run, Computer computer, Node node, EnvVars envs, TaskListener listener) {
Job<?, ?> job = run.getParent();
DatadogTracerJobProperty<?> tracerConfig = job.getProperty(DatadogTracerJobProperty.class);
if (tracerConfig == null || !tracerConfig.isOn()) {
return Collections.emptyMap();
}

String nodeHostname = DatadogUtilities.getNodeHostname(envs, computer);
Collection<TracerLanguage> languages = tracerConfig.getLanguages();
for (ConfigureTracerAction action : run.getActions(ConfigureTracerAction.class)) {
if (action.node == node && languages.equals(action.languages)) {
if (nodeHostname != null && nodeHostname.equals(action.nodeHostname) && action.languages.containsAll(languages)) {
return action.variables;
}
}

DatadogGlobalConfiguration datadogConfig = DatadogUtilities.getDatadogGlobalDescriptor();
if (datadogConfig == null) {
LOGGER.log(Level.WARNING, "Cannot set up tracer: Datadog config not found");
listener.error("[datadog] Cannot set up tracer: Datadog config not found");
return Collections.emptyMap();
}

TopLevelItem topLevelItem = getTopLevelItem(run);
FilePath workspacePath = node.getWorkspaceFor(topLevelItem);
if (workspacePath == null) {
throw new IllegalStateException("Cannot find workspace path for " + topLevelItem + " on " + node);
listener.error("[datadog] Cannot find workspace path for " + topLevelItem + " on " + node);
return Collections.emptyMap();
}

Map<String, String> variables = new HashMap<>(getCommonEnvVariables(datadogConfig, tracerConfig));
for (TracerLanguage language : languages) {
TracerConfigurator tracerConfigurator = configurators.get(language);
if (tracerConfigurator == null) {
LOGGER.log(Level.WARNING, "Cannot find tracer configurator for " + language);
listener.error("[datadog] Cannot find tracer configurator for " + language);
continue;
}

try {
Map<String, String> languageVariables = tracerConfigurator.configure(tracerConfig, node, workspacePath, envs);
Map<String, String> languageVariables = tracerConfigurator.configure(tracerConfig, node, workspacePath, envs, listener);
variables.putAll(languageVariables);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error while configuring " + language + " Datadog Tracer for run " + run + " and node " + node, e);
ExceptionUtils.printRootCauseStackTrace(e, listener.error("[datadog] Error while configuring " + language + " Datadog Tracer for run " + run + " and node " + node));
return Collections.emptyMap();
}
}
run.addAction(new ConfigureTracerAction(node, languages, variables));
run.addAction(new ConfigureTracerAction(nodeHostname, languages, variables));
return variables;
}

Expand Down Expand Up @@ -160,13 +163,13 @@ private static String getAgentPort(Integer traceCollectionPort) {
}
}

private static final class ConfigureTracerAction extends InvisibleAction {
private final Node node;
private static final class ConfigureTracerAction extends DatadogPluginAction {
private final String nodeHostname;
private final Collection<TracerLanguage> languages;
private final Map<String, String> variables;

private ConfigureTracerAction(Node node, Collection<TracerLanguage> languages, Map<String, String> variables) {
this.node = node;
private ConfigureTracerAction(String nodeHostname, @Nonnull Collection<TracerLanguage> languages, Map<String, String> variables) {
this.nodeHostname = nodeHostname;
this.languages = languages;
this.variables = variables;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.datadog.jenkins.plugins.datadog.tracer;
package org.datadog.jenkins.plugins.datadog.apm;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.EnvVars;
import hudson.Extension;
import hudson.model.Computer;
import hudson.model.EnvironmentContributor;
import hudson.model.Executor;
import hudson.model.Node;
Expand All @@ -26,8 +27,9 @@ public void buildEnvironmentFor(@NonNull Run run, @NonNull EnvVars envs, @NonNul
return;
}

Node node = executor.getOwner().getNode();
Map<String, String> additionalEnvVars = DatadogTracerConfigurator.INSTANCE.configure(run, node, envs);
Computer computer = executor.getOwner();
Node node = computer.getNode();
Map<String, String> additionalEnvVars = DatadogTracerConfigurator.INSTANCE.configure(run, computer, node, envs, listener);
envs.putAll(additionalEnvVars);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.datadog.jenkins.plugins.datadog.tracer;
package org.datadog.jenkins.plugins.datadog.apm;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand All @@ -14,6 +14,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
Expand All @@ -27,7 +28,7 @@ public class DatadogTracerJobProperty<T extends Job<?, ?>> extends JobProperty<T
private final Collection<TracerLanguage> languages;
private final Map<String, String> additionalVariables;

public DatadogTracerJobProperty(boolean on, String serviceName, Collection<TracerLanguage> languages, Map<String, String> additionalVariables) {
public DatadogTracerJobProperty(boolean on, String serviceName, @Nonnull Collection<TracerLanguage> languages, Map<String, String> additionalVariables) {
this.on = on;
this.serviceName = serviceName;
this.languages = languages;
Expand All @@ -42,6 +43,7 @@ public String getServiceName() {
return serviceName;
}

@Nonnull
public Collection<TracerLanguage> getLanguages() {
return languages;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.datadog.jenkins.plugins.datadog.tracer;
package org.datadog.jenkins.plugins.datadog.apm;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.EnvVars;
import hudson.Extension;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.model.Run;
import hudson.model.TaskListener;
Expand All @@ -17,9 +18,10 @@ public class DatadogTracerStepEnvironmentContributor extends StepEnvironmentCont
@Override
public void buildEnvironmentFor(StepContext stepContext, @NonNull EnvVars envs, @NonNull TaskListener listener) throws IOException, InterruptedException {
Run<?, ?> run = stepContext.get(Run.class);
Computer computer = stepContext.get(Computer.class);
Node node = stepContext.get(Node.class);
if (run != null && node != null) {
Map<String, String> additionalEnvVars = DatadogTracerConfigurator.INSTANCE.configure(run, node, envs);
Map<String, String> additionalEnvVars = DatadogTracerConfigurator.INSTANCE.configure(run, computer, node, envs, listener);
envs.putAll(additionalEnvVars);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.datadog.jenkins.plugins.datadog.tracer;
package org.datadog.jenkins.plugins.datadog.apm;

import hudson.FilePath;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.util.Secret;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
Expand All @@ -15,7 +16,7 @@
import java.util.function.Function;
import jenkins.model.Jenkins;
import org.datadog.jenkins.plugins.datadog.clients.HttpClient;
import org.datadog.jenkins.plugins.datadog.tracer.signature.SignatureVerifier;
import org.datadog.jenkins.plugins.datadog.apm.signature.SignatureVerifier;

final class JavaConfigurator implements TracerConfigurator {

Expand All @@ -33,18 +34,19 @@ final class JavaConfigurator implements TracerConfigurator {
private final HttpClient httpClient = new HttpClient(TRACER_DOWNLOAD_TIMEOUT_MILLIS);

@Override
public Map<String, String> configure(DatadogTracerJobProperty<?> tracerConfig, Node node, FilePath workspacePath, Map<String, String> envs) throws Exception {
FilePath tracerFile = downloadTracer(tracerConfig, workspacePath);
public Map<String, String> configure(DatadogTracerJobProperty<?> tracerConfig, Node node, FilePath workspacePath, Map<String, String> envs, TaskListener listener) throws Exception {
FilePath tracerFile = downloadTracer(tracerConfig, workspacePath,node, listener);
return getEnvVariables(tracerConfig, node, tracerFile, envs);
}

private FilePath downloadTracer(DatadogTracerJobProperty<?> tracerConfig, FilePath workspacePath) throws Exception {
private FilePath downloadTracer(DatadogTracerJobProperty<?> tracerConfig, FilePath workspacePath, Node node, TaskListener listener) throws Exception {
FilePath datadogFolder = workspacePath.child(".datadog");
datadogFolder.mkdirs();

FilePath datadogTracerFile = datadogFolder.child(TRACER_FILE_NAME);
long minutesSinceModification = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - datadogTracerFile.lastModified());
if (minutesSinceModification < getTracerJarCacheTtlMinutes(tracerConfig)) {
listener.getLogger().println("[datadog] Configuring DD Java tracer: using existing tracer available at " + datadogTracerFile);
// downloaded tracer is fresh enough
return datadogTracerFile.absolutize();
}
Expand All @@ -58,6 +60,8 @@ private FilePath downloadTracer(DatadogTracerJobProperty<?> tracerConfig, FilePa
}
});

listener.getLogger().println("[datadog] Configuring DD Java tracer: tracer installed in " + workspacePath + " on " + node);

if (!DEFAULT_TRACER_DISTRIBUTION_URL.equals(tracerDistributionUrl)) {
// verify signature if downloading from Maven Central
String signatureFileUrl = tracerDistributionUrl + ".asc";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.datadog.jenkins.plugins.datadog.apm;

import hudson.FilePath;
import hudson.model.Node;
import hudson.model.TaskListener;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

final class JavascriptConfigurator implements TracerConfigurator {

private static final int GET_NPM_VERSION_TIMEOUT_MILLIS = 30_000;
private static final int INSTALL_TRACER_TIMEOUT_MILLIS = 300_000;

@Override
public Map<String, String> configure(DatadogTracerJobProperty<?> tracerConfig, Node node, FilePath workspacePath, Map<String, String> envs, TaskListener listener) throws Exception {
String nodeVersion = workspacePath.act(new ShellCommandCallable(Collections.emptyMap(), GET_NPM_VERSION_TIMEOUT_MILLIS, "npm", "-v"));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If npm is not installed, would this return an exception?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it'll throw an exception that will be caught and logged in the calling class

listener.getLogger().println("[datadog] Configuring DD JS tracer: got npm version " + nodeVersion + " from " + workspacePath + " on " + node);

FilePath datadogPath = workspacePath.child(".datadog");
datadogPath.mkdirs();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know if the plugin has permissions to create folders?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has, and we only do things inside the job's workspace


// set location for installing global packages (Jenkins agent user may not have the permissions to write to the default one)
Path absoluteDatadogPath = Paths.get(datadogPath.absolutize().getRemote());
Map<String, String> environment = Collections.singletonMap("NPM_CONFIG_PREFIX", absoluteDatadogPath.toString());

// we install dd-trace as a "global" package
// (otherwise, doing SCM checkout might rollback the changes to package.json, and any subsequent `npm install` calls will result in removing the package)
String installTracerOutput = workspacePath.act(new ShellCommandCallable(environment, INSTALL_TRACER_TIMEOUT_MILLIS, "npm", "install", "-g", "dd-trace"));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we have permissions issues?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "global" package naming is a bit misleading here - what this means is we are not installing it inside the repo (because subsequent SCM checkout would overwrite this) and instead we install it inside the .datadog folder which we create inside the job's workspace - where we are allowed to write.

We could have a permissions issue if the OS user that runs the Jenkins agent is not allowed to execute npm, but I don't think there's anything we can do in this case other than log an error.

Path tracerPath = absoluteDatadogPath.resolve("lib/node_modules/dd-trace");
listener.getLogger().println("[datadog] Configuring DD JS tracer: tracer installed in " + tracerPath + " on " + node + "; output: " + installTracerOutput);

Map<String, String> variables = new HashMap<>();
variables.put("DD_TRACE_PATH", tracerPath.toString());
variables.put("NODE_OPTIONS", PropertyUtils.prepend(envs, "NODE_OPTIONS", "-r $DD_TRACE_PATH/ci/init"));
return variables;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.datadog.jenkins.plugins.datadog.tracer;
package org.datadog.jenkins.plugins.datadog.apm;

import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
package org.datadog.jenkins.plugins.datadog.tracer;
package org.datadog.jenkins.plugins.datadog.apm;

import hudson.FilePath;
import hudson.model.Node;
import hudson.model.TaskListener;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

final class PythonConfigurator implements TracerConfigurator {

private static final Logger LOGGER = Logger.getLogger(PythonConfigurator.class.getName());

private static final int GET_PIP_VERSION_TIMEOUT_MILLIS = 30_000;
private static final int INSTALL_TRACER_TIMEOUT_MILLIS = 300_000;
private static final int SHOW_TRACER_PACKAGE_DETAILS_TIMEOUT_MILLIS = 300_000;

@Override
public Map<String, String> configure(DatadogTracerJobProperty<?> tracerConfig, Node node, FilePath workspacePath, Map<String, String> envs) throws Exception {
String pipVersion = workspacePath.act(new ShellCommandCallable(GET_PIP_VERSION_TIMEOUT_MILLIS, "pip", "-V"));
LOGGER.log(Level.FINE, "Got pip version " + pipVersion + " from " + workspacePath + " on " + node);
public Map<String, String> configure(DatadogTracerJobProperty<?> tracerConfig, Node node, FilePath workspacePath, Map<String, String> envs, TaskListener listener) throws Exception {
String pipVersion = workspacePath.act(new ShellCommandCallable(Collections.emptyMap(), GET_PIP_VERSION_TIMEOUT_MILLIS, "pip", "-V"));
listener.getLogger().println("[datadog] Configuring DD Python tracer: got pip version " + pipVersion + " from " + workspacePath + " on " + node);

String installTracerOutput = workspacePath.act(new ShellCommandCallable(INSTALL_TRACER_TIMEOUT_MILLIS, "pip", "install", "-U", "ddtrace"));
LOGGER.log(Level.FINE, "Tracer installed in " + workspacePath + " on " + node + "; output: " + installTracerOutput);
String installTracerOutput = workspacePath.act(new ShellCommandCallable(Collections.emptyMap(), INSTALL_TRACER_TIMEOUT_MILLIS, "pip", "install", "-U", "ddtrace"));
listener.getLogger().println("[datadog] Configuring DD Python tracer: tracer installed in " + workspacePath + " on " + node + "; output: " + installTracerOutput);

String tracerLocation = getTracerLocation(workspacePath);

Expand All @@ -33,7 +31,7 @@ public Map<String, String> configure(DatadogTracerJobProperty<?> tracerConfig, N
}

private static String getTracerLocation(FilePath workspacePath) throws IOException, InterruptedException {
String getTracerLocationOutput = workspacePath.act(new ShellCommandCallable(SHOW_TRACER_PACKAGE_DETAILS_TIMEOUT_MILLIS, "pip", "show", "ddtrace"));
String getTracerLocationOutput = workspacePath.act(new ShellCommandCallable(Collections.emptyMap(), SHOW_TRACER_PACKAGE_DETAILS_TIMEOUT_MILLIS, "pip", "show", "ddtrace"));
for (String line : getTracerLocationOutput.split("\n")) {
if (line.contains("Location")) {
return line.substring(line.indexOf(':') + 2);
Expand Down
Loading
Loading