/*
* Copyright (C) 2010 Evgeny Mandrikov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sonar.plugins.buildstability;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.model.CiManagement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.PropertiesBuilder;
import org.sonar.api.resources.Project;
import org.sonar.plugins.buildstability.ci.CiConnector;
import org.sonar.plugins.buildstability.ci.CiFactory;
import java.util.*;
/**
* @author Evgeny Mandrikov
*/
public class BuildStabilitySensor implements Sensor {
public static final String DAYS_PROPERTY = "sonar.build-stability.days";
public static final int DAYS_DEFAULT_VALUE = 30;
public static final String USERNAME_PROPERTY = "sonar.build-stability.username.secured";
public static final String PASSWORD_PROPERTY = "sonar.build-stability.password.secured";
public static final String USE_JSECURITYCHECK_PROPERTY = "sonar.build-stability.use_jsecuritycheck";
public static final boolean USE_JSECURITYCHECK_DEFAULT_VALUE = false;
public static final String CI_URL_PROPERTY = "sonar.build-stability.url";
public boolean shouldExecuteOnProject(Project project) {
return project.isRoot() &&
StringUtils.isNotEmpty(getCiUrl(project));
}
protected String getCiUrl(Project project) {
String url = project.getConfiguration().getString(CI_URL_PROPERTY);
if (StringUtils.isNotEmpty(url)) {
return url;
}
CiManagement ci = project.getPom().getCiManagement();
if (ci != null && StringUtils.isNotEmpty(ci.getSystem()) && StringUtils.isNotEmpty(ci.getUrl())) {
return ci.getSystem() + ":" + ci.getUrl();
}
return null;
}
public void analyse(Project project, SensorContext context) {
Logger logger = LoggerFactory.getLogger(getClass());
String ciUrl = getCiUrl(project);
logger.info("CI URL: {}", ciUrl);
String username = project.getConfiguration().getString(USERNAME_PROPERTY);
String password = project.getConfiguration().getString(PASSWORD_PROPERTY);
boolean useJSecurityCheck = project.getConfiguration().getBoolean(USE_JSECURITYCHECK_PROPERTY, USE_JSECURITYCHECK_DEFAULT_VALUE);
List<Build> builds;
try {
CiConnector connector = CiFactory.create(ciUrl, username, password, useJSecurityCheck);
if (connector == null) {
logger.warn("Unknown CiManagement system or incorrect URL: {}", ciUrl);
return;
}
int daysToRetrieve = project.getConfiguration().getInt(DAYS_PROPERTY, DAYS_DEFAULT_VALUE);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -daysToRetrieve);
Date date = calendar.getTime();
builds = connector.getBuildsSince(date);
logger.info("Retrieved {} builds since {}", builds.size(), date);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return;
}
analyseBuilds(builds, context);
}
protected void analyseBuilds(List<Build> builds, SensorContext context) {
Logger logger = LoggerFactory.getLogger(getClass());
Collections.sort(builds, new Comparator<Build>() {
public int compare(Build o1, Build o2) {
return o1.getNumber() - o2.getNumber();
}
});
PropertiesBuilder<Integer, Double> durationsBuilder = new PropertiesBuilder<Integer, Double>(BuildStabilityMetrics.DURATIONS);
PropertiesBuilder<Integer, String> resultsBuilder = new PropertiesBuilder<Integer, String>(BuildStabilityMetrics.RESULTS);
double successful = 0;
double failed = 0;
double duration = 0;
double shortest = Double.POSITIVE_INFINITY;
double longest = Double.NEGATIVE_INFINITY;
double totalTimeToFix = 0;
double totalBuildsToFix = 0;
double longestTimeToFix = Double.NEGATIVE_INFINITY;
int fixes = 0;
Build firstFailed = null;
for (Build build : builds) {
logger.debug(build.toString());
int buildNumber = build.getNumber();
double buildDuration = build.getDuration();
resultsBuilder.add(buildNumber, build.isSuccessful() ? "g" : "r");
durationsBuilder.add(buildNumber, buildDuration / 1000);
if (build.isSuccessful()) {
successful++;
duration += buildDuration;
shortest = Math.min(shortest, buildDuration);
longest = Math.max(longest, buildDuration);
if (firstFailed != null) {
// Build fixed
long buildsToFix = build.getNumber() - firstFailed.getNumber();
totalBuildsToFix += buildsToFix;
double timeToFix = build.getTimestamp() - firstFailed.getTimestamp();
totalTimeToFix += timeToFix;
longestTimeToFix = Math.max(longestTimeToFix, timeToFix);
fixes++;
firstFailed = null;
}
} else {
failed++;
if (firstFailed == null) {
// Build failed
firstFailed = build;
}
}
}
double count = successful + failed;
context.saveMeasure(new Measure(BuildStabilityMetrics.BUILDS, count));
context.saveMeasure(new Measure(BuildStabilityMetrics.FAILED, failed));
context.saveMeasure(new Measure(BuildStabilityMetrics.SUCCESS_RATE, divide(successful, count) * 100));
context.saveMeasure(new Measure(BuildStabilityMetrics.AVG_DURATION, divide(duration, successful)));
context.saveMeasure(new Measure(BuildStabilityMetrics.LONGEST_DURATION, normalize(longest)));
context.saveMeasure(new Measure(BuildStabilityMetrics.SHORTEST_DURATION, normalize(shortest)));
context.saveMeasure(new Measure(BuildStabilityMetrics.AVG_TIME_TO_FIX, divide(totalTimeToFix, fixes)));
context.saveMeasure(new Measure(BuildStabilityMetrics.LONGEST_TIME_TO_FIX, normalize(longestTimeToFix)));
context.saveMeasure(new Measure(BuildStabilityMetrics.AVG_BUILDS_TO_FIX, divide(totalBuildsToFix, fixes)));
if (!builds.isEmpty()) {
context.saveMeasure(durationsBuilder.build());
context.saveMeasure(resultsBuilder.build());
}
}
private double normalize(double value) {
return Double.isInfinite(value) ? 0 : value;
}
private double divide(double v1, double v2) {
return v2 == 0 ? 0 : v1 / v2;
}
}