org.sonar.plugins.buildstability.BuildStabilitySensor.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.plugins.buildstability.BuildStabilitySensor.java

Source

/*
 * 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;
    }
}