From a03b079d011d3bc3f709a2a5f25f8b0a0e09cbb3 Mon Sep 17 00:00:00 2001 From: Nikita Tkachenko Date: Mon, 1 Apr 2024 17:21:19 +0200 Subject: [PATCH] Implement running jobs metric + update metrics code --- .../plugins/datadog/DatadogClient.java | 20 +--- .../clients/ConcurrentMetricCounters.java | 102 ------------------ .../datadog/clients/DatadogAgentClient.java | 44 ++++---- .../datadog/clients/DatadogApiClient.java | 49 ++------- .../plugins/datadog/clients/Metrics.java | 16 --- .../listeners/DatadogBuildListener.java | 54 +++++----- .../listeners/DatadogComputerListener.java | 17 ++- .../listeners/DatadogGraphListener.java | 7 +- .../listeners/DatadogItemListener.java | 10 +- .../datadog/listeners/DatadogSCMListener.java | 4 +- .../listeners/DatadogSaveableListener.java | 5 +- .../listeners/DatadogSecurityListener.java | 10 +- .../MetricKey.java} | 51 ++++----- .../plugins/datadog/metrics/Metrics.java | 55 ++++++++++ .../datadog/metrics/MetricsClient.java | 26 +++++ .../publishers/DatadogComputerPublisher.java | 48 +++++++-- .../publishers/DatadogCountersPublisher.java | 38 ++++--- .../publishers/DatadogJenkinsPublisher.java | 4 +- .../publishers/DatadogQueuePublisher.java | 4 +- .../datadog/clients/DatadogClientStub.java | 31 ++---- .../datadog/clients/DatadogClientTest.java | 32 +++--- .../listeners/DatadogBuildListenerTest.java | 24 ++++- .../listeners/DatadogGraphListenerTest.java | 4 + 23 files changed, 303 insertions(+), 352 deletions(-) delete mode 100644 src/main/java/org/datadog/jenkins/plugins/datadog/clients/ConcurrentMetricCounters.java delete mode 100644 src/main/java/org/datadog/jenkins/plugins/datadog/clients/Metrics.java rename src/main/java/org/datadog/jenkins/plugins/datadog/{clients/CounterMetric.java => metrics/MetricKey.java} (58%) create mode 100644 src/main/java/org/datadog/jenkins/plugins/datadog/metrics/Metrics.java create mode 100644 src/main/java/org/datadog/jenkins/plugins/datadog/metrics/MetricsClient.java diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java index eb72d033a..9b271bdd1 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java @@ -28,7 +28,7 @@ of this software and associated documentation files (the "Software"), to deal import com.timgroup.statsd.ServiceCheck; import java.util.Map; import java.util.Set; -import org.datadog.jenkins.plugins.datadog.clients.Metrics; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; import org.datadog.jenkins.plugins.datadog.traces.write.TraceWriteStrategy; public interface DatadogClient { @@ -69,23 +69,7 @@ public ServiceCheck.Status toServiceCheckStatus(){ */ boolean event(DatadogEvent event); - /** - * 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 - * @return a boolean to signify the success or failure of increment submission. - */ - boolean incrementCounter(String name, String hostname, Map> tags); - - /** - * Submit all your counters as rate with 10 seconds intervals. - */ - void flushCounters(); - - Metrics metrics(); + MetricsClient metrics(); /** * Sends a service check to the Datadog API, including the check name, and status. diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ConcurrentMetricCounters.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ConcurrentMetricCounters.java deleted file mode 100644 index 8d8fd05a2..000000000 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ConcurrentMetricCounters.java +++ /dev/null @@ -1,102 +0,0 @@ -/* -The MIT License - -Copyright (c) 2015-Present Datadog, Inc -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - */ - -package org.datadog.jenkins.plugins.datadog.clients; - -import org.datadog.jenkins.plugins.datadog.util.SuppressFBWarnings; - -import java.util.Map; -import java.util.Set; -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 volatile ConcurrentMetricCounters instance; - private static ConcurrentMap counters = new ConcurrentHashMap<>(); - - private ConcurrentMetricCounters(){} - - @SuppressFBWarnings(value="DC_DOUBLECHECK") - public static ConcurrentMetricCounters getInstance(){ - if(instance == null){ - synchronized (ConcurrentMetricCounters.class) { - if(instance == null){ - instance = new ConcurrentMetricCounters(); - } - } - } - return instance; - } - - private static ConcurrentMap get(){ - ConcurrentMetricCounters countersInstance = ConcurrentMetricCounters.getInstance(); - return countersInstance.getCounters(); - } - - private static void reset(){ - ConcurrentMetricCounters countersInstance = ConcurrentMetricCounters.getInstance(); - countersInstance.resetCounters(); - } - - @SuppressFBWarnings(value="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD") - private void resetCounters(){ - counters = new ConcurrentHashMap<>(); - } - - private ConcurrentMap getCounters(){ - return counters; - } - - public synchronized void increment(String name, String hostname, Map> tags) { - ConcurrentMap 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) ? Integer.valueOf(0) : previousValue; - logger.fine("Counter " + name + " updated from previousValue " + previousValue + " to " - + (previousValue + Integer.valueOf(1))); - } - - public synchronized ConcurrentMap getAndReset(){ - ConcurrentMap counters = ConcurrentMetricCounters.get(); - ConcurrentMetricCounters.reset(); - return counters; - } - -} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogAgentClient.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogAgentClient.java index 4b952677e..7a717aa0d 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogAgentClient.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogAgentClient.java @@ -49,6 +49,7 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; import org.datadog.jenkins.plugins.datadog.traces.mapper.JsonTraceSpanMapper; import org.datadog.jenkins.plugins.datadog.traces.write.AgentTraceWriteStrategy; import org.datadog.jenkins.plugins.datadog.traces.write.Payload; @@ -399,35 +400,13 @@ public boolean event(DatadogEvent event) { } @Override - public boolean incrementCounter(String name, String hostname, Map> tags) { - try { - boolean status = reinitializeStatsDClient(false); - if(!status){ - return false; - } - logger.fine("increment counter with dogStatD client"); - this.statsd.incrementCounter(name, TagsUtil.convertTagsToArray(tags)); - return true; - } catch(Exception e){ - DatadogUtilities.severe(logger, e, "Failed to increment counter with DogStatsD"); - reinitializeStatsDClient(true); - return false; - } - } - - @Override - public void flushCounters() { - return; //noop - } - - @Override - public Metrics metrics() { + public MetricsClient metrics() { return new AgentMetrics(); } - private final class AgentMetrics implements Metrics { + private final class AgentMetrics implements MetricsClient { @Override - public void gauge(String name, long value, String hostname, Map> tags) { + public void gauge(String name, double value, String hostname, Map> tags) { try { boolean status = reinitializeStatsDClient(false); if (!status) { @@ -441,6 +420,21 @@ public void gauge(String name, long value, String hostname, Map> tags) { + try { + boolean status = reinitializeStatsDClient(false); + if(!status){ + return; + } + logger.fine("increment counter with dogStatD client"); + statsd.count(name, value, TagsUtil.convertTagsToArray(tags)); + } catch(Exception e){ + DatadogUtilities.severe(logger, e, "Failed to increment counter with DogStatsD"); + reinitializeStatsDClient(true); + } + } + @Override public void close() throws Exception { // no op diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogApiClient.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogApiClient.java index 288137cfd..2fe18ff3b 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogApiClient.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogApiClient.java @@ -34,7 +34,6 @@ of this software and associated documentation files (the "Software"), to deal import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import net.sf.json.JSON; @@ -46,6 +45,7 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; import org.datadog.jenkins.plugins.datadog.traces.write.Payload; import org.datadog.jenkins.plugins.datadog.traces.write.TraceWriteStrategy; import org.datadog.jenkins.plugins.datadog.traces.write.TraceWriteStrategyImpl; @@ -258,43 +258,11 @@ public boolean event(DatadogEvent event) { } @Override - public boolean incrementCounter(String name, String hostname, Map> tags) { - if(this.defaultIntakeConnectionBroken){ - logger.severe("Your client is not initialized properly"); - return false; - } - ConcurrentMetricCounters.getInstance().increment(name, hostname, tags); - return true; - } - - @Override - public void flushCounters() { - ConcurrentMap counters = ConcurrentMetricCounters.getInstance().getAndReset(); - - logger.fine("Run flushCounters method"); - try (HttpMetrics metrics = metrics()) { - // Submit all metrics as gauge - for (Map.Entry entry : counters.entrySet()) { - CounterMetric counterMetric = entry.getKey(); - int count = entry.getValue(); - logger.fine("Flushing: " + counterMetric.getMetricName() + " - " + count); - - metrics.rate( - counterMetric.getMetricName(), count, - counterMetric.getHostname(), - counterMetric.getTags()); - } - } catch (Exception e) { - DatadogUtilities.severe(logger, e, "Failed to flush counters"); - } + public MetricsClient metrics() { + return new ApiMetrics(); } - @Override - public HttpMetrics metrics() { - return new HttpMetrics(); - } - - private final class HttpMetrics implements Metrics { + private final class ApiMetrics implements MetricsClient { // when we submit a rate we need to divide the submitted value by the interval (10) private static final int RATE_INTERVAL = 10; @@ -302,21 +270,22 @@ private final class HttpMetrics implements Metrics { private final long timestamp = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); @Override - public void gauge(String name, long value, String hostname, Map> tags) { + public void gauge(String name, double value, String hostname, Map> tags) { addMetric(name, value, hostname, tags, "gauge"); } - public void rate(String name, float value, String hostname, Map> tags) { + @Override + public void rate(String name, double value, String hostname, Map> tags) { addMetric(name, value, hostname, tags, "rate"); } - private void addMetric(String name, float value, String hostname, Map> tags, String type) { + private void addMetric(String name, double value, String hostname, Map> tags, String type) { logger.fine(String.format("Sending metric '%s' with value %s", name, value)); JSONArray point = new JSONArray(); point.add(timestamp); if (type.equals("rate")) { - point.add(value / (float) RATE_INTERVAL); + point.add(value / RATE_INTERVAL); } else { point.add(value); } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/Metrics.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/Metrics.java deleted file mode 100644 index 201eb4e3f..000000000 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/Metrics.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.datadog.jenkins.plugins.datadog.clients; - -import java.util.Map; -import java.util.Set; - -public interface Metrics extends AutoCloseable { - /** - * Sends a metric to the Datadog API, including the gauge name, and value. - * - * @param name - A String with the name of the metric to record. - * @param value - A long containing the value to submit. - * @param hostname - A String with the hostname to submit. - * @param tags - A Map containing the tags to submit. - */ - void gauge(String name, long value, String hostname, Map> tags); -} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java index 9c322bad1..6cb19ac17 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java @@ -42,6 +42,7 @@ of this software and associated documentation files (the "Software"), to deal import hudson.model.TaskListener; import hudson.model.listeners.RunListener; import java.io.IOException; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -52,10 +53,11 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; -import org.datadog.jenkins.plugins.datadog.clients.Metrics; import org.datadog.jenkins.plugins.datadog.events.BuildAbortedEventImpl; import org.datadog.jenkins.plugins.datadog.events.BuildFinishedEventImpl; import org.datadog.jenkins.plugins.datadog.events.BuildStartedEventImpl; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; import org.datadog.jenkins.plugins.datadog.model.BuildData; import org.datadog.jenkins.plugins.datadog.model.GitCommitAction; import org.datadog.jenkins.plugins.datadog.model.GitRepositoryAction; @@ -200,6 +202,25 @@ public void onStarted(Run run, TaskListener listener) { return; } + Long waitingMs; + try { + Queue queue = getQueue(); + Queue.Item item = queue.getItem(run.getQueueId()); + waitingMs = (DatadogUtilities.currentTimeMillis() - item.getInQueueSince()); + PipelineQueueInfoAction queueInfoAction = run.getAction(PipelineQueueInfoAction.class); + if (queueInfoAction != null) { + // this needs to be set before BuildData is created, as BuildData will use this value + queueInfoAction.setQueueTimeMillis(waitingMs); + } + } catch (NullPointerException e) { + // item.getInQueueSince() may raise a NPE if a worker node is spinning up to run the job. + // This could be expected behavior with ec2 spot instances/ecs containers, meaning no waiting + // queue times if the plugin is spinning up an instance/container for one/first job. + logger.warning("Unable to get queue waiting time. " + + "item.getInQueueSince() unavailable, possibly due to worker instance provisioning"); + waitingMs = null; + } + // Collect Build Data BuildData buildData; try { @@ -218,30 +239,17 @@ public void onStarted(Run run, TaskListener listener) { DatadogEvent event = new BuildStartedEventImpl(buildData); client.event(event); } - // Send a metric - // item.getInQueueSince() may raise a NPE if a worker node is spinning up to run the job. - // This could be expected behavior with ec2 spot instances/ecs containers, meaning no waiting - // queue times if the plugin is spinning up an instance/container for one/first job. - Queue queue = getQueue(); - Queue.Item item = queue.getItem(run.getQueueId()); + Map> tags = buildData.getTags(); String hostname = buildData.getHostname(DatadogUtilities.getHostname(null)); - try (Metrics metrics = client.metrics()) { - long waitingMs = (DatadogUtilities.currentTimeMillis() - item.getInQueueSince()); - metrics.gauge("jenkins.job.waiting", TimeUnit.MILLISECONDS.toSeconds(waitingMs), hostname, tags); - PipelineQueueInfoAction queueInfoAction = run.getAction(PipelineQueueInfoAction.class); - if (queueInfoAction != null) { - queueInfoAction.setQueueTimeMillis(waitingMs); + if (waitingMs != null) { + try (MetricsClient metrics = client.metrics()) { + metrics.gauge("jenkins.job.waiting", TimeUnit.MILLISECONDS.toSeconds(waitingMs), hostname, tags); } - - } catch (NullPointerException e) { - logger.warning("Unable to compute 'waiting' metric. " + - "item.getInQueueSince() unavailable, possibly due to worker instance provisioning"); } - // Submit counter - client.incrementCounter("jenkins.job.started", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.job.started", hostname, tags); // APM Traces if (DatadogUtilities.getDatadogGlobalDescriptor().getEnableCiVisibility()) { @@ -289,7 +297,7 @@ public void onCompleted(Run run, @Nonnull TaskListener listener) { return; } - try (Metrics metrics = client.metrics()) { + try (MetricsClient metrics = client.metrics()) { // Collect Build Data BuildData buildData; try { @@ -325,8 +333,7 @@ public void onCompleted(Run run, @Nonnull TaskListener listener) { String.format("[%s]: Build Duration (without pause): %s", buildData.getJobName(), toTimeString(buildDuration))); } - // Submit counter - client.incrementCounter("jenkins.job.completed", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.job.completed", hostname, tags); // Send a service check String buildResult = buildData.getResult(Result.NOT_BUILT.toString()); @@ -479,9 +486,8 @@ public void onDeleted(Run run) { client.event(event); } - // Submit counter Map> tags = buildData.getTags(); - client.incrementCounter("jenkins.job.aborted", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.job.aborted", hostname, tags); logger.fine("End DatadogBuildListener#onDeleted"); } catch (Exception e) { diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogComputerListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogComputerListener.java index b1f6db317..0d6fa1427 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogComputerListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogComputerListener.java @@ -34,6 +34,7 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; import org.datadog.jenkins.plugins.datadog.events.*; import org.datadog.jenkins.plugins.datadog.util.TagsUtil; import javax.annotation.CheckForNull; @@ -81,9 +82,8 @@ public void onOnline(Computer computer, TaskListener listener) throws IOExceptio client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.computer.online", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.computer.online", hostname, tags); logger.fine("End DatadogComputerListener#onOnline"); } catch (Exception e) { @@ -114,9 +114,8 @@ public void onOffline(@Nonnull Computer computer, @CheckForNull OfflineCause cau client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.computer.offline", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.computer.offline", hostname, tags); logger.fine("End DatadogComputerListener#onOffline"); } catch (Exception e) { @@ -147,10 +146,8 @@ public void onTemporarilyOnline(Computer computer) { client.event(event); } - - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.computer.temporarily_online", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.computer.temporarily_online", hostname, tags); logger.fine("End DatadogComputerListener#onTemporarilyOnline"); } catch (Exception e) { @@ -180,9 +177,8 @@ public void onTemporarilyOffline(Computer computer, OfflineCause cause) { client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.computer.temporarily_offline", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.computer.temporarily_offline", hostname, tags); logger.fine("End DatadogComputerListener#onTemporarilyOffline"); } catch (Exception e) { @@ -213,9 +209,8 @@ public void onLaunchFailure(Computer computer, TaskListener taskListener) throws client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.computer.launch_failure", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.computer.launch_failure", hostname, tags); logger.fine("End DatadogComputerListener#onLaunchFailure"); } catch (Exception e) { diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogGraphListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogGraphListener.java index e1b283353..9c2dcfe0f 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogGraphListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogGraphListener.java @@ -41,7 +41,8 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.audit.DatadogAudit; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; -import org.datadog.jenkins.plugins.datadog.clients.Metrics; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; import org.datadog.jenkins.plugins.datadog.model.BuildData; import org.datadog.jenkins.plugins.datadog.model.DatadogPluginAction; import org.datadog.jenkins.plugins.datadog.model.PipelineStepData; @@ -116,7 +117,7 @@ public void onNewHead(FlowNode flowNode) { return; } - try (Metrics metrics = client.metrics()) { + try (MetricsClient metrics = client.metrics()) { String result = DatadogUtilities.getResultTag(endNode); String hostname = null; @@ -144,7 +145,7 @@ public void onNewHead(FlowNode flowNode) { metrics.gauge("jenkins.job.stage_duration", getTime(startNode, endNode), hostname, tags); metrics.gauge("jenkins.job.stage_pause_duration", pauseDuration, hostname, tags); - client.incrementCounter("jenkins.job.stage_completed", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.job.stage_completed", hostname, tags); } catch (InterruptedException e) { Thread.currentThread().interrupt(); DatadogUtilities.severe(logger, e, "Interrupted while trying to submit the stage duration metric for " + getStageName(startNode)); diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogItemListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogItemListener.java index 36a7f9ef1..6b6baa767 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogItemListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogItemListener.java @@ -32,6 +32,7 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; import org.datadog.jenkins.plugins.datadog.events.ItemCRUDEventImpl; import org.datadog.jenkins.plugins.datadog.events.ItemCopiedEventImpl; import org.datadog.jenkins.plugins.datadog.events.ItemLocationChangedEventImpl; @@ -92,9 +93,8 @@ private void onCRUD(Item item, String action) { client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.item." + action.toLowerCase(), hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.item." + action.toLowerCase(), hostname, tags); logger.fine("End DatadogItemListener#on" + action); } catch (Exception e) { @@ -126,9 +126,8 @@ public void onCopied(Item src, Item item) { client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.item.copied", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.item.copied", hostname, tags); logger.fine("End DatadogItemListener#onCopied"); } catch (Exception e) { @@ -160,9 +159,8 @@ public void onLocationChanged(Item item, String oldFullName, String newFullName) client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.item.location_changed", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.item.location_changed", hostname, tags); logger.fine("End DatadogItemListener#onLocationChanged"); } catch (Exception e) { diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSCMListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSCMListener.java index 77a4713a0..51dc21de0 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSCMListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSCMListener.java @@ -49,6 +49,7 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.audit.DatadogAudit; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; import org.datadog.jenkins.plugins.datadog.events.SCMCheckoutCompletedEventImpl; import org.datadog.jenkins.plugins.datadog.model.BuildData; import org.datadog.jenkins.plugins.datadog.model.GitCommitAction; @@ -143,10 +144,9 @@ public void onCheckout(Run build, SCM scm, FilePath workspace, TaskListene client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); Map> tags = buildData.getTags(); - client.incrementCounter("jenkins.scm.checkout", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.scm.checkout", hostname, tags); logger.fine("End DatadogSCMListener#onCheckout"); } catch (InterruptedException e) { diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSaveableListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSaveableListener.java index b105fb67c..11c49e2ca 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSaveableListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSaveableListener.java @@ -30,9 +30,9 @@ of this software and associated documentation files (the "Software"), to deal import hudson.model.Saveable; import hudson.model.listeners.SaveableListener; import org.datadog.jenkins.plugins.datadog.DatadogClient; -import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; import org.datadog.jenkins.plugins.datadog.util.TagsUtil; import java.util.Map; @@ -65,9 +65,8 @@ public void onChange(Saveable config, XmlFile file) { return; } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.config.changed", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.config.changed", hostname, tags); logger.fine("End DatadogSaveableListener#onChange"); } catch (Exception e) { diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSecurityListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSecurityListener.java index 5a3e74afb..a9321d926 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSecurityListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSecurityListener.java @@ -32,6 +32,7 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; import org.datadog.jenkins.plugins.datadog.events.UserAuthenticationEventImpl; import org.datadog.jenkins.plugins.datadog.util.TagsUtil; @@ -75,9 +76,8 @@ protected void authenticated(@Nonnull UserDetails details) { client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.user.authenticated", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.user.authenticated", hostname, tags); logger.fine("End DatadogSecurityListener#authenticated"); } catch (Exception e) { @@ -108,9 +108,8 @@ protected void failedToAuthenticate(@Nonnull String username) { client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.user.access_denied", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.user.access_denied", hostname, tags); logger.fine("End DatadogSecurityListener#failedToAuthenticate"); } catch (Exception e) { @@ -151,9 +150,8 @@ protected void loggedOut(@Nonnull String username) { client.event(event); } - // Submit counter String hostname = DatadogUtilities.getHostname(null); - client.incrementCounter("jenkins.user.logout", hostname, tags); + Metrics.getInstance().incrementCounter("jenkins.user.logout", hostname, tags); logger.fine("End DatadogSecurityListener#loggedOut"); } catch (Exception e) { diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/CounterMetric.java b/src/main/java/org/datadog/jenkins/plugins/datadog/metrics/MetricKey.java similarity index 58% rename from src/main/java/org/datadog/jenkins/plugins/datadog/clients/CounterMetric.java rename to src/main/java/org/datadog/jenkins/plugins/datadog/metrics/MetricKey.java index 8cc2be6a7..c40b0fe27 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/CounterMetric.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/metrics/MetricKey.java @@ -23,19 +23,19 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. */ -package org.datadog.jenkins.plugins.datadog.clients; +package org.datadog.jenkins.plugins.datadog.metrics; -import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; -public class CounterMetric { +public class MetricKey { - private Map> tags = new HashMap<>(); - private String metricName; - private String hostname; + private final Map> tags; + private final String metricName; + private final String hostname; - public CounterMetric(Map> tags, String metricName, String hostname) { + public MetricKey(Map> tags, String metricName, String hostname) { this.tags = tags; this.metricName = metricName; this.hostname = hostname; @@ -45,49 +45,36 @@ public Map> getTags() { return tags; } - public void setTags(Map> 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; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MetricKey that = (MetricKey) o; + return Objects.equals(tags, that.tags) + && Objects.equals(metricName, that.metricName) + && Objects.equals(hostname, that.hostname); } @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; + return Objects.hash(tags, metricName, hostname); } @Override public String toString() { - return "CounterMetric{" + + return "MetricKey{" + "tags=" + tags + ", metricName='" + metricName + '\'' + ", hostname='" + hostname + '\'' + diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/metrics/Metrics.java b/src/main/java/org/datadog/jenkins/plugins/datadog/metrics/Metrics.java new file mode 100644 index 000000000..f96195793 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/metrics/Metrics.java @@ -0,0 +1,55 @@ +/* +The MIT License + +Copyright (c) 2015-Present Datadog, Inc +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ + +package org.datadog.jenkins.plugins.datadog.metrics; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReference; + +public class Metrics { + + private static final Metrics instance = new Metrics(); + + public static Metrics getInstance() { + return instance; + } + + private final AtomicReference> counters = new AtomicReference<>(new ConcurrentHashMap<>()); + + private Metrics(){} + + public void incrementCounter(String name, String hostname, Map> tags) { + MetricKey key = new MetricKey(tags, name, hostname); + counters.get().merge(key, 1, Integer::sum); + } + + public Map getAndResetCounters() { + return counters.getAndSet(new ConcurrentHashMap<>()); + } + +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/metrics/MetricsClient.java b/src/main/java/org/datadog/jenkins/plugins/datadog/metrics/MetricsClient.java new file mode 100644 index 000000000..f2d044290 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/metrics/MetricsClient.java @@ -0,0 +1,26 @@ +package org.datadog.jenkins.plugins.datadog.metrics; + +import java.util.Map; +import java.util.Set; + +public interface MetricsClient extends AutoCloseable { + /** + * Sends a metric to the Datadog API, including the gauge name and value. + * + * @param name - A String with the name of the metric to record. + * @param value - A long containing the value to submit. + * @param hostname - A String with the hostname to submit. + * @param tags - A Map containing the tags to submit. + */ + void gauge(String name, double value, String hostname, Map> tags); + + /** + * Sends a rate metric to the Datadog API, including the counter name and value. + * + * @param name - A String with the name of the metric to record. + * @param value - A long containing the value to submit. + * @param hostname - A String with the hostname to submit. + * @param tags - A Map containing the tags to submit. + */ + void rate(String name, double value, String hostname, Map> tags); +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogComputerPublisher.java b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogComputerPublisher.java index 8e76b7d3c..03fb833da 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogComputerPublisher.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogComputerPublisher.java @@ -27,19 +27,23 @@ of this software and associated documentation files (the "Software"), to deal import hudson.Extension; import hudson.model.Computer; +import hudson.model.Executor; import hudson.model.PeriodicWork; +import hudson.model.Queue; +import hudson.model.Run; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; import jenkins.model.Jenkins; import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; -import org.datadog.jenkins.plugins.datadog.clients.Metrics; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; import org.datadog.jenkins.plugins.datadog.util.TagsUtil; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; - /** * This class registers a {@link PeriodicWork} with Jenkins to run periodically in order to enable * us to compute metrics related to nodes and executors. @@ -65,7 +69,7 @@ protected void doRun() throws Exception { return; } - try (Metrics metrics = client.metrics()) { + try (MetricsClient metrics = client.metrics()) { String hostname = DatadogUtilities.getHostname(null); long nodeCount = 0; long nodeOffline = 0; @@ -85,7 +89,7 @@ protected void doRun() throws Exception { if (computer.isOffline()) { nodeOffline++; metrics.gauge("jenkins.node_status.up", 0, hostname, tags); - } + } if (computer.isOnline()) { nodeOnline++; metrics.gauge("jenkins.node_status.up", 1, hostname, tags); @@ -100,14 +104,40 @@ protected void doRun() throws Exception { metrics.gauge("jenkins.executor.in_use", inUse, hostname, tags); metrics.gauge("jenkins.executor.free", free, hostname, tags); } + metrics.gauge("jenkins.node.count", nodeCount, hostname, globalTags); metrics.gauge("jenkins.node.offline", nodeOffline, hostname, globalTags); metrics.gauge("jenkins.node.online", nodeOnline, hostname, globalTags); + metrics.gauge("jenkins.job.currently_building", getCurrentRunCount(computers), hostname, globalTags); } catch (Exception e) { DatadogUtilities.severe(logger, e, "Failed to compute and send node metrics"); } - + } + + private int getCurrentRunCount(Computer[] computers) { + Set> runs = new HashSet<>(); + for (Computer computer : computers) { + List executors = computer.getExecutors(); + for (Executor executor : executors) { + Run run = getCurrentRun(executor); + if (run != null) { + runs.add(run); + } + } + } + return runs.size(); + } + + private Run getCurrentRun(Executor executor) { + Queue.Executable executable = executor.getCurrentExecutable(); + while (executable != null) { + if (executable instanceof Run) { + return (Run) executable; + } + executable = executable.getParentExecutable(); + } + return null; } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogCountersPublisher.java b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogCountersPublisher.java index 020f679b6..96e8f4990 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogCountersPublisher.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogCountersPublisher.java @@ -26,15 +26,19 @@ of this software and associated documentation files (the "Software"), to deal package org.datadog.jenkins.plugins.datadog.publishers; import hudson.Extension; -import hudson.model.*; +import hudson.model.AsyncPeriodicWork; +import hudson.model.TaskListener; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import java.util.logging.Logger; import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; +import org.datadog.jenkins.plugins.datadog.metrics.MetricKey; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; @Extension public class DatadogCountersPublisher extends AsyncPeriodicWork { @@ -52,16 +56,26 @@ public long getRecurrencePeriod() { @Override protected void execute(TaskListener taskListener) throws IOException, InterruptedException { - try { - logger.fine("Execute called: Publishing counters"); + logger.fine("Execute called: Publishing counters"); + DatadogClient client = ClientFactory.getClient(); + if (client == null) { + return; + } + + publishMetrics(client); + } - // Get Datadog Client Instance - DatadogClient client = ClientFactory.getClient(); - if(client == null){ - return; + // exposed to trigger metrics flush in tests + public static void publishMetrics(DatadogClient client) { + try (MetricsClient metricsClient = client.metrics()) { + Map counters = Metrics.getInstance().getAndResetCounters(); + for (Map.Entry entry : counters.entrySet()) { + MetricKey metric = entry.getKey(); + Number value = entry.getValue(); + logger.fine("Flushing: " + metric.getMetricName() + " - " + value); + metricsClient.rate(metric.getMetricName(), value.doubleValue(), metric.getHostname(), metric.getTags()); } - client.flushCounters(); } catch (Exception e) { DatadogUtilities.severe(logger, e, "Failed to publish counters"); } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogJenkinsPublisher.java b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogJenkinsPublisher.java index 7953dfd38..cae7cfa54 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogJenkinsPublisher.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogJenkinsPublisher.java @@ -34,7 +34,7 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; -import org.datadog.jenkins.plugins.datadog.clients.Metrics; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; import org.datadog.jenkins.plugins.datadog.model.PluginData; import org.datadog.jenkins.plugins.datadog.util.TagsUtil; @@ -85,7 +85,7 @@ protected void doRun() throws Exception { } PluginData pluginData = collectPluginData(instance); - try (Metrics metrics = client.metrics()) { + try (MetricsClient metrics = client.metrics()) { metrics.gauge("jenkins.project.count", projectCount, hostname, tags); metrics.gauge("jenkins.plugin.count", pluginData.getCount(), hostname, tags); metrics.gauge("jenkins.plugin.active", pluginData.getActive(), hostname, tags); diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogQueuePublisher.java b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogQueuePublisher.java index 2cbda3715..3636ddee1 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogQueuePublisher.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogQueuePublisher.java @@ -38,7 +38,7 @@ of this software and associated documentation files (the "Software"), to deal import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; -import org.datadog.jenkins.plugins.datadog.clients.Metrics; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; import org.datadog.jenkins.plugins.datadog.util.TagsUtil; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.support.steps.ExecutorStepExecution; @@ -69,7 +69,7 @@ protected void doRun() throws Exception { return; } - try (Metrics metrics = client.metrics()) { + try (MetricsClient metrics = client.metrics()) { Map> tags = DatadogUtilities.getTagsFromGlobalTags(); // Add JenkinsUrl Tag tags = TagsUtil.addTagToTags(tags, "jenkins_url", DatadogUtilities.getJenkinsUrl()); diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientStub.java b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientStub.java index f39a97488..8d3fe3d3b 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientStub.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientStub.java @@ -45,6 +45,7 @@ of this software and associated documentation files (the "Software"), to deal import net.sf.json.JSONObject; import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogEvent; +import org.datadog.jenkins.plugins.datadog.metrics.MetricsClient; import org.datadog.jenkins.plugins.datadog.model.BuildData; import org.datadog.jenkins.plugins.datadog.model.PipelineStepData; import org.datadog.jenkins.plugins.datadog.traces.DatadogTraceBuildLogic; @@ -79,32 +80,18 @@ public boolean event(DatadogEvent event) { } @Override - public boolean incrementCounter(String name, String hostname, Map> tags) { - for (DatadogMetric m : this.metrics) { - if (m.same(new DatadogMetric(name, 0, hostname, convertTagMapToList(tags)))) { - double value = m.getValue() + 1; - this.metrics.remove(m); - this.metrics.add(new DatadogMetric(name, value, hostname, convertTagMapToList(tags))); - return true; - } - } - this.metrics.add(new DatadogMetric(name, 1, hostname, convertTagMapToList(tags))); - return true; - } - - @Override - public void flushCounters() { - // noop - } - - @Override - public Metrics metrics() { + public MetricsClient metrics() { return new StubMetrics(); } - public final class StubMetrics implements Metrics { + public final class StubMetrics implements MetricsClient { + @Override + public void gauge(String name, double value, String hostname, Map> tags) { + metrics.add(new DatadogMetric(name, value, hostname, convertTagMapToList(tags))); + } + @Override - public void gauge(String name, long value, String hostname, Map> tags) { + public void rate(String name, double value, String hostname, Map> tags) { metrics.add(new DatadogMetric(name, value, hostname, convertTagMapToList(tags))); } diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java index 79e194405..a557659e4 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java @@ -28,6 +28,8 @@ of this software and associated documentation files (the "Software"), to deal 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.metrics.MetricKey; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; @@ -139,31 +141,31 @@ public void testIncrementCountAndFlush() throws IOException, InterruptedExceptio Map> tags1 = new HashMap<>(); tags1 = DatadogClientStub.addTagToMap(tags1, "tag1", "value"); tags1 = DatadogClientStub.addTagToMap(tags1, "tag2", "value"); - client.incrementCounter("metric1", "host1", tags1); - client.incrementCounter("metric1", "host1", tags1); + Metrics.getInstance().incrementCounter("metric1", "host1", tags1); + Metrics.getInstance().incrementCounter("metric1", "host1", tags1); Map> tags2 = new HashMap<>(); tags2 = DatadogClientStub.addTagToMap(tags2, "tag1", "value"); tags2 = DatadogClientStub.addTagToMap(tags2, "tag2", "value"); tags2 = DatadogClientStub.addTagToMap(tags2, "tag3", "value"); - client.incrementCounter("metric1", "host1", tags2); + Metrics.getInstance().incrementCounter("metric1", "host1", tags2); - client.incrementCounter("metric1", "host2", tags2); - client.incrementCounter("metric1", "host2", tags2); + Metrics.getInstance().incrementCounter("metric1", "host2", tags2); + Metrics.getInstance().incrementCounter("metric1", "host2", tags2); - client.incrementCounter("metric2", "host2", tags2); + Metrics.getInstance().incrementCounter("metric2", "host2", tags2); // The following code should be the same as in the flushCounters method - ConcurrentMap counters = ConcurrentMetricCounters.getInstance().getAndReset(); + Map counters = Metrics.getInstance().getAndResetCounters(); // Check counter is reset as expected - ConcurrentMap countersEmpty = ConcurrentMetricCounters.getInstance().getAndReset(); + Map countersEmpty = Metrics.getInstance().getAndResetCounters(); Assert.assertTrue("size = " + countersEmpty.size(), countersEmpty.size() == 0); // Check that metrics to submit are correct boolean check1 = false, check2 = false, check3 = false, check4 = false; Assert.assertTrue("counters = " + counters.size(), counters.size() == 4); - for (CounterMetric counterMetric: counters.keySet()) { + for (MetricKey counterMetric: counters.keySet()) { int count = counters.get(counterMetric); if(counterMetric.getMetricName().equals("metric1") && counterMetric.getHostname().equals("host1") && counterMetric.getTags().size() == 2){ @@ -201,7 +203,7 @@ public void run() { Map> tags = new HashMap<>(); tags = DatadogClientStub.addTagToMap(tags, "tag1", "value"); tags = DatadogClientStub.addTagToMap(tags, "tag2", "value"); - client.incrementCounter("metric1", "host1", tags); + Metrics.getInstance().incrementCounter("metric1", "host1", tags); } }; @@ -212,7 +214,7 @@ public void run() { stop(executor); // Check counter is reset as expected - ConcurrentMap counters = ConcurrentMetricCounters.getInstance().getAndReset(); + Map counters = Metrics.getInstance().getAndResetCounters(); Assert.assertTrue("size = " + counters.size(), counters.size() == 1); Assert.assertTrue("counters.values() = " + counters.values(), counters.values().contains(10000)); @@ -230,7 +232,7 @@ public void run() { Map> tags = new HashMap<>(); tags = DatadogClientStub.addTagToMap(tags, "tag1", "value"); tags = DatadogClientStub.addTagToMap(tags, "tag2", "value"); - client.incrementCounter("metric1", "host1", tags); + Metrics.getInstance().incrementCounter("metric1", "host1", tags); } }; @@ -246,7 +248,7 @@ public void run() { @Override public Boolean call() throws Exception { // Check counter is reset as expected - ConcurrentMap counters = ConcurrentMetricCounters.getInstance().getAndReset(); + Map counters = Metrics.getInstance().getAndResetCounters(); Assert.assertTrue("size = " + counters.size(), counters.size() == 1); Assert.assertTrue("counters.values() = " + counters.values(), counters.values().contains(10000)); return true; @@ -272,7 +274,7 @@ public void run() { Map> tags = new HashMap<>(); tags = DatadogClientStub.addTagToMap(tags, "tag1", "value"); tags = DatadogClientStub.addTagToMap(tags, "tag2", "value"); - client.incrementCounter("metric1", "host1", tags); + Metrics.getInstance().incrementCounter("metric1", "host1", tags); } }; @@ -283,7 +285,7 @@ public void run() { stop(executor); // Check counter is reset as expected - ConcurrentMap counters = ConcurrentMetricCounters.getInstance().getAndReset(); + Map counters = Metrics.getInstance().getAndResetCounters(); Assert.assertTrue("size = " + counters.size(), counters.size() == 1); Assert.assertTrue("counters.values() = " + counters.values(), counters.values().contains(10000)); diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListenerTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListenerTest.java index 4d7058ada..538faf00c 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListenerTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListenerTest.java @@ -32,6 +32,8 @@ of this software and associated documentation files (the "Software"), to deal import jenkins.model.Jenkins; import org.datadog.jenkins.plugins.datadog.DatadogEvent.AlertType; import org.datadog.jenkins.plugins.datadog.DatadogEvent.Priority; +import org.datadog.jenkins.plugins.datadog.metrics.Metrics; +import org.datadog.jenkins.plugins.datadog.publishers.DatadogCountersPublisher; import org.datadog.jenkins.plugins.datadog.stubs.BuildStub; import org.datadog.jenkins.plugins.datadog.stubs.ProjectStub; import org.datadog.jenkins.plugins.datadog.clients.DatadogClientStub; @@ -116,6 +118,8 @@ public void testOnCompletedOnSuccessfulRun() throws Exception { 124000L, 4, previousFailedRun2, 4000000L, null); datadogBuildListener.onCompleted(previousSuccessfulRun, mock(TaskListener.class)); + DatadogCountersPublisher.publishMetrics(client); + String[] scExpectedTags1 = new String[]{ "job:ParentFullName/JobName", "node:test-node", "user_id:anonymous", "jenkins_url:unknown", "branch:test-branch" }; @@ -124,35 +128,43 @@ public void testOnCompletedOnSuccessfulRun() throws Exception { metricExpectedTags1[5] = "result:SUCCESS"; client.assertMetric("jenkins.job.duration", 121, "test-hostname-2", metricExpectedTags1); client.assertMetric("jenkins.job.leadtime", 121, "test-hostname-2", metricExpectedTags1); + client.assertMetric("jenkins.job.completed", 1, "test-hostname-2", metricExpectedTags1); client.assertServiceCheck("jenkins.job.status", 0, "test-hostname-2", scExpectedTags1); client.assertEvent("Job ParentFullName/JobName build #1 success on test-hostname-2", Priority.LOW, AlertType.SUCCESS, 1121L); datadogBuildListener.onCompleted(previousFailedRun1, mock(TaskListener.class)); + DatadogCountersPublisher.publishMetrics(client); + String[] metricExpectedTags2 = new String[6]; System.arraycopy(scExpectedTags1, 0, metricExpectedTags2, 0, 5); metricExpectedTags2[5] = "result:FAILURE"; client.assertMetric("jenkins.job.duration", 122, "test-hostname-2", metricExpectedTags2); client.assertMetric("jenkins.job.feedbacktime", 122, "test-hostname-2", metricExpectedTags2); + client.assertMetric("jenkins.job.completed", 1, "test-hostname-2", metricExpectedTags2); client.assertServiceCheck("jenkins.job.status", 2, "test-hostname-2", scExpectedTags1); client.assertEvent("Job ParentFullName/JobName build #2 failure on test-hostname-2", Priority.NORMAL, AlertType.ERROR, 2122L); datadogBuildListener.onCompleted(previousFailedRun2, mock(TaskListener.class)); + DatadogCountersPublisher.publishMetrics(client); + client.assertMetric("jenkins.job.duration", 123, "test-hostname-2", metricExpectedTags2); client.assertMetric("jenkins.job.feedbacktime", 123, "test-hostname-2", metricExpectedTags2); - client.assertMetric("jenkins.job.completed", 2, "test-hostname-2", metricExpectedTags2); + client.assertMetric("jenkins.job.completed", 1, "test-hostname-2", metricExpectedTags2); client.assertServiceCheck("jenkins.job.status", 2, "test-hostname-2", scExpectedTags1); client.assertEvent("Job ParentFullName/JobName build #3 failure on test-hostname-2", Priority.NORMAL, AlertType.ERROR, 3123L); datadogBuildListener.onCompleted(successRun, mock(TaskListener.class)); + DatadogCountersPublisher.publishMetrics(client); + client.assertMetric("jenkins.job.duration", 124, "test-hostname-2", metricExpectedTags1); client.assertMetric("jenkins.job.leadtime", 2124, "test-hostname-2", metricExpectedTags1); client.assertMetric("jenkins.job.cycletime", (4000+124)-(1000+121), "test-hostname-2", metricExpectedTags1); client.assertMetric("jenkins.job.mttr", 4000-2000, "test-hostname-2", metricExpectedTags1); client.assertServiceCheck("jenkins.job.status", 0, "test-hostname-2", scExpectedTags1); - client.assertMetric("jenkins.job.completed", 2, "test-hostname-2", metricExpectedTags1); + client.assertMetric("jenkins.job.completed", 1, "test-hostname-2", metricExpectedTags1); client.assertEvent("Job ParentFullName/JobName build #4 success on test-hostname-2", Priority.LOW, AlertType.SUCCESS, 4124L); client.assertedAllMetricsAndServiceChecks(); @@ -192,6 +204,7 @@ public void testOnCompletedWorkflowRun() throws Exception { ((DatadogBuildListenerTestWrapper)datadogBuildListener).setStubbedRunExt(runExt); datadogBuildListener.onCompleted(workflowRun, listener); + DatadogCountersPublisher.publishMetrics(client); String[] expectedTags = { "result:SUCCESS", @@ -217,6 +230,8 @@ public void testOnCompletedOnFailedRun() throws Exception { 124000L, 2, null, 2000000L, previousSuccessfulRun); datadogBuildListener.onCompleted(previousSuccessfulRun, mock(TaskListener.class)); + DatadogCountersPublisher.publishMetrics(client); + String[] scExpectedTags = new String[]{ "job:ParentFullName/JobName", "node:test-node", "user_id:anonymous", "jenkins_url:unknown", "branch:test-branch" }; @@ -232,6 +247,8 @@ public void testOnCompletedOnFailedRun() throws Exception { client.assertedAllMetricsAndServiceChecks(); datadogBuildListener.onCompleted(failedRun, mock(TaskListener.class)); + DatadogCountersPublisher.publishMetrics(client); + String[] metricExpectedTags2 = new String[6]; System.arraycopy(scExpectedTags, 0, metricExpectedTags2, 0, 5); metricExpectedTags2[5] = "result:FAILURE"; @@ -255,6 +272,7 @@ public void testEvents() throws Exception { 124000L, 2, null, 2000000L, previousSuccessfulRun); datadogBuildListener.onCompleted(previousSuccessfulRun, mock(TaskListener.class)); + client.assertEvent("Job ParentFullName/JobName build #1 success on test-hostname-2", Priority.LOW, AlertType.SUCCESS, 1123L); @@ -288,6 +306,8 @@ public void testOnStarted() throws Exception { 123000L, 1, null, 1000000L, null); datadogBuildListener.onStarted(run, mock(TaskListener.class)); + DatadogCountersPublisher.publishMetrics(client); + String[] expectedTags = new String[6]; expectedTags[0] = "job:ParentFullName/JobName"; expectedTags[1] = "node:test-node"; diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogGraphListenerTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogGraphListenerTest.java index 95d0ac7af..f42b70b39 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogGraphListenerTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogGraphListenerTest.java @@ -53,6 +53,7 @@ import org.datadog.jenkins.plugins.datadog.clients.ClientFactory; import org.datadog.jenkins.plugins.datadog.clients.DatadogClientStub; import org.datadog.jenkins.plugins.datadog.model.PipelineStepData; +import org.datadog.jenkins.plugins.datadog.publishers.DatadogCountersPublisher; import org.datadog.jenkins.plugins.datadog.traces.CITags; import org.datadog.jenkins.plugins.datadog.traces.message.TraceSpan; import org.jenkinsci.plugins.workflow.actions.LabelAction; @@ -167,6 +168,9 @@ public void testNewNode() throws IOException { when(endNode.getExecution()).thenReturn(flowExecution); listener.onNewHead(endNode); + + DatadogCountersPublisher.publishMetrics(clientStub); + String hostname = DatadogUtilities.getHostname(null); String[] expectedTags = new String[] { "jenkins_url:" + DatadogUtilities.getJenkinsUrl(), "user_id:anonymous", "stage_name:low", "job:pipeline", "parent_stage_name:medium", "stage_depth:2", "result:SUCCESS" };