選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

IssueCounter.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 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.ce.task.projectanalysis.issue;
  21. import com.google.common.collect.EnumMultiset;
  22. import com.google.common.collect.HashMultiset;
  23. import com.google.common.collect.ImmutableMap;
  24. import com.google.common.collect.Multiset;
  25. import java.util.HashMap;
  26. import java.util.Map;
  27. import javax.annotation.Nullable;
  28. import org.sonar.api.rules.RuleType;
  29. import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
  30. import org.sonar.ce.task.projectanalysis.component.Component;
  31. import org.sonar.ce.task.projectanalysis.measure.Measure;
  32. import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
  33. import org.sonar.ce.task.projectanalysis.metric.Metric;
  34. import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
  35. import org.sonar.ce.task.projectanalysis.period.Period;
  36. import org.sonar.ce.task.projectanalysis.period.PeriodHolder;
  37. import org.sonar.core.issue.DefaultIssue;
  38. import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
  39. import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
  40. import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
  41. import static org.sonar.api.issue.Issue.STATUS_OPEN;
  42. import static org.sonar.api.issue.Issue.STATUS_REOPENED;
  43. import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY;
  44. import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
  45. import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY;
  46. import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY;
  47. import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY;
  48. import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY;
  49. import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY;
  50. import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY;
  51. import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY;
  52. import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY;
  53. import static org.sonar.api.measures.CoreMetrics.NEW_BUGS_KEY;
  54. import static org.sonar.api.measures.CoreMetrics.NEW_CODE_SMELLS_KEY;
  55. import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY;
  56. import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY;
  57. import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY;
  58. import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY;
  59. import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY;
  60. import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY;
  61. import static org.sonar.api.measures.CoreMetrics.NEW_VULNERABILITIES_KEY;
  62. import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY;
  63. import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES_KEY;
  64. import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
  65. import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY;
  66. import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
  67. import static org.sonar.api.measures.CoreMetrics.WONT_FIX_ISSUES_KEY;
  68. import static org.sonar.api.rule.Severity.BLOCKER;
  69. import static org.sonar.api.rule.Severity.CRITICAL;
  70. import static org.sonar.api.rule.Severity.INFO;
  71. import static org.sonar.api.rule.Severity.MAJOR;
  72. import static org.sonar.api.rule.Severity.MINOR;
  73. import static org.sonar.api.rules.RuleType.BUG;
  74. import static org.sonar.api.rules.RuleType.CODE_SMELL;
  75. import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
  76. import static org.sonar.api.rules.RuleType.VULNERABILITY;
  77. import static org.sonar.api.utils.DateUtils.truncateToSeconds;
  78. /**
  79. * For each component, computes the measures related to number of issues:
  80. * <ul>
  81. * <li>issues per status (open, reopen, confirmed)</li>
  82. * <li>issues per resolution (unresolved, false-positives, won't fix)</li>
  83. * <li>issues per severity (from info to blocker)</li>
  84. * <li>issues per type (code smell, bug, vulnerability, security hotspots)</li>
  85. * </ul>
  86. * For each value, the variation on configured periods is also computed.
  87. */
  88. public class IssueCounter extends IssueVisitor {
  89. private static final Map<String, String> SEVERITY_TO_METRIC_KEY = ImmutableMap.of(
  90. BLOCKER, BLOCKER_VIOLATIONS_KEY,
  91. CRITICAL, CRITICAL_VIOLATIONS_KEY,
  92. MAJOR, MAJOR_VIOLATIONS_KEY,
  93. MINOR, MINOR_VIOLATIONS_KEY,
  94. INFO, INFO_VIOLATIONS_KEY);
  95. private static final Map<String, String> SEVERITY_TO_NEW_METRIC_KEY = ImmutableMap.of(
  96. BLOCKER, NEW_BLOCKER_VIOLATIONS_KEY,
  97. CRITICAL, NEW_CRITICAL_VIOLATIONS_KEY,
  98. MAJOR, NEW_MAJOR_VIOLATIONS_KEY,
  99. MINOR, NEW_MINOR_VIOLATIONS_KEY,
  100. INFO, NEW_INFO_VIOLATIONS_KEY);
  101. private static final Map<RuleType, String> TYPE_TO_METRIC_KEY = ImmutableMap.<RuleType, String>builder()
  102. .put(CODE_SMELL, CODE_SMELLS_KEY)
  103. .put(BUG, BUGS_KEY)
  104. .put(VULNERABILITY, VULNERABILITIES_KEY)
  105. .put(SECURITY_HOTSPOT, SECURITY_HOTSPOTS_KEY)
  106. .build();
  107. private static final Map<RuleType, String> TYPE_TO_NEW_METRIC_KEY = ImmutableMap.<RuleType, String>builder()
  108. .put(CODE_SMELL, NEW_CODE_SMELLS_KEY)
  109. .put(BUG, NEW_BUGS_KEY)
  110. .put(VULNERABILITY, NEW_VULNERABILITIES_KEY)
  111. .put(SECURITY_HOTSPOT, NEW_SECURITY_HOTSPOTS_KEY)
  112. .build();
  113. private final PeriodHolder periodHolder;
  114. private final AnalysisMetadataHolder analysisMetadataHolder;
  115. private final MetricRepository metricRepository;
  116. private final MeasureRepository measureRepository;
  117. private final Map<String, Counters> countersByComponentUuid = new HashMap<>();
  118. private Counters currentCounters;
  119. public IssueCounter(PeriodHolder periodHolder, AnalysisMetadataHolder analysisMetadataHolder,
  120. MetricRepository metricRepository, MeasureRepository measureRepository) {
  121. this.periodHolder = periodHolder;
  122. this.analysisMetadataHolder = analysisMetadataHolder;
  123. this.metricRepository = metricRepository;
  124. this.measureRepository = measureRepository;
  125. }
  126. @Override
  127. public void beforeComponent(Component component) {
  128. currentCounters = new Counters();
  129. countersByComponentUuid.put(component.getUuid(), currentCounters);
  130. // aggregate children counters
  131. for (Component child : component.getChildren()) {
  132. // no need to keep the children in memory. They can be garbage-collected.
  133. Counters childCounters = countersByComponentUuid.remove(child.getUuid());
  134. currentCounters.add(childCounters);
  135. }
  136. }
  137. @Override
  138. public void onIssue(Component component, DefaultIssue issue) {
  139. currentCounters.add(issue);
  140. if (analysisMetadataHolder.isPullRequest()) {
  141. currentCounters.addOnPeriod(issue);
  142. } else if (periodHolder.hasPeriod()) {
  143. Period period = periodHolder.getPeriod();
  144. if (issue.creationDate().getTime() > truncateToSeconds(period.getSnapshotDate())) {
  145. currentCounters.addOnPeriod(issue);
  146. }
  147. }
  148. }
  149. @Override
  150. public void afterComponent(Component component) {
  151. addMeasuresBySeverity(component);
  152. addMeasuresByStatus(component);
  153. addMeasuresByType(component);
  154. addNewMeasures(component);
  155. currentCounters = null;
  156. }
  157. private void addMeasuresBySeverity(Component component) {
  158. for (Map.Entry<String, String> entry : SEVERITY_TO_METRIC_KEY.entrySet()) {
  159. String severity = entry.getKey();
  160. String metricKey = entry.getValue();
  161. addMeasure(component, metricKey, currentCounters.counter().severityBag.count(severity));
  162. }
  163. }
  164. private void addMeasuresByStatus(Component component) {
  165. addMeasure(component, VIOLATIONS_KEY, currentCounters.counter().unresolved);
  166. addMeasure(component, OPEN_ISSUES_KEY, currentCounters.counter().open);
  167. addMeasure(component, REOPENED_ISSUES_KEY, currentCounters.counter().reopened);
  168. addMeasure(component, CONFIRMED_ISSUES_KEY, currentCounters.counter().confirmed);
  169. addMeasure(component, FALSE_POSITIVE_ISSUES_KEY, currentCounters.counter().falsePositives);
  170. addMeasure(component, WONT_FIX_ISSUES_KEY, currentCounters.counter().wontFix);
  171. }
  172. private void addMeasuresByType(Component component) {
  173. for (Map.Entry<RuleType, String> entry : TYPE_TO_METRIC_KEY.entrySet()) {
  174. addMeasure(component, entry.getValue(), currentCounters.counter().typeBag.count(entry.getKey()));
  175. }
  176. }
  177. private void addMeasure(Component component, String metricKey, int value) {
  178. Metric metric = metricRepository.getByKey(metricKey);
  179. measureRepository.add(component, metric, Measure.newMeasureBuilder().create(value));
  180. }
  181. private void addNewMeasures(Component component) {
  182. if (!periodHolder.hasPeriod() && !analysisMetadataHolder.isPullRequest()) {
  183. return;
  184. }
  185. double unresolvedVariations = currentCounters.counterForPeriod().unresolved;
  186. measureRepository.add(component, metricRepository.getByKey(NEW_VIOLATIONS_KEY), Measure.newMeasureBuilder()
  187. .setVariation(unresolvedVariations)
  188. .createNoValue());
  189. for (Map.Entry<String, String> entry : SEVERITY_TO_NEW_METRIC_KEY.entrySet()) {
  190. String severity = entry.getKey();
  191. String metricKey = entry.getValue();
  192. Multiset<String> bag = currentCounters.counterForPeriod().severityBag;
  193. Metric metric = metricRepository.getByKey(metricKey);
  194. measureRepository.add(component, metric, Measure.newMeasureBuilder()
  195. .setVariation(bag.count(severity))
  196. .createNoValue());
  197. }
  198. // waiting for Java 8 lambda in order to factor this loop with the previous one
  199. // (see call currentCounters.counterForPeriod(period.getIndex()).xxx with xxx as severityBag or typeBag)
  200. for (Map.Entry<RuleType, String> entry : TYPE_TO_NEW_METRIC_KEY.entrySet()) {
  201. RuleType type = entry.getKey();
  202. String metricKey = entry.getValue();
  203. Multiset<RuleType> bag = currentCounters.counterForPeriod().typeBag;
  204. Metric metric = metricRepository.getByKey(metricKey);
  205. measureRepository.add(component, metric, Measure.newMeasureBuilder()
  206. .setVariation(bag.count(type))
  207. .createNoValue());
  208. }
  209. }
  210. /**
  211. * Count issues by status, resolutions, rules and severities
  212. */
  213. private static class Counter {
  214. private int unresolved = 0;
  215. private int open = 0;
  216. private int reopened = 0;
  217. private int confirmed = 0;
  218. private int falsePositives = 0;
  219. private int wontFix = 0;
  220. private final Multiset<String> severityBag = HashMultiset.create();
  221. private final EnumMultiset<RuleType> typeBag = EnumMultiset.create(RuleType.class);
  222. void add(Counter counter) {
  223. unresolved += counter.unresolved;
  224. open += counter.open;
  225. reopened += counter.reopened;
  226. confirmed += counter.confirmed;
  227. falsePositives += counter.falsePositives;
  228. wontFix += counter.wontFix;
  229. severityBag.addAll(counter.severityBag);
  230. typeBag.addAll(counter.typeBag);
  231. }
  232. void add(DefaultIssue issue) {
  233. if (issue.type() == SECURITY_HOTSPOT) {
  234. if (issue.resolution() == null) {
  235. typeBag.add(SECURITY_HOTSPOT);
  236. }
  237. return;
  238. }
  239. if (issue.resolution() == null) {
  240. unresolved++;
  241. typeBag.add(issue.type());
  242. severityBag.add(issue.severity());
  243. } else if (RESOLUTION_FALSE_POSITIVE.equals(issue.resolution())) {
  244. falsePositives++;
  245. } else if (RESOLUTION_WONT_FIX.equals(issue.resolution())) {
  246. wontFix++;
  247. }
  248. switch (issue.status()) {
  249. case STATUS_OPEN:
  250. open++;
  251. break;
  252. case STATUS_REOPENED:
  253. reopened++;
  254. break;
  255. case STATUS_CONFIRMED:
  256. confirmed++;
  257. break;
  258. default:
  259. // Other statuses are ignored
  260. }
  261. }
  262. }
  263. /**
  264. * List of {@link Counter} for regular value and period.
  265. */
  266. private static class Counters {
  267. private final Counter counter = new Counter();
  268. private final Counter counterForPeriod = new Counter();
  269. void add(@Nullable Counters other) {
  270. if (other != null) {
  271. counter.add(other.counter);
  272. counterForPeriod.add(other.counterForPeriod);
  273. }
  274. }
  275. void addOnPeriod(DefaultIssue issue) {
  276. counterForPeriod.add(issue);
  277. }
  278. void add(DefaultIssue issue) {
  279. counter.add(issue);
  280. }
  281. Counter counter() {
  282. return counter;
  283. }
  284. Counter counterForPeriod() {
  285. return counterForPeriod;
  286. }
  287. }
  288. }