Skip to content

Commit

Permalink
Implemented a 'jenkins.job.completed' counter for Datadog. Implements #…
Browse files Browse the repository at this point in the history
  • Loading branch information
MadsNielsen committed May 4, 2016
1 parent a52e0d7 commit b040956
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 2 deletions.
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
<artifactId>httpclient</artifactId>
<version>4.2.5</version>
</dependency>
<dependency>
<groupId>com.datadoghq</groupId>
<artifactId>java-dogstatsd-client</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -89,5 +94,5 @@
</plugin>
</plugins>
</reporting>

</project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.datadog.jenkins.plugins.datadog;

import com.timgroup.statsd.NonBlockingStatsDClient;
import com.timgroup.statsd.StatsDClient;
import com.timgroup.statsd.StatsDClientException;
import static hudson.Util.fixEmptyAndTrim;

import hudson.EnvVars;
Expand All @@ -26,10 +29,13 @@
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import org.apache.commons.lang.StringUtils;

/**
* DatadogBuildListener {@link RunListener}.
Expand Down Expand Up @@ -75,7 +81,6 @@ public class DatadogBuildListener extends RunListener<Run>
* Runs when the {@link DatadogBuildListener} class is created.
*/
public DatadogBuildListener() { }

/**
* Called when a build is first started.
*
Expand Down Expand Up @@ -153,6 +158,31 @@ public final void onCompleted(final Run run, @Nonnull final TaskListener listene
} else {
serviceCheck("jenkins.job.status", DatadogBuildListener.CRITICAL, builddata, extraTags);
}

//At this point. We will always have some tags. So no defensive checks needed.
//We add the tags into our array, so we can easily pass the to the stats counter.
//Tags after this point will be propertly formatted.
JSONArray arr = evt.createPayload().getJSONArray("tags");
String[] tagsToCounter = new String[arr.size()];
for(int i=0; i<arr.size()-1; i++) {
tagsToCounter[i] = arr.getString(i);
}


if(DatadogUtilities.isValidDaemon(getDescriptor().getDaemonHost())) {
logger.fine(String.format("Sending completed counter to %s ", getDescriptor().getDaemonHost()));
try {
//The client is a threadpool so instead of creating a new instance of the pool
//we lease the exiting one registerd with Jenkins.
StatsDClient statsd = getDescriptor().leaseClient();
statsd.increment("completed", tagsToCounter);
logger.fine("Jenkins completed counter sent!");
} catch (StatsDClientException e) {
logger.log(Level.SEVERE, "Runtime exception thrown using the StatsDClient", e);
}
} else {
logger.warning("Invalid dogstats daemon host specificed");
}
}
}

Expand Down Expand Up @@ -306,6 +336,22 @@ public final DescriptorImpl getDescriptor() {
*/
@Extension // Indicates to Jenkins that this is an extension point implementation.
public static final class DescriptorImpl extends Descriptor<DatadogBuildListener> {

/**
* @return - A {@link StatsDClient} lease for this registered {@link RunListener}
*/
public StatsDClient leaseClient() {
try {
if(client == null) {
client = new NonBlockingStatsDClient("jenkins.job", daemonHost.split(":")[0],
Integer.parseInt(daemonHost.split(":")[1]));
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error while configuring client", e);
}
return client;
}

/**
* Persist global configuration information by storing in a field and
* calling save().
Expand All @@ -314,6 +360,9 @@ public static final class DescriptorImpl extends Descriptor<DatadogBuildListener
private String hostname = null;
private String blacklist = null;
private Boolean tagNode = null;
private String daemonHost = "localhost:8125";
//The StatsDClient instance variable. This variable is leased by the RunLIstener
private static StatsDClient client;

/**
* Runs when the {@link DescriptorImpl} class is created.
Expand All @@ -322,6 +371,11 @@ public DescriptorImpl() {
load(); // load the persisted global configuration
}

@Override
public DatadogBuildListener newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return super.newInstance(req, formData); //To change body of generated methods, choose Tools | Templates.
}

/**
* Tests the {@link apiKey} from the configuration screen, to check its' validity.
*
Expand Down Expand Up @@ -396,6 +450,34 @@ public FormValidation doTestHostname(@QueryParameter("hostname") final String fo
}
}

/**
*
* @param daemonHost - The hostname for the dogstatsdaemon. Defaults to localhost:8125
* @return a FormValidation object used to display a message to the user on the configuration
* screen.
*/
public FormValidation doCheckDaemonHost(@QueryParameter("daemonHost") final String daemonHost) {
if(!daemonHost.contains(":")) {
return FormValidation.error("The field must be configured in the form <hostname>:<port>");
}

String hn = daemonHost.split(":")[0];
String pn = daemonHost.split(":").length > 1 ? daemonHost.split(":")[1] : "";

if(StringUtils.isBlank(hn)) {
return FormValidation.error("Empty hostname");
}

//Match ports [1024-65535]
Pattern p = Pattern.compile("^(102[4-9]|10[3-9]\\d|1[1-9]\\d{2}|[2-9]\\d{3}|[1-5]\\d{4}|6[0-4]"
+ "\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$");
if(!p.matcher(pn).find()) {
return FormValidation.error("Invalid port specified. Range must be 1024-65535");
}

return FormValidation.ok("Everything ok");
}

/**
* Indicates if this builder can be used with all kinds of project types.
*
Expand Down Expand Up @@ -447,6 +529,22 @@ public boolean configure(final StaplerRequest req, final JSONObject formData)
tagNode = false;
}

daemonHost = formData.getString("daemonHost");
//When form is saved...reinitialize the StatsDClient.
//We need to stop the old one first. And crete a new one with the new data from
//The global configuration
if (client != null) {
try {
client.stop();
String hp = daemonHost.split(":")[0];
int pp = Integer.parseInt(daemonHost.split(":")[1]);
client = new NonBlockingStatsDClient("jenkins.job", hp, pp);
logger.finer(String.format("Created new dogstatsdaemon client (%s:%S)!", hp, pp));
} catch (Exception e) {
logger.log(Level.SEVERE, "Unable to crete new DogstatsClient", e);
}
}

// Persist global configuration information
save();
return super.configure(req, formData);
Expand Down Expand Up @@ -488,6 +586,20 @@ public String getBlacklist() {
public Boolean getTagNode() {
return tagNode;
}

/**
* @return The host definition for the dogstats daemon
*/
public String getDaemonHost() {
return daemonHost;
}

/**
* @param daemonHost - The host specification for the dogstats daemon
*/
public void setDaemonHost(String daemonHost) {
this.daemonHost = daemonHost;
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import static org.datadog.jenkins.plugins.datadog.DatadogBuildListener.getOS;

public class DatadogUtilities {
Expand Down Expand Up @@ -263,6 +264,38 @@ public static final Boolean isValidHostname(final String hostname) {
return m.find();
}

/**
* @param daemonHost - The host to check
*
* @return - A boolean that checks if the daemonHost is valid
*/
public static boolean isValidDaemon(final String daemonHost) {
if(!daemonHost.contains(":")) {
logger.info("Daemon host does not contain the port seperator ':'");
return false;
}

String hn = daemonHost.split(":")[0];
String pn = daemonHost.split(":").length > 1 ? daemonHost.split(":")[1] : "";

if(StringUtils.isBlank(hn)) {
logger.info("Daemon host part is empty");
return false;
}

//Match ports [1024-65535]
Pattern p = Pattern.compile("^(102[4-9]|10[3-9]\\d|1[1-9]\\d{2}|[2-9]\\d{3}|[1-5]\\d{4}|6[0-4]"
+ "\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$");

boolean match = p.matcher(pn).find();

if(!match) {
logger.info("Port number is invalid must be in the range [1024-65535]");
}

return match;
}


/**
* Safe getter function to make sure an exception is not reached.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@
description="Name of node the current build is running on (i.e. 'master' for master node)." >
<f:checkbox title="node" field="tagNode" optional="true" />
</f:entry>
<f:advanced>
<f:entry title="Dogstats daemon host" field="daemonHost">
<f:textbox field="daemonHost" default="${daemonHost}" />
</f:entry>
</f:advanced>
</f:section>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Enter your Datadog statsdaemon host specification. In the form <em>hostname:port</em>
</div>

0 comments on commit b040956

Please sign in to comment.