All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.plugins.buildstability.BuildStabilitySensor Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
/*
 * Sonar Build Stability Plugin
 * Copyright (C) 2010 SonarSource
 * [email protected]
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package org.sonar.plugins.buildstability;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.config.Settings;
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 org.sonar.plugins.buildstability.ci.MavenCiConfiguration;
import org.sonar.plugins.buildstability.ci.api.Build;

import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

/**
 * @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";

  private final Settings settings;
  private final MavenCiConfiguration mavenCiConfiguration;

  public BuildStabilitySensor(Settings settings, MavenCiConfiguration mavenCiConfiguration) {
    this.settings = settings;
    this.mavenCiConfiguration = mavenCiConfiguration;
  }

  /**
   * In case we are not in a Maven build this constructor will be called
   */
  public BuildStabilitySensor(Settings settings) {
    this(settings, null /* Not in a Maven build */);
  }

  public boolean shouldExecuteOnProject(Project project) {
    return project.isRoot() &&
      StringUtils.isNotEmpty(getCiUrl(project));
  }

  protected String getCiUrl(Project project) {
    String url = settings.getString(CI_URL_PROPERTY);
    if (StringUtils.isNotEmpty(url)) {
      return url;
    }
    if (mavenCiConfiguration != null
      && StringUtils.isNotEmpty(mavenCiConfiguration.getSystem())
      && StringUtils.isNotEmpty(mavenCiConfiguration.getUrl())) {
      return mavenCiConfiguration.getSystem() + ":" + mavenCiConfiguration.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 = settings.getString(USERNAME_PROPERTY);
    String password = settings.getString(PASSWORD_PROPERTY);
    boolean useJSecurityCheck = settings.getBoolean(USE_JSECURITYCHECK_PROPERTY);
    List 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 = settings.getInt(DAYS_PROPERTY);
      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 builds, SensorContext context) {
    Logger logger = LoggerFactory.getLogger(getClass());

    Collections.sort(builds, new Comparator() {
      public int compare(Build o1, Build o2) {
        return o1.getNumber() - o2.getNumber();
      }
    });

    PropertiesBuilder durationsBuilder = new PropertiesBuilder(BuildStabilityMetrics.DURATIONS);
    PropertiesBuilder resultsBuilder = new PropertiesBuilder(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;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy