You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LiveMeasureComputerImpl.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.measure.live;
  21. import java.util.ArrayList;
  22. import java.util.Collection;
  23. import java.util.HashSet;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Objects;
  27. import java.util.Optional;
  28. import java.util.Set;
  29. import javax.annotation.CheckForNull;
  30. import org.sonar.api.config.Configuration;
  31. import org.sonar.api.measures.Metric;
  32. import org.sonar.api.utils.log.Loggers;
  33. import org.sonar.db.DbClient;
  34. import org.sonar.db.DbSession;
  35. import org.sonar.db.component.BranchDto;
  36. import org.sonar.db.component.BranchType;
  37. import org.sonar.db.component.ComponentDto;
  38. import org.sonar.db.component.SnapshotDto;
  39. import org.sonar.db.measure.LiveMeasureComparator;
  40. import org.sonar.db.measure.LiveMeasureDto;
  41. import org.sonar.db.metric.MetricDto;
  42. import org.sonar.db.project.ProjectDto;
  43. import org.sonar.server.es.ProjectIndexer;
  44. import org.sonar.server.es.ProjectIndexers;
  45. import org.sonar.server.measure.DebtRatingGrid;
  46. import org.sonar.server.measure.Rating;
  47. import org.sonar.server.qualitygate.EvaluatedQualityGate;
  48. import org.sonar.server.qualitygate.QualityGate;
  49. import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
  50. import org.sonar.server.setting.ProjectConfigurationLoader;
  51. import static com.google.common.base.Preconditions.checkState;
  52. import static java.util.Collections.emptyList;
  53. import static java.util.Collections.singleton;
  54. import static java.util.stream.Collectors.groupingBy;
  55. import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
  56. import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
  57. import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
  58. public class LiveMeasureComputerImpl implements LiveMeasureComputer {
  59. private final DbClient dbClient;
  60. private final IssueMetricFormulaFactory formulaFactory;
  61. private final LiveQualityGateComputer qGateComputer;
  62. private final ProjectConfigurationLoader projectConfigurationLoader;
  63. private final ProjectIndexers projectIndexer;
  64. public LiveMeasureComputerImpl(DbClient dbClient, IssueMetricFormulaFactory formulaFactory,
  65. LiveQualityGateComputer qGateComputer, ProjectConfigurationLoader projectConfigurationLoader, ProjectIndexers projectIndexer) {
  66. this.dbClient = dbClient;
  67. this.formulaFactory = formulaFactory;
  68. this.qGateComputer = qGateComputer;
  69. this.projectConfigurationLoader = projectConfigurationLoader;
  70. this.projectIndexer = projectIndexer;
  71. }
  72. @Override
  73. public List<QGChangeEvent> refresh(DbSession dbSession, Collection<ComponentDto> components) {
  74. if (components.isEmpty()) {
  75. return emptyList();
  76. }
  77. List<QGChangeEvent> result = new ArrayList<>();
  78. Map<String, List<ComponentDto>> componentsByProjectUuid = components.stream().collect(groupingBy(ComponentDto::projectUuid));
  79. for (List<ComponentDto> groupedComponents : componentsByProjectUuid.values()) {
  80. Optional<QGChangeEvent> qgChangeEvent = refreshComponentsOnSameProject(dbSession, groupedComponents);
  81. qgChangeEvent.ifPresent(result::add);
  82. }
  83. return result;
  84. }
  85. private Optional<QGChangeEvent> refreshComponentsOnSameProject(DbSession dbSession, List<ComponentDto> touchedComponents) {
  86. // load all the components to be refreshed, including their ancestors
  87. List<ComponentDto> components = loadTreeOfComponents(dbSession, touchedComponents);
  88. ComponentDto branchComponent = findBranchComponent(components);
  89. BranchDto branch = loadBranch(dbSession, branchComponent);
  90. ProjectDto project = loadProject(dbSession, branch.getProjectUuid());
  91. Optional<SnapshotDto> lastAnalysis = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, branchComponent.uuid());
  92. if (!lastAnalysis.isPresent()) {
  93. return Optional.empty();
  94. }
  95. QualityGate qualityGate = qGateComputer.loadQualityGate(dbSession, project, branch);
  96. Collection<String> metricKeys = getKeysOfAllInvolvedMetrics(qualityGate);
  97. List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, metricKeys);
  98. Map<String, MetricDto> metricsPerId = metrics.stream()
  99. .collect(uniqueIndex(MetricDto::getUuid));
  100. List<String> componentUuids = components.stream().map(ComponentDto::uuid).collect(toArrayList(components.size()));
  101. List<LiveMeasureDto> dbMeasures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricUuids(dbSession, componentUuids, metricsPerId.keySet());
  102. // previous status must be load now as MeasureMatrix mutate the LiveMeasureDto which are passed to it
  103. Metric.Level previousStatus = loadPreviousStatus(metrics, dbMeasures);
  104. Configuration config = projectConfigurationLoader.loadProjectConfiguration(dbSession, branchComponent);
  105. DebtRatingGrid debtRatingGrid = new DebtRatingGrid(config);
  106. MeasureMatrix matrix = new MeasureMatrix(components, metricsPerId.values(), dbMeasures);
  107. FormulaContextImpl context = new FormulaContextImpl(matrix, debtRatingGrid);
  108. long beginningOfLeak = getBeginningOfLeakPeriod(lastAnalysis, branch);
  109. components.forEach(c -> {
  110. IssueCounter issueCounter = new IssueCounter(dbClient.issueDao().selectIssueGroupsByBaseComponent(dbSession, c, beginningOfLeak));
  111. for (IssueMetricFormula formula : formulaFactory.getFormulas()) {
  112. // use formulas when the leak period is defined, it's a PR, or the formula is not about the leak period
  113. if (shouldUseLeakFormulas(lastAnalysis.get(), branch) || !formula.isOnLeak()) {
  114. context.change(c, formula);
  115. try {
  116. formula.compute(context, issueCounter);
  117. } catch (RuntimeException e) {
  118. throw new IllegalStateException("Fail to compute " + formula.getMetric().getKey() + " on " + context.getComponent().getDbKey(), e);
  119. }
  120. }
  121. }
  122. });
  123. EvaluatedQualityGate evaluatedQualityGate = qGateComputer.refreshGateStatus(branchComponent, qualityGate, matrix, config);
  124. // persist the measures that have been created or updated
  125. matrix.getChanged().sorted(LiveMeasureComparator.INSTANCE)
  126. .forEach(m -> dbClient.liveMeasureDao().insertOrUpdate(dbSession, m));
  127. projectIndexer.commitAndIndexComponents(dbSession, singleton(branchComponent), ProjectIndexer.Cause.MEASURE_CHANGE);
  128. return Optional.of(
  129. new QGChangeEvent(project, branch, lastAnalysis.get(), config, previousStatus, () -> Optional.of(evaluatedQualityGate)));
  130. }
  131. private static long getBeginningOfLeakPeriod(Optional<SnapshotDto> lastAnalysis, BranchDto branch) {
  132. if (isPR(branch)) {
  133. return 0L;
  134. } else {
  135. Optional<Long> beginningOfLeakPeriod = lastAnalysis.map(SnapshotDto::getPeriodDate);
  136. return beginningOfLeakPeriod.orElse(Long.MAX_VALUE);
  137. }
  138. }
  139. private static boolean isPR(BranchDto branch) {
  140. return branch.getBranchType() == BranchType.PULL_REQUEST;
  141. }
  142. private static boolean shouldUseLeakFormulas(SnapshotDto lastAnalysis, BranchDto branch) {
  143. return lastAnalysis.getPeriodDate() != null || isPR(branch);
  144. }
  145. @CheckForNull
  146. private static Metric.Level loadPreviousStatus(List<MetricDto> metrics, List<LiveMeasureDto> dbMeasures) {
  147. MetricDto alertStatusMetric = metrics.stream()
  148. .filter(m -> ALERT_STATUS_KEY.equals(m.getKey()))
  149. .findAny()
  150. .orElseThrow(() -> new IllegalStateException(String.format("Metric with key %s is not registered", ALERT_STATUS_KEY)));
  151. return dbMeasures.stream()
  152. .filter(m -> m.getMetricUuid().equals(alertStatusMetric.getUuid()))
  153. .map(LiveMeasureDto::getTextValue)
  154. .filter(Objects::nonNull)
  155. .map(m -> {
  156. try {
  157. return Metric.Level.valueOf(m);
  158. } catch (IllegalArgumentException e) {
  159. Loggers.get(LiveMeasureComputerImpl.class)
  160. .trace("Failed to parse value of metric '{}'", m, e);
  161. return null;
  162. }
  163. })
  164. .filter(Objects::nonNull)
  165. .findAny()
  166. .orElse(null);
  167. }
  168. private List<ComponentDto> loadTreeOfComponents(DbSession dbSession, List<ComponentDto> touchedComponents) {
  169. Set<String> componentUuids = new HashSet<>();
  170. for (ComponentDto component : touchedComponents) {
  171. componentUuids.add(component.uuid());
  172. // ancestors, excluding self
  173. componentUuids.addAll(component.getUuidPathAsList());
  174. }
  175. // Contrary to the formulas in Compute Engine,
  176. // measures do not aggregate values of descendant components.
  177. // As a consequence nodes do not need to be sorted. Formulas can be applied
  178. // on components in any order.
  179. return dbClient.componentDao().selectByUuids(dbSession, componentUuids);
  180. }
  181. private Set<String> getKeysOfAllInvolvedMetrics(QualityGate gate) {
  182. Set<String> metricKeys = new HashSet<>();
  183. for (Metric metric : formulaFactory.getFormulaMetrics()) {
  184. metricKeys.add(metric.getKey());
  185. }
  186. metricKeys.addAll(qGateComputer.getMetricsRelatedTo(gate));
  187. return metricKeys;
  188. }
  189. private static ComponentDto findBranchComponent(Collection<ComponentDto> components) {
  190. return components.stream().filter(ComponentDto::isRootProject).findFirst()
  191. .orElseThrow(() -> new IllegalStateException("No project found in " + components));
  192. }
  193. private BranchDto loadBranch(DbSession dbSession, ComponentDto branchComponent) {
  194. return dbClient.branchDao().selectByUuid(dbSession, branchComponent.uuid())
  195. .orElseThrow(() -> new IllegalStateException("Branch not found: " + branchComponent.uuid()));
  196. }
  197. private ProjectDto loadProject(DbSession dbSession, String uuid) {
  198. return dbClient.projectDao().selectByUuid(dbSession, uuid)
  199. .orElseThrow(() -> new IllegalStateException("Project not found: " + uuid));
  200. }
  201. private static class FormulaContextImpl implements IssueMetricFormula.Context {
  202. private final MeasureMatrix matrix;
  203. private final DebtRatingGrid debtRatingGrid;
  204. private ComponentDto currentComponent;
  205. private IssueMetricFormula currentFormula;
  206. private FormulaContextImpl(MeasureMatrix matrix, DebtRatingGrid debtRatingGrid) {
  207. this.matrix = matrix;
  208. this.debtRatingGrid = debtRatingGrid;
  209. }
  210. private void change(ComponentDto component, IssueMetricFormula formula) {
  211. this.currentComponent = component;
  212. this.currentFormula = formula;
  213. }
  214. @Override
  215. public ComponentDto getComponent() {
  216. return currentComponent;
  217. }
  218. @Override
  219. public DebtRatingGrid getDebtRatingGrid() {
  220. return debtRatingGrid;
  221. }
  222. @Override
  223. public Optional<Double> getValue(Metric metric) {
  224. Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
  225. return measure.map(LiveMeasureDto::getValue);
  226. }
  227. @Override
  228. public Optional<Double> getLeakValue(Metric metric) {
  229. Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
  230. return measure.map(LiveMeasureDto::getVariation);
  231. }
  232. @Override
  233. public void setValue(double value) {
  234. String metricKey = currentFormula.getMetric().getKey();
  235. checkState(!currentFormula.isOnLeak(), "Formula of metric %s accepts only leak values", metricKey);
  236. matrix.setValue(currentComponent, metricKey, value);
  237. }
  238. @Override
  239. public void setLeakValue(double value) {
  240. String metricKey = currentFormula.getMetric().getKey();
  241. checkState(currentFormula.isOnLeak(), "Formula of metric %s does not accept leak values", metricKey);
  242. matrix.setLeakValue(currentComponent, metricKey, value);
  243. }
  244. @Override
  245. public void setValue(Rating value) {
  246. String metricKey = currentFormula.getMetric().getKey();
  247. checkState(!currentFormula.isOnLeak(), "Formula of metric %s accepts only leak values", metricKey);
  248. matrix.setValue(currentComponent, metricKey, value);
  249. }
  250. @Override
  251. public void setLeakValue(Rating value) {
  252. String metricKey = currentFormula.getMetric().getKey();
  253. checkState(currentFormula.isOnLeak(), "Formula of metric %s does not accept leak values", metricKey);
  254. matrix.setLeakValue(currentComponent, metricKey, value);
  255. }
  256. }
  257. }