Skip to content

Commit

Permalink
Aggregate buildStarted messages based on interval provided by user
Browse files Browse the repository at this point in the history
  • Loading branch information
Jimilian committed Oct 26, 2017
1 parent 47c4bea commit 1edbeab
Show file tree
Hide file tree
Showing 14 changed files with 740 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,15 @@ public class PluginConfig implements GerritWorkersConfig {
* Default number of sending worker threads.
*/
public static final int DEFAULT_NR_OF_SENDING_WORKER_THREADS = 1;
/**
* Default time for sending feedback aggregation.
*/
public static final int DEFAULT_AGGREGATION_TIME = 0;

private int numberOfReceivingWorkerThreads;
private int numberOfSendingWorkerThreads;
private int replicationCacheExpirationInMinutes;
private int aggregateJobStartedInterval;

/**
* Constructs a config with default data.
Expand All @@ -75,6 +80,7 @@ public PluginConfig(PluginConfig pluginConfig) {
numberOfReceivingWorkerThreads = pluginConfig.getNumberOfReceivingWorkerThreads();
numberOfSendingWorkerThreads = pluginConfig.getNumberOfSendingWorkerThreads();
replicationCacheExpirationInMinutes = pluginConfig.getReplicationCacheExpirationInMinutes();
aggregateJobStartedInterval = pluginConfig.getAggregateJobStartedInterval();
}

/**
Expand Down Expand Up @@ -112,6 +118,11 @@ public void setValues(JSONObject formData) {
if (replicationCacheExpirationInMinutes <= 0) {
replicationCacheExpirationInMinutes = ReplicationCache.DEFAULT_EXPIRATION_IN_MINUTES;
}

aggregateJobStartedInterval = formData.optInt("aggregateJobStartedInterval", DEFAULT_AGGREGATION_TIME);
if (aggregateJobStartedInterval <= 0) {
aggregateJobStartedInterval = DEFAULT_AGGREGATION_TIME;
}
}

/**
Expand Down Expand Up @@ -178,4 +189,26 @@ public int getReplicationCacheExpirationInMinutes() {
public void setReplicationCacheExpirationInMinutes(int replicationCacheExpirationInMinutes) {
this.replicationCacheExpirationInMinutes = replicationCacheExpirationInMinutes;
}

/**
* Aggregate job started messages for some interval.
* I.e. setting this value to 2 means
* "wait first 2 minutes and send all notification only if 2 minutes passed or lst build had started already"
*
* @param aggregateJobStartedInterval time to wait builds before sending start messages
*/
public void setAggregateJobStartedInterval(Integer aggregateJobStartedInterval) {
this.aggregateJobStartedInterval = aggregateJobStartedInterval;
}

/**
* Aggregate job started messages for some interval.
* I.e. "10" means
* "first wait 10 seconds and send all notification only if 10 seconds passed or lst build had started already"
*
* @return aggregation interval
*/
public int getAggregateJobStartedInterval() {
return aggregateJobStartedInterval;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
* Start position that notifies Gerrit of events.
* @author Robert Sandell &lt;[email protected]&gt;
Expand Down Expand Up @@ -70,18 +72,24 @@ public GerritNotifier(IGerritHudsonTriggerConfig config, GerritCmdRunner cmdRunn

/**
* Generates the build-started command based on configured templates and build-values and sends it to Gerrit.
* @param build the build.
* @param builds the builds.
* @param taskListener the taskListener.
* @param event the event.
* @param stats the stats.
*/
public void buildStarted(Run build, TaskListener taskListener,
GerritTriggeredEvent event, BuildsStartedStats stats) {
public void buildStarted(List<Run> builds, TaskListener taskListener,
GerritTriggeredEvent event, BuildsStartedStats stats) {
try {
/* Without a change, it doesn't make sense to notify gerrit */
if (event instanceof ChangeBasedEvent) {
String command =
parameterExpander.getBuildStartedCommand(build, taskListener, (ChangeBasedEvent)event, stats);
String command;
if (builds.size() == 1) {
command = parameterExpander.getBuildStartedCommand(builds.get(0), taskListener,
(ChangeBasedEvent)event, stats);
} else {
command = parameterExpander.getBuildsStartedCommand(builds, taskListener,
(ChangeBasedEvent)event, stats);
}
if (command != null) {
if (!command.isEmpty()) {
logger.info("Notifying BuildStarted to gerrit: {}", command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
* A factory for creating notification entities.
* This factory is mainly created and used to ease unit testing.
Expand Down Expand Up @@ -183,24 +185,24 @@ private String getServerName(GerritTriggeredEvent event) {
/**
* Queues a build started command on the send-command queue.
*
* @param build the build.
* @param builds the build.
* @param listener a listener.
* @param event the event.
* @param stats the started stats.
* @see GerritSendCommandQueue#queue(com.sonymobile.tools.gerrit.gerritevents.workers.cmd.AbstractSendCommandJob)
* @see BuildStartedCommandJob
*/
public void queueBuildStarted(Run build, TaskListener listener,
public void queueBuildsStarted(List<Run> builds, TaskListener listener,
GerritTriggeredEvent event, BuildsStartedStats stats) {
String serverName = getServerName(event);
if (serverName != null) {
IGerritHudsonTriggerConfig config = getConfig(serverName);
if (config != null) {
if (config.isUseRestApi() && event instanceof ChangeBasedEvent) {
GerritSendCommandQueue.queue(new BuildStartedRestCommandJob(config, build, listener,
GerritSendCommandQueue.queue(new BuildStartedRestCommandJob(config, builds, listener,
(ChangeBasedEvent)event, stats));
} else {
GerritSendCommandQueue.queue(new BuildStartedCommandJob(config, build, listener, event, stats));
GerritSendCommandQueue.queue(new BuildStartedCommandJob(config, builds, listener, event, stats));
}
} else {
logger.warn("Nothing queued since there is no configuration for serverName: {}", serverName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@
import hudson.model.Run;
import hudson.model.TaskListener;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jenkins.model.Jenkins;
Expand Down Expand Up @@ -105,7 +107,7 @@ public String getBuildStartedCommand(Run r, TaskListener taskListener,

GerritTrigger trigger = GerritTrigger.getTrigger(r.getParent());
String gerritCmd = config.getGerritCmdBuildStarted();
Map<String, String> parameters = createStandardParameters(r, event,
Map<String, String> parameters = createStandardParameters(event,
getBuildStartedCodeReviewValue(r),
getBuildStartedVerifiedValue(r),
Notify.ALL.name());
Expand All @@ -127,11 +129,76 @@ public String getBuildStartedCommand(Run r, TaskListener taskListener,
}
}

parameters.put("BUILDURL", jenkins.getRootUrl() + r.getUrl());
parameters.put("STARTED_STATS", startedStats.toString());

return expandParameters(gerritCmd, r, taskListener, parameters);
}

/**
* Gets the expanded string to send to Gerrit for a build-started event.
* @param runs the builds.
* @param taskListener the taskListener.
* @param event the event.
* @param stats the statistics.
* @return the "expanded" command string.
*/
public String getBuildsStartedCommand(List<Run> runs, TaskListener taskListener,
ChangeBasedEvent event, BuildsStartedStats stats) {

String gerritCmd = config.getGerritCmdBuildStarted();

Integer minCodeReview = Integer.MAX_VALUE;
Integer minVerified = Integer.MAX_VALUE;

StringBuilder aggregatedBuilds = new StringBuilder();

int offset = runs.size() - 1;

for (Run r : runs) {
GerritTrigger trigger = GerritTrigger.getTrigger(r.getParent());

minCodeReview = Math.min(minCodeReview, getBuildStartedCodeReviewValue(r));
minVerified = Math.min(minVerified, getBuildStartedVerifiedValue(r));

aggregatedBuilds.append("\n\n");

String buildStartMessage = trigger.getBuildStartMessage();
if (buildStartMessage != null && !buildStartMessage.isEmpty()) {
aggregatedBuilds.append("\n\n").append(buildStartMessage);
}

String buildUrl = jenkins.getRootUrl() + r.getUrl();
aggregatedBuilds.append(buildUrl);

if (stats.getTotalBuildsToStart() > 1) {
aggregatedBuilds.append(" ");
aggregatedBuilds.append(stats.stringWithOffset(offset));
aggregatedBuilds.append(" ");
offset--;
}

if (config.isEnablePluginMessages()) {
for (GerritMessageProvider messageProvider : emptyIfNull(GerritMessageProvider.all())) {
String extensionMessage = messageProvider.getBuildStartedMessage(r);
if (extensionMessage != null) {
aggregatedBuilds.append("\n\n").append(extensionMessage);
}
}
}
}

Map<String, String> parameters = createStandardParameters(event,
minCodeReview,
minVerified,
Notify.ALL.name());

parameters.put("BUILDURL", "");
parameters.put("STARTED_STATS", aggregatedBuilds.toString());

return expandParameters(gerritCmd, runs.get(0), taskListener, parameters);
}

/**
* Helper for ensuring no NPEs when iterating iterables.
*
Expand Down Expand Up @@ -213,14 +280,13 @@ private Integer getBuildStartedCodeReviewValue(Run r) {
* <li><strong>CODE_REVIEW</strong>: The code review vote.</li>
* <li><strong>NOTIFICATION_LEVEL</strong>: The notification level.</li>
* </ul>
* @param r the build.
* @param gerritEvent the event.
* @param codeReview the code review vote.
* @param verified the verified vote.
* @param notifyLevel the notify level.
* @return the parameters and their values.
*/
private Map<String, String> createStandardParameters(Run r, GerritTriggeredEvent gerritEvent,
private Map<String, String> createStandardParameters(GerritTriggeredEvent gerritEvent,
Integer codeReview, Integer verified, String notifyLevel) {
//<GERRIT_NAME> <BRANCH> <CHANGE> <PATCHSET> <PATCHSET_REVISION> <REFSPEC> <BUILDURL> VERIFIED CODE_REVIEW
Map<String, String> map = new HashMap<String, String>(DEFAULT_PARAMETERS_COUNT);
Expand All @@ -239,9 +305,7 @@ private Map<String, String> createStandardParameters(Run r, GerritTriggeredEvent
map.put("REFSPEC", StringUtil.makeRefSpec(event));
}
}
if (r != null) {
map.put("BUILDURL", jenkins.getRootUrl() + r.getUrl());
}

map.put("VERIFIED", String.valueOf(verified));
map.put("CODE_REVIEW", String.valueOf(codeReview));
map.put("NOTIFICATION_LEVEL", notifyLevel);
Expand Down Expand Up @@ -404,6 +468,24 @@ public Integer getMinimumVerifiedValue(MemoryImprint memoryImprint, boolean only
return verified;
}

/**
* Convert entries of memoryImprint object to list of builds
*
* @param memoryImprint the memory
* @return the list of run objects from memory
*/
private List<Run> fromMemoryImprintToBuilds(MemoryImprint memoryImprint) {
final List<Run> runs = new ArrayList<Run>(memoryImprint.getEntries().length);
for (Entry entry : memoryImprint.getEntries()) {
if (entry == null || entry.getBuild() == null) {
continue;
}
runs.add(entry.getBuild());
}

return runs;
}

/**
* Returns the minimum code review value for the build results in the memory.
* If no builds have contributed to code review value, this method returns null
Expand Down Expand Up @@ -449,21 +531,30 @@ public Integer getMinimumCodeReviewValue(MemoryImprint memoryImprint, boolean on
* @return the highest configured notification level.
*/
public Notify getHighestNotificationLevel(MemoryImprint memoryImprint, boolean onlyBuilt) {
return getHighestNotificationLevel(fromMemoryImprintToBuilds(memoryImprint), onlyBuilt);
}

/**
* Returns the highest configured notification level.
*
* @param builds the list of builds
* @param onlyBuilt only count builds that completed (no NOT_BUILT builds)
* @return the highest configured notification level.
*/
public Notify getHighestNotificationLevel(List<Run> builds, boolean onlyBuilt) {
Notify highestLevel = Notify.NONE;
for (Entry entry : memoryImprint.getEntries()) {
if (entry == null) {
continue;
}
Run build = entry.getBuild();

for (Run build : builds) {
if (build == null) {
continue;
}

Result result = build.getResult();
if (onlyBuilt && result == Result.NOT_BUILT) {
continue;
}

GerritTrigger trigger = GerritTrigger.getTrigger(entry.getProject());
GerritTrigger trigger = GerritTrigger.getTrigger(build.getParent());
if (trigger == null || shouldSkip(trigger.getSkipVote(), result)) {
continue;
}
Expand All @@ -473,6 +564,7 @@ public Notify getHighestNotificationLevel(MemoryImprint memoryImprint, boolean o
highestLevel = level;
}
}

return highestLevel;
}

Expand Down Expand Up @@ -530,7 +622,7 @@ public String getBuildCompletedCommand(MemoryImprint memoryImprint, TaskListener
notifyLevel = getHighestNotificationLevel(memoryImprint, onlyCountBuilt);
}

Map<String, String> parameters = createStandardParameters(null, memoryImprint.getEvent(),
Map<String, String> parameters = createStandardParameters(memoryImprint.getEvent(),
codeReview, verified, notifyLevel.name());
// escapes ' as '"'"' in order to avoid breaking command line param
// Details: http://stackoverflow.com/a/26165123/99834
Expand Down Expand Up @@ -668,15 +760,21 @@ public String getBuildCompletedMessage(MemoryImprint memoryImprint, TaskListener
/**
* Returns cover message to be send after build has been started.
*
* @param build build
* @param builds build
* @param listener listener
* @param event event
* @param stats stats
* @return the message for the build started command.
*/
public String getBuildStartedMessage(Run build, TaskListener listener, ChangeBasedEvent event,
BuildsStartedStats stats) {
String startedCommand = getBuildStartedCommand(build, listener, event, stats);
public String getBuildsStartedMessage(List<Run> builds, TaskListener listener, ChangeBasedEvent event,
BuildsStartedStats stats) {
String startedCommand;
if (builds.size() == 1) {
startedCommand = getBuildStartedCommand(builds.get(0), listener, event, stats);
} else {
startedCommand = getBuildsStartedCommand(builds, listener, event, stats);
}

return findMessage(startedCommand);
}

Expand Down
Loading

0 comments on commit 1edbeab

Please sign in to comment.