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.

IssueCounter.java 12KB

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