Skip to content
This repository has been archived by the owner on Jan 23, 2020. It is now read-only.

Send all metrics w/o DogstatsD #169

Merged
merged 30 commits into from
Nov 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fd047c0
Investigate use of ThreadLocal
Nov 12, 2019
44a30d2
Fix threadlocal
Nov 12, 2019
b26cf08
Attempt to use PeriodicWork
Nov 13, 2019
887d6be
Implement threadlocal with hashmaps for counters and job tags
Nov 13, 2019
d48f7de
Draft deepcopy
Nov 13, 2019
588873d
Solve import typo
Nov 13, 2019
24878b4
Remove DogstatsD to send all metrics
Nov 14, 2019
7e7c574
More cleanup for DogstatsD
Nov 14, 2019
6f15f7e
Modify test to try to assert all the metrics
Nov 14, 2019
01e1388
Implement part of the review feedback
Nov 15, 2019
1b49ff6
More refactoring / tests: assess more metrics
Nov 15, 2019
9bcbb01
[Not working] Assess JSONArray tags
Nov 15, 2019
fd3fe2f
More test updates
Nov 18, 2019
5878544
More review feedback for tests
Nov 18, 2019
cf80076
Attempt to test mtbf, mttr and cycletime
Nov 19, 2019
f7dc16c
refactoring and fix tests
Nov 21, 2019
82d5ba0
adding and improving tests
Nov 22, 2019
d697505
testing threadlocal logic and improving it
Nov 22, 2019
659dc12
clean up and doc
Nov 22, 2019
fdfcb14
add try catch statement and incorporate feedbacks
Nov 22, 2019
8acb597
change counter submission type to rate
Nov 22, 2019
d962c90
setting up proper interval when submitting rate metrics
Nov 22, 2019
7c7d954
update packages and some clean up
Nov 22, 2019
d3c536f
singletons, threads, locks and friends
Nov 26, 2019
2bbdf52
update comment
Nov 27, 2019
25e3998
remove lock and use synchronized - incorporate feedbacks
Nov 27, 2019
a1b8eca
import clean up
Nov 27, 2019
5e14c94
refactoring around synchronized method location
Nov 27, 2019
520569b
change count as rate for backward compatibility
Nov 27, 2019
6025d1a
incorporate feedback
Nov 27, 2019
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.datadog.jenkins.plugins.datadog;

import hudson.util.Secret;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

Expand All @@ -13,6 +14,10 @@ public interface DatadogClient {
public Integer CRITICAL = 2;
public Integer UNKNOWN = 3;

public void setUrl(String url);

public void setApiKey(Secret apiKey);

/**
* Sends an event to the Datadog API, including the event payload.
*
Expand All @@ -21,6 +26,21 @@ public interface DatadogClient {
*/
public boolean sendEvent(JSONObject payload);

/**
* Increment a counter for the given metrics.
* NOTE: To submit all counters you need to execute the flushCounters method.
* This is to aggregate counters and submit them in batch to Datadog in order to minimize network traffic.
* @param name - metric name
* @param hostname - metric hostname
* @param tags - metric tags
*/
public void incrementCounter(String name, String hostname, JSONArray tags);

/**
* Submit all your counters as rate with 10 seconds intervals.
*/
public void flushCounters();
gzussa marked this conversation as resolved.
Show resolved Hide resolved

/**
* Sends a metric to the Datadog API, including the gauge name, and value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,22 @@ public long getRecurrencePeriod() {

@Override
protected void doRun() throws Exception {
if (DatadogUtilities.isApiKeyNull()) {
return;
}
logger.fine("doRun called: Computing queue metrics");
try {
if (DatadogUtilities.isApiKeyNull()) {
return;
}
logger.fine("doRun called: Computing queue metrics");

// Get Datadog Client Instance
DatadogClient client = DatadogUtilities.getDatadogClient();

// Instantiate the Datadog Client
DatadogClient client = DatadogUtilities.getDatadogDescriptor().leaseDatadogClient();
long size = queue.getApproximateItemsQuickly().size();
String hostname = DatadogUtilities.getHostname("null");
client.gauge("jenkins.queue.size", size, hostname, null);

} catch (Exception e) {
logger.warning("Unexpected exception occurred - " + e.getMessage());
}

client.gauge("jenkins.queue.size",
queue.getApproximateItemsQuickly().size(),
DatadogUtilities.getHostname(null),
null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import hudson.model.listeners.SCMListener;
import hudson.scm.SCM;
import hudson.scm.SCMRevisionState;
import net.sf.json.JSONArray;
import org.datadog.jenkins.plugins.datadog.events.CheckoutCompletedEventImpl;
import org.datadog.jenkins.plugins.datadog.model.BuildData;

Expand Down Expand Up @@ -39,36 +40,45 @@ public class DatadogSCMListener extends SCMListener {
@Override
public void onCheckout(Run<?, ?> build, SCM scm, FilePath workspace, TaskListener listener,
File changelogFile, SCMRevisionState pollingBaseline) throws Exception {
if (DatadogUtilities.isApiKeyNull()) {
return;
}
try {
if (DatadogUtilities.isApiKeyNull()) {
return;
}

// Process only if job is NOT in blacklist and is in whitelist
DatadogJobProperty prop = DatadogUtilities.retrieveProperty(build);
if (!(DatadogUtilities.isJobTracked(build.getParent().getFullName())
&& prop != null && prop.isEmitOnCheckout())) {
return;
}
logger.fine("Checkout! in onCheckout()");
// Process only if job is NOT in blacklist and is in whitelist
DatadogJobProperty prop = DatadogUtilities.retrieveProperty(build);
if (!(DatadogUtilities.isJobTracked(build.getParent().getFullName())
&& prop != null && prop.isEmitOnCheckout())) {
return;
}
logger.fine("Checkout! in onCheckout()");

// Instantiate the Datadog Client
DatadogClient client = DatadogUtilities.getDatadogDescriptor().leaseDatadogClient();
// Get Datadog Client Instance
DatadogClient client = DatadogUtilities.getDatadogClient();

// Collect Build Data
BuildData buildData;
try {
buildData = new BuildData(build, listener);
} catch (IOException | InterruptedException e) {
logger.severe(e.getMessage());
return;
}
// Collect Build Data
BuildData buildData;
try {
buildData = new BuildData(build, listener);
} catch (IOException | InterruptedException e) {
logger.severe(e.getMessage());
return;
}

// Get the list of global tags to apply
HashMap<String, String> extraTags = DatadogUtilities.buildExtraTags(build, listener);

// Get the list of global tags to apply
HashMap<String, String> extraTags = DatadogUtilities.buildExtraTags(build, listener);
// Send event
DatadogEvent event = new CheckoutCompletedEventImpl(buildData, extraTags);
client.sendEvent(event.createPayload());

// Send event
DatadogEvent event = new CheckoutCompletedEventImpl(buildData, extraTags);
client.sendEvent(event.createPayload());
// Submit counter
JSONArray tags = buildData.getAssembledTags(extraTags);
String hostname = DatadogUtilities.getHostname("null");
client.incrementCounter("jenkins.scm.checkout", hostname, tags);
} catch (Exception e) {
logger.warning("Unexpected exception occurred - " + e.getMessage());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import hudson.model.TaskListener;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.datadog.jenkins.plugins.datadog.clients.DatadogHttpClient;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
Expand All @@ -25,8 +26,7 @@ public class DatadogUtilities {
private static final Integer MAX_HOSTNAME_LEN = 255;

/**
* @return - The descriptor for the Datadog plugin. In this case the global
* - configuration.
* @return - The descriptor for the Datadog plugin. In this case the global configuration.
*/
public static DatadogBuildListener.DescriptorImpl getDatadogDescriptor() {
Jenkins jenkins = Jenkins.getInstance();
Expand All @@ -36,6 +36,14 @@ public static DatadogBuildListener.DescriptorImpl getDatadogDescriptor() {
return (DatadogBuildListener.DescriptorImpl) jenkins.getDescriptorOrDie(DatadogBuildListener.class);
}

/**
* @return - The descriptor for the Datadog plugin. In this case the global configuration.
*/
public static DatadogClient getDatadogClient() {
DatadogBuildListener.DescriptorImpl descriptor = getDatadogDescriptor();
return DatadogHttpClient.getInstance(descriptor.getTargetMetricURL(), descriptor.getApiKey());
}

/**
* Check if apiKey is null
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.datadog.jenkins.plugins.datadog.clients;

import net.sf.json.JSONArray;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;

public class ConcurrentMetricCounters {
private static final Logger logger = Logger.getLogger(ConcurrentMetricCounters.class.getName());
private static ConcurrentMetricCounters instance;
private static ConcurrentMap<CounterMetric, Integer> counters = new ConcurrentHashMap<>();

private ConcurrentMetricCounters(){}

public static ConcurrentMetricCounters getInstance(){
if(instance == null){
zippolyte marked this conversation as resolved.
Show resolved Hide resolved
synchronized (ConcurrentMetricCounters.class) {
zippolyte marked this conversation as resolved.
Show resolved Hide resolved
if(instance == null){
instance = new ConcurrentMetricCounters();
}
}
}
return instance;
}

private static ConcurrentMap<CounterMetric, Integer> get(){
ConcurrentMetricCounters countersInstance = ConcurrentMetricCounters.getInstance();
return countersInstance.getCounters();
}

private static void reset(){
ConcurrentMetricCounters countersInstance = ConcurrentMetricCounters.getInstance();
countersInstance.resetCounters();
}

private void resetCounters(){
counters = new ConcurrentHashMap<>();
}

private ConcurrentMap<CounterMetric, Integer> getCounters(){
return counters;
}
gzussa marked this conversation as resolved.
Show resolved Hide resolved

public synchronized void increment(String name, String hostname, JSONArray tags) {
ConcurrentMap<CounterMetric, Integer> counters = ConcurrentMetricCounters.get();
CounterMetric counterMetric = new CounterMetric(tags, name, hostname);
Integer previousValue = counters.putIfAbsent(counterMetric, 1);
if (previousValue != null){
boolean ok = counters.replace(counterMetric, previousValue, previousValue + 1);
// NOTE:
// This while loop below should never be called since we are using a lock when flushing and
// incrementing counters.
while(!ok) {
logger.warning("Couldn't increment counter " + name + " with value " + (previousValue + 1) +
" previousValue = " + previousValue);
previousValue = counters.get(counterMetric);
ok = counters.replace(counterMetric, previousValue, previousValue + 1);
}
}
previousValue = previousValue == null ? 0 : previousValue;
logger.fine("Counter " + name + " updated from previousValue " + previousValue + " to "
+ (previousValue + 1));
}

public synchronized ConcurrentMap<CounterMetric, Integer> getAndReset(){
ConcurrentMap<CounterMetric, Integer> counters = ConcurrentMetricCounters.get();
ConcurrentMetricCounters.reset();
return counters;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.datadog.jenkins.plugins.datadog.clients;

import net.sf.json.JSONArray;

public class CounterMetric {
private JSONArray tags = new JSONArray();
private String metricName;
private String hostname;

public CounterMetric(JSONArray tags, String metricName, String hostname) {
this.tags = tags;
this.metricName = metricName;
this.hostname = hostname;
}

public JSONArray getTags() {
return tags;
}

public void setTags(JSONArray tags) {
this.tags = tags;
}

public String getMetricName() {
return metricName;
}

public void setMetricName(String metricName) {
this.metricName = metricName;
}

public String getHostname() {
return hostname;
}

public void setHostname(String hostname) {
this.hostname = hostname;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CounterMetric)) return false;

CounterMetric that = (CounterMetric) o;

if (tags != null ? !tags.equals(that.tags) : that.tags != null) return false;
if (metricName != null ? !metricName.equals(that.metricName) : that.metricName != null) return false;
return hostname != null ? hostname.equals(that.hostname) : that.hostname == null;
}

@Override
public int hashCode() {
int result = tags != null ? tags.hashCode() : 0;
result = 31 * result + (metricName != null ? metricName.hashCode() : 0);
result = 31 * result + (hostname != null ? hostname.hashCode() : 0);
return result;
}

@Override
public String toString() {
return "CounterMetric{" +
"tags=" + tags +
", metricName='" + metricName + '\'' +
", hostname='" + hostname + '\'' +
'}';
}
}
Loading