This is an automated email from the ASF dual-hosted git repository. dpavlov pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/ignite-teamcity-bot.git
commit 9e3cb0bb05d7205dad68a12f9edb0df78cca3749 Author: Nikolai Kulagin <[email protected]> AuthorDate: Tue Sep 11 14:17:32 2018 +0300 Refactor save of value in cache and fix NPE - Fixes #3. Signed-off-by: Dmitriy Pavlov <[email protected]> --- .../main/java/org/apache/ignite/ci/ITeamcity.java | 36 ++- .../apache/ignite/ci/IgnitePersistentTeamcity.java | 75 ++++-- .../org/apache/ignite/ci/IgniteTeamcityHelper.java | 61 ++++- .../org/apache/ignite/ci/analysis/Expirable.java | 14 +- .../apache/ignite/ci/analysis/MultBuildRunCtx.java | 6 +- .../ci/issue/{IssueRef.java => ProblemRef.java} | 8 +- .../apache/ignite/ci/tcmodel/hist/BuildRef.java | 2 + .../org/apache/ignite/ci/tcmodel/result/Build.java | 16 +- .../ci/tcmodel/result/ProblemOccurrencesRef.java | 4 + .../result/RelatedIssuesRef.java} | 14 +- .../ci/tcmodel/result/TestOccurrencesRef.java | 2 + .../IssueRef.java} | 23 +- .../IssueUsage.java} | 28 +- .../IssuesUsagesList.java} | 30 ++- .../tcmodel/result/problems/ProblemOccurrence.java | 30 ++- .../result/problems/ProblemOccurrences.java | 3 +- .../web/model/current/BuildStatisticsSummary.java | 286 +++++++++++++++++++++ .../ci/web/model/current/SuiteCurrentStatus.java | 8 +- .../ignite/ci/web/model/current/TestFailure.java | 8 +- .../ci/web/rest/build/GetBuildTestFailures.java | 79 +++++- ignite-tc-helper-web/src/main/webapp/index.html | 7 +- .../src/main/webapp/statistics.html | 203 +++++++++++++++ 22 files changed, 836 insertions(+), 107 deletions(-) diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java index 9782cbf..d9d8d1d 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java @@ -33,6 +33,7 @@ import org.apache.ignite.ci.tcmodel.changes.ChangesList; import org.apache.ignite.ci.tcmodel.conf.BuildType; import org.apache.ignite.ci.tcmodel.hist.BuildRef; import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.tcmodel.result.issues.IssuesUsagesList; import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrence; import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrences; import org.apache.ignite.ci.tcmodel.result.stat.Statistics; @@ -52,6 +53,7 @@ import static org.apache.ignite.ci.db.DbMigrations.TESTS_COUNT_7700; public interface ITeamcity extends AutoCloseable { String DEFAULT = "<default>"; + long DEFAULT_BUILDS_COUNT = 1000; CompletableFuture<List<BuildType>> getProjectSuites(String projectId); @@ -62,7 +64,17 @@ public interface ITeamcity extends AutoCloseable { * @param branch * @return list of builds in historical order, recent builds coming last */ - List<BuildRef> getFinishedBuilds(String projectId, String branch); + default List<BuildRef> getFinishedBuilds(String projectId, String branch) { + return getFinishedBuilds(projectId, branch, null); + }; + + /** + * @param projectId suite ID (string without spaces) + * @param branch + * @param cnt builds count + * @return list of builds in historical order, recent builds coming last + */ + List<BuildRef> getFinishedBuilds(String projectId, String branch, Long cnt); /** * Includes snapshot dependencies failed builds into list @@ -71,7 +83,19 @@ public interface ITeamcity extends AutoCloseable { * @param branch branch in TC identification * @return list of builds in historical order, recent builds coming last */ - List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch); + default List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch){ + return getFinishedBuilds(projectId, branch, null); + }; + + /** + * Includes snapshot dependencies failed builds into list + * + * @param projectId suite ID (string without spaces) + * @param branch branch in TC identification + * @param cnt builds count + * @return list of builds in historical order, recent builds coming last + */ + List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch, Long cnt); /** */ CompletableFuture<List<BuildRef>> getRunningBuilds(@Nullable String branch); @@ -80,7 +104,11 @@ public interface ITeamcity extends AutoCloseable { CompletableFuture<List<BuildRef>> getQueuedBuilds(@Nullable String branch); default int[] getBuildNumbersFromHistory(String projectId, String branchNameForHist) { - return getFinishedBuilds(projectId, branchNameForHist).stream().mapToInt(BuildRef::getId).toArray(); + return getBuildNumbersFromHistory(projectId, branchNameForHist, null); + } + + default int[] getBuildNumbersFromHistory(String projectId, String branchNameForHist, Long cnt) { + return getFinishedBuilds(projectId, branchNameForHist, cnt).stream().mapToInt(BuildRef::getId).toArray(); } Build getBuild(String href); @@ -114,6 +142,8 @@ public interface ITeamcity extends AutoCloseable { ChangesList getChangesList(String href); + IssuesUsagesList getIssuesUsagesList(String href); + /** * Runs deep collection of all related statistics for particular build * diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java index 99f61bb..e89e2f2 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java @@ -36,6 +36,7 @@ import java.util.concurrent.locks.Lock; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.annotation.Nullable; @@ -57,6 +58,7 @@ import org.apache.ignite.ci.tcmodel.changes.ChangesList; import org.apache.ignite.ci.tcmodel.conf.BuildType; import org.apache.ignite.ci.tcmodel.hist.BuildRef; import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.tcmodel.result.issues.IssuesUsagesList; import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrences; import org.apache.ignite.ci.tcmodel.result.stat.Statistics; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrence; @@ -84,6 +86,7 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea public static final String LOG_CHECK_RESULT = "logCheckResult"; public static final String CHANGE_INFO_FULL = "changeInfoFull"; public static final String CHANGES_LIST = "changesList"; + public static final String ISSUES_USAGES_LIST = "issuesUsagesList"; public static final String TEST_FULL = "testFull"; public static final String BUILD_PROBLEMS = "buildProblems"; public static final String BUILD_STATISTICS = "buildStatistics"; @@ -197,7 +200,7 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea } /** - * @return Build history: {@link BuildRef} lists cache, 32 parts, transaactional + * @return Build history: {@link BuildRef} lists cache, 32 parts, transactional */ public IgniteCache<SuiteInBranch, Expirable<List<BuildRef>>> buildHistIncFailedCache() { return getOrCreateCacheV2Tx(ignCacheNme(BUILD_HIST_FINISHED_OR_FAILED)); @@ -240,14 +243,15 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea return loaded; } - private <K, V> V timedLoadIfAbsentOrMerge(IgniteCache<K, Expirable<V>> cache, int seconds, K key, + private <K, V extends List> V timedLoadIfAbsentOrMerge(IgniteCache<K, Expirable<V>> cache, int seconds, Long cnt, K key, BiFunction<K, V, V> loadWithMerge) { @Nullable final Expirable<V> persistedBuilds = cache.get(key); int fields = ObjectInterner.internFields(persistedBuilds); if (persistedBuilds != null) { - if (persistedBuilds.isAgeLessThanSecs(seconds)) + if (persistedBuilds.isAgeLessThanSecs(seconds) && + (cnt == null || persistedBuilds.hasCounterGreaterThan(cnt))) return persistedBuilds.getData(); } @@ -258,7 +262,7 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea try { apply = loadWithMerge.apply(key, persistedBuilds != null ? persistedBuilds.getData() : null); - final Expirable<V> newVal = new Expirable<>(System.currentTimeMillis(), apply); + final Expirable<V> newVal = new Expirable<>(System.currentTimeMillis(), apply.size(), apply); cache.put(key, newVal); } @@ -270,14 +274,14 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea } /** {@inheritDoc} */ - @Override public List<BuildRef> getFinishedBuilds(String projectId, String branch) { + @Override public List<BuildRef> getFinishedBuilds(String projectId, String branch, Long cnt) { final SuiteInBranch suiteInBranch = new SuiteInBranch(projectId, branch); - return timedLoadIfAbsentOrMerge(buildHistCache(), 60, suiteInBranch, + List<BuildRef> buildRefs = timedLoadIfAbsentOrMerge(buildHistCache(), 60, cnt, suiteInBranch, (key, persistedValue) -> { List<BuildRef> builds; try { - builds = teamcity.getFinishedBuilds(projectId, branch); + builds = teamcity.getFinishedBuilds(projectId, branch, cnt); } catch (Exception e) { if (Throwables.getRootCause(e) instanceof FileNotFoundException) { @@ -290,6 +294,13 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea return mergeByIdToHistoricalOrder(persistedValue, builds); }); + + if (cnt == null) + return buildRefs; + + return buildRefs.stream() + .skip(cnt < buildRefs.size() ? buildRefs.size() - cnt : 0) + .collect(Collectors.toList()); } @NotNull @@ -307,12 +318,12 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea //loads build history with following parameter: defaultFilter:false,state:finished /** {@inheritDoc} */ - @Override public List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch) { + @Override public List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch, Long cnt) { final SuiteInBranch suiteInBranch = new SuiteInBranch(projectId, branch); - return timedLoadIfAbsentOrMerge(buildHistIncFailedCache(), 60, suiteInBranch, + return timedLoadIfAbsentOrMerge(buildHistIncFailedCache(), 60, cnt, suiteInBranch, (key, persistedValue) -> { - List<BuildRef> failed = teamcity.getFinishedBuildsIncludeSnDepFailed(projectId, branch); + List<BuildRef> failed = teamcity.getFinishedBuildsIncludeSnDepFailed(projectId, branch, cnt); return mergeByIdToHistoricalOrder(persistedValue, failed); }); @@ -335,8 +346,8 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea public static int getTriggerRelCacheValidSecs(int defaultSecs) { long msSinceTrigger = System.currentTimeMillis() - lastTriggerMs; - long secondsSinceTigger = TimeUnit.MILLISECONDS.toSeconds(msSinceTrigger); - return Math.min((int)secondsSinceTigger, defaultSecs); + long secondsSinceTrigger = TimeUnit.MILLISECONDS.toSeconds(msSinceTrigger); + return Math.min((int)secondsSinceTrigger, defaultSecs); } /** {@inheritDoc} */ @@ -446,18 +457,22 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea /** {@inheritDoc}*/ @Override public ProblemOccurrences getProblems(Build build) { - String href = build.problemOccurrences.href; + if (build.problemOccurrences != null) { + String href = build.problemOccurrences.href; - return loadIfAbsent( - buildProblemsCache(), - href, - k -> { - ProblemOccurrences problems = teamcity.getProblems(build); + return loadIfAbsent( + buildProblemsCache(), + href, + k -> { + ProblemOccurrences problems = teamcity.getProblems(build); - registerCriticalBuildProblemInStat(build, problems); + registerCriticalBuildProblemInStat(build, problems); - return problems; - }); + return problems; + }); + } + else + return new ProblemOccurrences(); } private void registerCriticalBuildProblemInStat(Build build, ProblemOccurrences problems) { @@ -591,6 +606,24 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea }); } + @Override public IssuesUsagesList getIssuesUsagesList(String href) { + IssuesUsagesList issuesUsages = loadIfAbsentV2(ISSUES_USAGES_LIST, href, href1 -> { + try { + return teamcity.getIssuesUsagesList(href1); + } + catch (Exception e) { + if (Throwables.getRootCause(e) instanceof FileNotFoundException) { + System.err.println("Issues Usage List not found for href : " + href); + + return new IssuesUsagesList(); + } + else + throw e; + } + }); + return issuesUsages; + } + /** {@inheritDoc} */ @Override public List<RunStat> topTestFailing(int cnt) { return CollectionUtil.top(allTestAnalysis(), cnt, Comparator.comparing(RunStat::getFailRate)); diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java index 079c80b..d3421b7 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java @@ -62,6 +62,7 @@ import org.apache.ignite.ci.tcmodel.conf.bt.BuildTypeFull; import org.apache.ignite.ci.tcmodel.hist.BuildRef; import org.apache.ignite.ci.tcmodel.hist.Builds; import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.tcmodel.result.issues.IssuesUsagesList; import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrences; import org.apache.ignite.ci.tcmodel.result.stat.Statistics; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull; @@ -354,18 +355,28 @@ public class IgniteTeamcityHelper implements ITeamcity { private List<BuildRef> getBuildHistory(@Nullable String buildTypeId, @Nullable String branchName, boolean dfltFilter, - @Nullable String state) { + @Nullable String state){ + + return getBuildHistory(buildTypeId, branchName, dfltFilter, state, null); + } + + private List<BuildRef> getBuildHistory(@Nullable String buildTypeId, + @Nullable String branchName, + boolean dfltFilter, + @Nullable String state, + @Nullable Long cnt) { String btFilter = isNullOrEmpty(buildTypeId) ? "" : ",buildType:" + buildTypeId + ""; String stateFilter = isNullOrEmpty(state) ? "" : (",state:" + state); - String brachFilter = isNullOrEmpty(branchName) ? "" :",branch:" + branchName; + String branchFilter = isNullOrEmpty(branchName) ? "" :",branch:" + branchName; + long cntFilter = cnt == null ? DEFAULT_BUILDS_COUNT : cnt; return sendGetXmlParseJaxb(host + "app/rest/latest/builds" + "?locator=" + "defaultFilter:" + dfltFilter + btFilter + stateFilter - + brachFilter - + ",count:1000", Builds.class).getBuildsNonNull(); + + branchFilter + + ",count:" + cntFilter, Builds.class).getBuildsNonNull(); } public BuildTypeFull getBuildType(String buildTypeId) { @@ -378,7 +389,16 @@ public class IgniteTeamcityHelper implements ITeamcity { } public ProblemOccurrences getProblems(Build build) { - return getJaxbUsingHref(build.problemOccurrences.href, ProblemOccurrences.class); + if (build.problemOccurrences != null) { + ProblemOccurrences problemOccurrences = getJaxbUsingHref(build.problemOccurrences.href, ProblemOccurrences.class); + + problemOccurrences.problemOccurrences + .forEach(p -> p.buildRef = build); + + return problemOccurrences; + } + else + return new ProblemOccurrences(); } public TestOccurrences getTests(String href, String normalizedBranch) { @@ -401,6 +421,8 @@ public class IgniteTeamcityHelper implements ITeamcity { return getJaxbUsingHref(href, ChangesList.class); } + public IssuesUsagesList getIssuesUsagesList(String href) { return getJaxbUsingHref(href, IssuesUsagesList.class); } + private <T> T getJaxbUsingHref(String href, Class<T> elem) { return sendGetXmlParseJaxb(host + (href.startsWith("/") ? href.substring(1) : href), elem); } @@ -411,17 +433,29 @@ public class IgniteTeamcityHelper implements ITeamcity { /** {@inheritDoc} */ @Override public List<BuildRef> getFinishedBuilds(String projectId, String branch) { + + return getFinishedBuilds(projectId, branch, null); + } + + /** {@inheritDoc} */ + public List<BuildRef> getFinishedBuilds(String projectId, + String branch, Long cnt) { List<BuildRef> finished = getBuildHistory(projectId, UrlUtil.escape(branch), true, - null); + null, cnt); return finished.stream().filter(BuildRef::isNotCancelled).collect(Collectors.toList()); } /** {@inheritDoc} */ @Override public List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch) { - return getBuildsInState(projectId, branch, BuildRef.STATE_FINISHED); + return getBuildsInState(projectId, branch, BuildRef.STATE_FINISHED, null); + } + + /** {@inheritDoc} */ + @Override public List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch, Long cnt) { + return getBuildsInState(projectId, branch, BuildRef.STATE_FINISHED, cnt); } /** {@inheritDoc} */ @@ -437,14 +471,23 @@ public class IgniteTeamcityHelper implements ITeamcity { private List<BuildRef> getBuildsInState( @Nullable final String projectId, @Nullable final String branch, - @Nonnull final String state) { + @Nonnull final String state, + @Nullable final Long cnt) { List<BuildRef> finished = getBuildHistory(projectId, UrlUtil.escape(branch), false, - state); + state, cnt); return finished.stream().filter(BuildRef::isNotCancelled).collect(Collectors.toList()); } + private List<BuildRef> getBuildsInState( + @Nullable final String projectId, + @Nullable final String branch, + @Nonnull final String state) { + + return getBuildsInState(projectId, branch, state, null); + } + public String serverId() { return tcName; } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/Expirable.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/Expirable.java index 16a9f2b..32d176a 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/Expirable.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/Expirable.java @@ -27,14 +27,16 @@ import org.apache.ignite.ci.db.Persisted; public class Expirable<D> { private final long ts; private final D data; + private final long cnt; public Expirable(D data) { - this(System.currentTimeMillis(), data); + this(System.currentTimeMillis(), 1, data); } - public Expirable(long ts, D data) { + public Expirable(long ts, long cnt, D data) { this.ts = ts; this.data = data; + this.cnt = cnt; } public long getTs() { @@ -45,6 +47,10 @@ public class Expirable<D> { return data; } + public long getCnt(){ + return cnt; + } + public long getAgeMs() { return System.currentTimeMillis() - ts; } @@ -52,4 +58,8 @@ public class Expirable<D> { public boolean isAgeLessThanSecs(int seconds) { return getAgeMs() < TimeUnit.SECONDS.toMillis(seconds); } + + public boolean hasCounterGreaterThan(long cnt){ + return cnt < this.cnt; + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/MultBuildRunCtx.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/MultBuildRunCtx.java index 7b62e23..4a9d47c 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/MultBuildRunCtx.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/MultBuildRunCtx.java @@ -125,7 +125,7 @@ public class MultBuildRunCtx implements ISuiteResults { public boolean hasNontestBuildProblem() { return problems != null && problems.stream().anyMatch(problem -> !problem.isFailedTests() - && !problem.isShaphotDepProblem() + && !problem.isSnapshotDepProblem() && !ProblemOccurrence.BUILD_FAILURE_ON_MESSAGE.equals(problem.type)); //todo what to do with BuildFailureOnMessage, now it is ignored } @@ -135,7 +135,7 @@ public class MultBuildRunCtx implements ISuiteResults { } private Optional<ProblemOccurrence> getBuildProblemExceptTestOrSnapshot() { - return problems.stream().filter(p -> !p.isFailedTests() && !p.isShaphotDepProblem()).findAny(); + return problems.stream().filter(p -> !p.isFailedTests() && !p.isSnapshotDepProblem()).findAny(); } public List<SingleBuildRunCtx> getBuilds() { @@ -244,7 +244,7 @@ public class MultBuildRunCtx implements ISuiteResults { Stream<ProblemOccurrence> stream = problems.stream().filter(p -> !p.isFailedTests() - && !p.isShaphotDepProblem() + && !p.isSnapshotDepProblem() && !p.isExecutionTimeout() && !p.isJvmCrash() && !p.isExitCode() diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/ProblemRef.java similarity index 83% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueRef.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/ProblemRef.java index d64ddf6..7c04fbd 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueRef.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/ProblemRef.java @@ -17,11 +17,15 @@ package org.apache.ignite.ci.issue; -public class IssueRef { +/** + * Reference to some Issue with current suite or test detected by the Bot. + * Currently contains only display name. + */ +public class ProblemRef { public String name; public String webUrl; - public IssueRef(String name) { + public ProblemRef(String name) { this.name = name; } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java index 5022985..0e6202b 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java @@ -52,6 +52,8 @@ public class BuildRef extends AbstractRef { @XmlAttribute public Boolean composite; + @XmlAttribute public String webUrl; + /** * @return Build ID */ diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Build.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Build.java index ece1c8f..9c6a9cd 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Build.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Build.java @@ -58,6 +58,8 @@ public class Build extends BuildRef implements IVersionedEntity { @XmlElement(name = "statistics") public StatisticsRef statisticsRef; + @XmlElement(name = "relatedIssues") public RelatedIssuesRef relatedIssuesRef; + /** Changes not included into build.*/ @XmlElement(name = "lastChanges") public ChangesList lastChanges; @@ -87,11 +89,19 @@ public class Build extends BuildRef implements IVersionedEntity { } public Date getFinishDate() { + return getDate(finishDate); + } + + public Date getStartDate() { + return getDate(startDate); + } + + private Date getDate(String date) { try { - if (finishDate == null) + if (date == null) return null; SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ"); - return f.parse(finishDate); + return f.parse(date); } catch (ParseException e) { throw new IllegalStateException(e); @@ -127,4 +137,4 @@ public class Build extends BuildRef implements IVersionedEntity { public void setTriggered(Triggered triggered) { this.triggered = triggered; } -} +} \ No newline at end of file diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/ProblemOccurrencesRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/ProblemOccurrencesRef.java index 7bc3ff1..a382ddb 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/ProblemOccurrencesRef.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/ProblemOccurrencesRef.java @@ -25,4 +25,8 @@ import javax.xml.bind.annotation.XmlAttribute; public class ProblemOccurrencesRef { /** Href without host name to obtain full problems list. */ @XmlAttribute public String href; + + @XmlAttribute public long count; + + @XmlAttribute public long newFailed; } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/RelatedIssuesRef.java similarity index 82% rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueRef.java rename to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/RelatedIssuesRef.java index d64ddf6..81e1157 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueRef.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/RelatedIssuesRef.java @@ -15,13 +15,11 @@ * limitations under the License. */ -package org.apache.ignite.ci.issue; +package org.apache.ignite.ci.tcmodel.result; -public class IssueRef { - public String name; - public String webUrl; - - public IssueRef(String name) { - this.name = name; - } +/** + * Related issues reference. + */ +public class RelatedIssuesRef extends AbstractRef { } + diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/TestOccurrencesRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/TestOccurrencesRef.java index 6cbda4a..b250e98 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/TestOccurrencesRef.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/TestOccurrencesRef.java @@ -29,5 +29,7 @@ public class TestOccurrencesRef { @XmlAttribute public Integer count; @XmlAttribute public Integer passed; @XmlAttribute public Integer failed; + @XmlAttribute public Integer newFailed; + @XmlAttribute public Integer ignored; @XmlAttribute public Integer muted; } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/TestOccurrencesRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueRef.java similarity index 63% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/TestOccurrencesRef.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueRef.java index 6cbda4a..09ea91a 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/TestOccurrencesRef.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueRef.java @@ -15,19 +15,24 @@ * limitations under the License. */ -package org.apache.ignite.ci.tcmodel.result; +package org.apache.ignite.ci.tcmodel.result.issues; import javax.xml.bind.annotation.XmlAttribute; /** - * Test occurrences reference + * Issue short version from list of build's related issues. + * + * See example of XML, e.g. here + * https://ci.ignite.apache.org/app/rest/latest/builds/id:1694977/relatedIssues */ -public class TestOccurrencesRef { - /** Href without host name to obtain full tests list. */ - @XmlAttribute public String href; +public class IssueRef { + @XmlAttribute public String id; + @XmlAttribute public String url; - @XmlAttribute public Integer count; - @XmlAttribute public Integer passed; - @XmlAttribute public Integer failed; - @XmlAttribute public Integer muted; + @Override public String toString() { + return "IssueRef{" + + "id='" + id + '\'' + + ", url='" + url + '\'' + + '}'; + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueUsage.java similarity index 58% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueUsage.java index 43166d4..ae57c4d 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueUsage.java @@ -15,23 +15,29 @@ * limitations under the License. */ -package org.apache.ignite.ci.tcmodel.result.problems; +package org.apache.ignite.ci.tcmodel.result.issues; -import java.util.Collections; -import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import org.apache.ignite.ci.tcmodel.result.ProblemOccurrencesRef; +import org.apache.ignite.ci.tcmodel.changes.ChangesList; /** - * List of problems associated with build. + * Build's related issue from TC. + * + * See example of XML, e.g. here + * https://ci.ignite.apache.org/app/rest/latest/builds/id:1694977/relatedIssues */ -@XmlRootElement(name = "problemOccurrences") -public class ProblemOccurrences extends ProblemOccurrencesRef { - @XmlElement(name = "problemOccurrence") - private List<ProblemOccurrence> problemOccurrences; +@XmlRootElement(name = "IssueUsage") +@XmlAccessorType(XmlAccessType.FIELD) +public class IssueUsage { + @XmlElement(name = "issue") + private IssueRef issue; + @XmlElement(name = "changes") + private ChangesList changesList; - public List<ProblemOccurrence> getProblemsNonNull() { - return problemOccurrences == null ? Collections.emptyList() : problemOccurrences; + public IssueRef getIssue(){ + return issue; } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssuesUsagesList.java similarity index 57% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssuesUsagesList.java index 43166d4..1212ab6 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssuesUsagesList.java @@ -15,23 +15,35 @@ * limitations under the License. */ -package org.apache.ignite.ci.tcmodel.result.problems; +package org.apache.ignite.ci.tcmodel.result.issues; import java.util.Collections; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import org.apache.ignite.ci.tcmodel.result.ProblemOccurrencesRef; /** - * List of problems associated with build. + * List of build's related issues from TC. + * + * See example of XML, e.g. here + * https://ci.ignite.apache.org/app/rest/latest/builds/id:1694977/relatedIssues */ -@XmlRootElement(name = "problemOccurrences") -public class ProblemOccurrences extends ProblemOccurrencesRef { - @XmlElement(name = "problemOccurrence") - private List<ProblemOccurrence> problemOccurrences; +@XmlRootElement(name = "issuesUsages") +public class IssuesUsagesList { + @XmlElement(name = "issueUsage") + private List<IssueUsage> issuesUsages; + + @XmlElement Integer count; + + @XmlElement String href; + + public List<IssueUsage> getIssuesUsagesNonNull() { + return issuesUsages == null ? Collections.emptyList() : issuesUsages; + } - public List<ProblemOccurrence> getProblemsNonNull() { - return problemOccurrences == null ? Collections.emptyList() : problemOccurrences; + @Override public String toString() { + return "IssuesUsagesList{" + + "issuesUsages=" + issuesUsages + + '}'; } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrence.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrence.java index 92868bc..b8ae4f0 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrence.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrence.java @@ -18,34 +18,41 @@ package org.apache.ignite.ci.tcmodel.result.problems; import javax.xml.bind.annotation.XmlAttribute; +import org.apache.ignite.ci.tcmodel.hist.BuildRef; /** * One build problem. Contains its type. */ public class ProblemOccurrence { public static final String BUILD_FAILURE_ON_MESSAGE = "BuildFailureOnMessage"; - private static final String TC_EXIT_CODE = "TC_EXIT_CODE"; - private static final String TC_OOME = "TC_OOME"; + public static final String TC_EXIT_CODE = "TC_EXIT_CODE"; + public static final String TC_OOME = "TC_OOME"; + public static final String TC_EXECUTION_TIMEOUT = "TC_EXECUTION_TIMEOUT"; + public static final String TC_FAILED_TESTS = "TC_FAILED_TESTS"; + public static final String TC_JVM_CRASH = "TC_JVM_CRASH"; + public static final String OTHER = "OTHER"; + public static final String SNAPSHOT_DEPENDENCY_ERROR = "SNAPSHOT_DEPENDENCY_ERROR_BUILD_PROCEEDS_TYPE"; @XmlAttribute public String id; @XmlAttribute public String identity; @XmlAttribute public String type; @XmlAttribute public String href; + public BuildRef buildRef; public boolean isExecutionTimeout() { - return "TC_EXECUTION_TIMEOUT".equals(type); + return TC_EXECUTION_TIMEOUT.equals(type); } public boolean isFailedTests() { - return "TC_FAILED_TESTS".equals(type); + return TC_FAILED_TESTS.equals(type); } - public boolean isShaphotDepProblem() { - return "SNAPSHOT_DEPENDENCY_ERROR_BUILD_PROCEEDS_TYPE".equals(type); + public boolean isSnapshotDepProblem() { + return SNAPSHOT_DEPENDENCY_ERROR.equals(type); } public boolean isJvmCrash() { - return "TC_JVM_CRASH".equals(type); + return TC_JVM_CRASH.equals(type); } public boolean isOome() { @@ -55,4 +62,13 @@ public class ProblemOccurrence { public boolean isExitCode() { return TC_EXIT_CODE.equals(type); } + + public boolean isOther(){ + return !isFailedTests() + && !isSnapshotDepProblem() + && !isExecutionTimeout() + && !isJvmCrash() + && !isExitCode() + && !isOome(); + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java index 43166d4..c95a7bc 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/problems/ProblemOccurrences.java @@ -29,9 +29,10 @@ import org.apache.ignite.ci.tcmodel.result.ProblemOccurrencesRef; @XmlRootElement(name = "problemOccurrences") public class ProblemOccurrences extends ProblemOccurrencesRef { @XmlElement(name = "problemOccurrence") - private List<ProblemOccurrence> problemOccurrences; + public List<ProblemOccurrence> problemOccurrences; public List<ProblemOccurrence> getProblemsNonNull() { return problemOccurrences == null ? Collections.emptyList() : problemOccurrences; } } + diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/BuildStatisticsSummary.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/BuildStatisticsSummary.java new file mode 100644 index 0000000..2b73771 --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/BuildStatisticsSummary.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.ci.web.model.current; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.apache.ignite.ci.ITeamcity; +import org.apache.ignite.ci.tcmodel.hist.BuildRef; +import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.tcmodel.result.issues.IssueRef; +import org.apache.ignite.ci.tcmodel.result.issues.IssueUsage; +import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrence; +import org.apache.ignite.ci.util.TimeUtil; +import org.apache.ignite.ci.web.IBackgroundUpdatable; + +/** + * Summary of build statistics. + */ +public class BuildStatisticsSummary extends UpdateInfo implements IBackgroundUpdatable { + /** Build with test and problems references. */ + public Build build; + + /** List of problem occurrences. */ + private List<ProblemOccurrence> problems; + + /** List of related issues. */ + public List<IssueRef> relatedIssues; + + /** Duration printable. */ + public String durationPrintable; + + /** Short build run result (without snapshot-dependencies printable result). */ + public String shortRes; + + /** Snapshot-dependencies build run result. */ + public List<String> fullRes; + + /** Snapshot-dependency. */ + private List<BuildRef> snapshotDependencies; + + /** Build problems count. */ + public long problemsCount; + + public BuildStatisticsSummary(Build build){ + this.build = build; + } + + /** Initialize build statistics. */ + public void initialize(@Nonnull final ITeamcity teamcity) { + + if (build.isFakeStub()) + return; + + relatedIssues = teamcity.getIssuesUsagesList(build.relatedIssuesRef.href).getIssuesUsagesNonNull().stream() + .map(IssueUsage::getIssue).collect(Collectors.toList()); + + durationPrintable = TimeUtil + .getDurationPrintable(build.getFinishDate().getTime() - build.getStartDate().getTime()); + + snapshotDependencies = getSnapshotDependencies(teamcity, build); + + problems = getProblems(teamcity); + + shortRes = getShortRes(); + + fullRes = getFullRes(); + + problemsCount = getAllProblemsCount(null); + } + + private long getExecutionTimeoutCount(String buildTypeId) { + return getProblemsStream(buildTypeId).filter(ProblemOccurrence::isExecutionTimeout).count(); + } + + private long getJvmCrashProblemCount(String buildTypeId) { + return getProblemsStream(buildTypeId).filter(ProblemOccurrence::isJvmCrash).count(); + } + + private long getExitCodeProblemsCount(String buildTypeId) { + return getProblemsStream(buildTypeId).filter(ProblemOccurrence::isExitCode).count(); + } + + private long getOomeProblemCount(String buildTypeId) { + return getProblemsStream(buildTypeId).filter(ProblemOccurrence::isOome).count(); + } + + private long getFailedTestsProblemCount(String buildTypeId) { + return getProblemsStream(buildTypeId).filter(ProblemOccurrence::isFailedTests).count(); + } + + private long getSnapshotDepProblemCount(String buildTypeId) { + return getProblemsStream(buildTypeId).filter(ProblemOccurrence::isSnapshotDepProblem).count(); + } + + private long getOtherProblemCount(String buildTypeId) { + return getProblemsStream(buildTypeId).filter(ProblemOccurrence::isOther).count(); + } + + private long getAllProblemsCount(String buildTypeId) { + return getProblemsStream(buildTypeId).count(); + } + + /** + * Problems for all snapshot-dependencies. + * + * @param teamcity Teamcity. + */ + private List<ProblemOccurrence> getProblems(@Nonnull final ITeamcity teamcity){ + if (snapshotDependencies == null) + return Collections.emptyList(); + + List<ProblemOccurrence> problemOccurrences = new ArrayList<>(); + + List<BuildRef> snapshotDependencyWithProblems = getSnapshotDependenciesWithProblems(); + + for (BuildRef buildRef : snapshotDependencyWithProblems) + problemOccurrences.addAll(teamcity + .getProblems(teamcity.getBuild(buildRef.href)) + .getProblemsNonNull()); + + return problemOccurrences; + } + + /** + * Snapshot-dependencies for build. + * + * @param teamcity Teamcity. + * @param buildRef Build reference. + */ + private List<BuildRef> getSnapshotDependencies(@Nonnull final ITeamcity teamcity, BuildRef buildRef){ + List<BuildRef> snapshotDependencies = new ArrayList<>(); + + if (buildRef.isComposite()){ + Build build = teamcity.getBuild(buildRef.href); + + for (BuildRef snDep : build.getSnapshotDependenciesNonNull()) + snapshotDependencies.addAll(getSnapshotDependencies(teamcity, snDep)); + } else + snapshotDependencies.add(buildRef); + + return snapshotDependencies; + } + + /** + * Snapshot-dependencies without status "Success". + */ + private List<BuildRef> getSnapshotDependenciesWithProblems(){ + if (snapshotDependencies == null) + return Collections.emptyList(); + + return snapshotDependencies.stream() + .filter(b -> !b.isSuccess()) + .collect(Collectors.toList()); + } + + /** + * @param buildTypeId Build type id (if null - for all problems). + */ + private Stream<ProblemOccurrence> getProblemsStream(String buildTypeId) { + if (problems == null) + return Stream.empty(); + + return problems.stream() + .filter(Objects::nonNull) + .filter(p -> buildTypeId == null || buildTypeId.equals(p.buildRef.buildTypeId) + ); + } + + /** + * Full build run result (snapshot-dependencies printable result). + * + * @return printable result; + */ + private List<String> getFullRes(){ + List<String> fullRes = new ArrayList<>(); + + List<BuildRef> snapshotDependencyWithProblems = getSnapshotDependenciesWithProblems(); + + for (BuildRef build : snapshotDependencyWithProblems) + fullRes.add(getRes(build.buildTypeId)); + + return fullRes.stream() + .sorted() + .collect(Collectors.toList()); + } + + /** + * Short build run result (without snapshot-dependencies printable result). + * + * @return printable result; + */ + private String getShortRes(){ + return getRes(null); + } + + /** + * Build run result for buildTypeId. + * + * @param buildTypeId buildTypeId. + * + * @return printable result. + */ + private String getRes(String buildTypeId){ + StringBuilder res = new StringBuilder(); + + addKnownProblemCnt(res, ProblemOccurrence.TC_EXECUTION_TIMEOUT, getExecutionTimeoutCount(buildTypeId)); + addKnownProblemCnt(res, ProblemOccurrence.TC_JVM_CRASH, getJvmCrashProblemCount(buildTypeId)); + addKnownProblemCnt(res, ProblemOccurrence.TC_OOME, getOomeProblemCount(buildTypeId)); + addKnownProblemCnt(res, ProblemOccurrence.TC_EXIT_CODE, getExitCodeProblemsCount(buildTypeId)); + addKnownProblemCnt(res, ProblemOccurrence.TC_FAILED_TESTS, getFailedTestsProblemCount(buildTypeId)); + addKnownProblemCnt(res, ProblemOccurrence.SNAPSHOT_DEPENDENCY_ERROR, getSnapshotDepProblemCount(buildTypeId)); + addKnownProblemCnt(res, ProblemOccurrence.OTHER, getOtherProblemCount(buildTypeId)); + + res.insert(0, (buildTypeId != null ? buildTypeId : "TOTAL") + " [" + getAllProblemsCount(buildTypeId) + "]" + + (res.length() != 0 ? ": " : " ")); + + return res.toString(); + } + + /** + * @param res Response. + * @param nme Name of problem. + * @param execToCnt Execute to count. + */ + private void addKnownProblemCnt(StringBuilder res, String nme, long execToCnt) { + if (execToCnt > 0) { + if (res.length() > 0) + res.append(", "); + + res.append(nme) + .append(execToCnt > 1 ? " [" + execToCnt + "]" : ""); + } + } + + /** {@inheritDoc} */ + @Override public void setUpdateRequired(boolean update) { + updateRequired = update; + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (this == o) + return true; + + if (!(o instanceof BuildStatisticsSummary)) + return false; + + BuildStatisticsSummary that = (BuildStatisticsSummary)o; + + return problemsCount == that.problemsCount && + Objects.equals(build, that.build) && + Objects.equals(problems, that.problems) && + Objects.equals(relatedIssues, that.relatedIssues) && + Objects.equals(durationPrintable, that.durationPrintable) && + Objects.equals(getShortRes(), that.getShortRes()) && + Objects.equals(getFullRes(), that.getFullRes()) && + Objects.equals(snapshotDependencies, that.snapshotDependencies); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + + return Objects.hash(build, problems, relatedIssues, durationPrintable, getShortRes(), getFullRes(), + snapshotDependencies, problemsCount); + } +} \ No newline at end of file diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/SuiteCurrentStatus.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/SuiteCurrentStatus.java index 1a6b6c6..91945c1 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/SuiteCurrentStatus.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/SuiteCurrentStatus.java @@ -38,7 +38,7 @@ import org.apache.ignite.ci.analysis.SuiteInBranch; import org.apache.ignite.ci.analysis.TestInBranch; import org.apache.ignite.ci.analysis.TestLogCheckResult; import org.apache.ignite.ci.issue.EventTemplates; -import org.apache.ignite.ci.issue.IssueRef; +import org.apache.ignite.ci.issue.ProblemRef; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull; import org.apache.ignite.ci.web.rest.GetBuildLog; import org.jetbrains.annotations.NotNull; @@ -105,7 +105,7 @@ import static org.apache.ignite.ci.util.UrlUtil.escape; public String durationPrintable; - @Nullable public IssueRef problemRef; + @Nullable public ProblemRef problemRef; public void initFromContext(@Nonnull final ITeamcity teamcity, @@ -244,12 +244,12 @@ import static org.apache.ignite.ci.util.UrlUtil.escape; RunStat.TestId testId = latestRunsSrc.detectTemplate(EventTemplates.newFailure); if (testId != null) - problemRef = new IssueRef("New Failure"); + problemRef = new ProblemRef("New Failure"); RunStat.TestId buildIdCritical = latestRunsSrc.detectTemplate(EventTemplates.newCriticalFailure); if (buildIdCritical != null) - problemRef = new IssueRef("New Critical Failure"); + problemRef = new ProblemRef("New Critical Failure"); } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java index 38b9399..559fb3e 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java @@ -32,7 +32,7 @@ import org.apache.ignite.ci.analysis.ITestFailureOccurrences; import org.apache.ignite.ci.analysis.RunStat; import org.apache.ignite.ci.analysis.TestInBranch; import org.apache.ignite.ci.issue.EventTemplates; -import org.apache.ignite.ci.issue.IssueRef; +import org.apache.ignite.ci.issue.ProblemRef; import org.apache.ignite.ci.logs.LogMsgToWarn; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull; @@ -78,7 +78,7 @@ import static org.apache.ignite.ci.web.model.current.SuiteCurrentStatus.branchFo public List<String> warnings = new ArrayList<>(); - @Nullable public IssueRef problemRef; + @Nullable public ProblemRef problemRef; @Nullable public String flakyComments; @@ -209,12 +209,12 @@ import static org.apache.ignite.ci.web.model.current.SuiteCurrentStatus.branchFo RunStat.TestId testId = latestRunsSrcInBranch.detectTemplate(EventTemplates.newFailure); if (testId != null) - problemRef = new IssueRef("New Failure"); + problemRef = new ProblemRef("New Failure"); RunStat.TestId recentContributedTestId = latestRunsSrcInBranch.detectTemplate(EventTemplates.newContributedTestFailure); if (recentContributedTestId != null) - problemRef = new IssueRef("Recently contributed test failure"); + problemRef = new ProblemRef("Recently contributed test failure"); flakyComments = latestRunsSrcInBranch.getFlakyComments(); } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/build/GetBuildTestFailures.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/build/GetBuildTestFailures.java index 7ba09a2..5c860c6 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/build/GetBuildTestFailures.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/build/GetBuildTestFailures.java @@ -17,13 +17,11 @@ package org.apache.ignite.ci.web.rest.build; +import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; @@ -36,27 +34,30 @@ import org.apache.ignite.ci.BuildChainProcessor; import org.apache.ignite.ci.IAnalyticsEnabledTeamcity; import org.apache.ignite.ci.ITcHelper; import org.apache.ignite.ci.ITeamcity; -import org.apache.ignite.ci.IgnitePersistentTeamcity; import org.apache.ignite.ci.analysis.FullChainRunCtx; import org.apache.ignite.ci.analysis.mode.LatestRebuildMode; import org.apache.ignite.ci.analysis.mode.ProcessLogsMode; import org.apache.ignite.ci.tcmodel.hist.BuildRef; import org.apache.ignite.ci.user.ICredentialsProv; -import org.apache.ignite.ci.web.rest.login.ServiceUnauthorizedException; import org.apache.ignite.ci.web.BackgroundUpdater; import org.apache.ignite.ci.web.CtxListener; +import org.apache.ignite.ci.web.model.current.BuildStatisticsSummary; import org.apache.ignite.ci.web.model.current.ChainAtServerCurrentStatus; import org.apache.ignite.ci.web.model.current.TestFailuresSummary; import org.apache.ignite.ci.web.model.current.UpdateInfo; +import org.apache.ignite.ci.web.rest.login.ServiceUnauthorizedException; import org.apache.ignite.ci.web.rest.parms.FullQueryParams; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static com.google.common.base.Strings.isNullOrEmpty; + @Path(GetBuildTestFailures.BUILD) @Produces(MediaType.APPLICATION_JSON) public class GetBuildTestFailures { public static final String BUILD = "build"; public static final String TEST_FAILURES_SUMMARY_CACHE_NAME = BUILD + "TestFailuresSummary"; + public static final String BUILDS_STATISTICS_SUMMARY_CACHE_NAME = BUILD + "sStatisticsSummary"; @Context private ServletContext context; @@ -88,7 +89,7 @@ public class GetBuildTestFailures { @QueryParam("serverId") String serverId, @QueryParam("buildId") Integer buildId, @Nullable @QueryParam("checkAllLogs") Boolean checkAllLogs) - throws ServiceUnauthorizedException { + throws ServiceUnauthorizedException { final BackgroundUpdater updater = CtxListener.getBackgroundUpdater(context); @@ -131,7 +132,6 @@ public class GetBuildTestFailures { (checkAllLogs != null && checkAllLogs) ? ProcessLogsMode.ALL : ProcessLogsMode.SUITE_NOT_COMPLETE, false, false, teamcity, failRateBranch); - pubCtx.ifPresent(ctx -> { final ChainAtServerCurrentStatus chainStatus = new ChainAtServerCurrentStatus(serverId, ctx.branchName()); @@ -149,4 +149,67 @@ public class GetBuildTestFailures { return res; } -} + + @GET + @Path("history") + public List<BuildStatisticsSummary> getBuildsHistory( + @Nullable @QueryParam("server") String server, + @Nullable @QueryParam("buildType") String buildType, + @Nullable @QueryParam("branch") String branch, + @Nullable @QueryParam("count") Long count) + throws ServiceUnauthorizedException { + + String srvId = isNullOrEmpty(server) ? "apache" : server; + String buildTypeId = isNullOrEmpty(buildType) ? "IgniteTests24Java8_RunAll" : buildType; + String branchName = isNullOrEmpty(branch) ? "refs/heads/master" : branch; + long cnt = count == null ? 50 : count; + + final BackgroundUpdater updater = CtxListener.getBackgroundUpdater(context); + + final ITcHelper tcHelper = CtxListener.getTcHelper(context); + + final ICredentialsProv prov = ICredentialsProv.get(req); + + try (IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, prov)) { + + int[] finishedBuilds = teamcity.getBuildNumbersFromHistory(buildTypeId, branchName, cnt); + + List<BuildStatisticsSummary> buildsStatistics = new ArrayList<>(); + + for (int i = 0; i < finishedBuilds.length; i++) { + int buildId = finishedBuilds[i]; + + FullQueryParams param = new FullQueryParams(); + param.setBuildId(buildId); + param.setBranch(branchName); + param.setServerId(srvId); + + BuildStatisticsSummary buildsStatistic = updater.get( + BUILDS_STATISTICS_SUMMARY_CACHE_NAME, prov, param, + (k) -> getBuildStatisticsSummaryNoCache(srvId, buildId), false); + + if (!buildsStatistic.build.isFakeStub()) + buildsStatistics.add(buildsStatistic); + } + + return buildsStatistics; + } + } + + private BuildStatisticsSummary getBuildStatisticsSummaryNoCache(String server, int buildId) { + + String srvId = isNullOrEmpty(server) ? "apache" : server; + + final ITcHelper tcHelper = CtxListener.getTcHelper(context); + + final ICredentialsProv creds = ICredentialsProv.get(req); + + try (IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, creds)) { + + BuildStatisticsSummary stat = new BuildStatisticsSummary(teamcity.getBuild(buildId)); + stat.initialize(teamcity); + + return stat; + } + } +} \ No newline at end of file diff --git a/ignite-tc-helper-web/src/main/webapp/index.html b/ignite-tc-helper-web/src/main/webapp/index.html index 2466f17..c642863 100644 --- a/ignite-tc-helper-web/src/main/webapp/index.html +++ b/ignite-tc-helper-web/src/main/webapp/index.html @@ -6,7 +6,7 @@ <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <link rel="stylesheet" href="css/style-1.5.css"> - + <script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script src="js/common-1.6.js"></script> @@ -45,7 +45,7 @@ function loadData() { error: showErrInLoadStatus }); - $.ajax({ + $.ajax({ url: "rest/branches/getServerIds", success: function(result) { $("#loadStatus").html(""); @@ -122,7 +122,8 @@ Statistics: <br> <a href="restpretty.html?url=top/failingSuite">Top failing suites</a> (JSON) <br> <!--<a href="./status">Current Build Status (obsolete)</a><br>--> <br> - +<a href="statistics.html?buildType=IgniteTests24Java8_RunAll&branch=refs/heads/master&count=50">Master's branch statistics</a><br> +<br> Check branch/PR: <br> <div id="suitesForPrCheck"></div> <br> diff --git a/ignite-tc-helper-web/src/main/webapp/statistics.html b/ignite-tc-helper-web/src/main/webapp/statistics.html new file mode 100644 index 0000000..ffdf9bb --- /dev/null +++ b/ignite-tc-helper-web/src/main/webapp/statistics.html @@ -0,0 +1,203 @@ +<html> +<head> + <title>Ignite Teamcity - statistics master's branch</title> + <link rel="icon" href="img/leaf-icon-png-7066.png"> + <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> + <link rel="stylesheet" href="css/style-1.5.css"> + + <script src="https://code.jquery.com/jquery-1.12.4.js"></script> + <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> + <script src="js/common-1.6.js"></script> + <script src="js/testfails-2.0.js"></script> + <style> + table { + width: 70%; + border-collapse: collapse; + } + td, th { + + padding: 10px 5px 10px 5px; + } + th { + text-align: left; + padding: 5px; + background-color: #f5f5ff; + color: #000000; + } + tr:nth-child(odd) { background-color: #fafaff; } + </style> +</head> +<body> +<script> + var g_shownDataHashCodeHex = "" + var g_updTimer = null; + + $(document).ready(function() { + $.getScript("js/testfails-2.0.js", function(data, textStatus, jqxhr){ }); + + $( document ).tooltip(); + loadData(); + //todo fix setInterval( function() { checkForUpdate(); }, 30000); + + $.ajax({ url: "rest/branches/version", success: showVersionInfo, error: showErrInLoadStatus }); + + if(g_updTimer==null) { + g_updTimer=setTimeout(tstTimeout, 3200); + } + setInterval(tstTimeout, 10000); + }); + + function tstTimeout() { + + if(g_updTimer!=null) { + clearTimeout(g_updTimer); + g_updTimer=null; + } + + + var d = new Date(); + var n = d.getTime(); + + // $(document.body).prepend("timeout at "+n+"<br>"); + + + if(g_updTimer==null) { + g_updTimer=setTimeout(tstTimeout, 3200); + } + } + + function parmsForRest() { + var curReqParms = ""; + var server = findGetParameter("server"); + if(server!=null) { + curReqParms += "&server=" + server; + } + + var buildType = findGetParameter("buildType"); + if(buildType!=null) { + curReqParms += "&buildType=" + buildType; + } + + var branch = findGetParameter("branch"); + if(branch!=null) { + curReqParms += "&branch=" + branch; + } + + var count = findGetParameter("count"); + if(count!=null) { + curReqParms += "&count=" + count; + } + + curReqParms = curReqParms.replace("&","?"); + + return curReqParms; + } + + function loadData() { + var curFailuresUrl = "rest/build/history" + parmsForRest(); + + $("#loadStatus").html("<img src='https://www.wallies.com/filebin/images/loading_apple.gif' width=20px height=20px> Please wait"); + $.ajax({ + url: curFailuresUrl, + success: function(result) { + if(result.updateRequired || (isDefinedAndFilled(result.runningUpdates) && result.runningUpdates>0)) { + setTimeout(checkForUpdate, 3000) + $("#loadStatus").html("<img src='https://www.wallies.com/filebin/images/loading_apple.gif' width=20px height=20px> Updating"); + } else { + $("#loadStatus").html(""); + } + showData(result); + g_shownDataHashCodeHex = isDefinedAndFilled(result.hashCodeHex) ? result.hashCodeHex : ""; + }, + error: showErrInLoadStatus + }); + } + function showData(result) { + $("#statistics").html(showBuildHistory(result)); + } + + function median(arr){ + arr = arr.sort(function(a, b){ return a - b; }); + var i = arr.length / 2; + return i % 1 == 0 ? (arr[i - 1] + arr[i]) / 2 : arr[Math.floor(i)]; + } + + function formatString(string) { + if (string.indexOf("_") < string.indexOf(":")) + string = string.substring(string.indexOf("_") + 1); + + return "<b>" + string.substring(0, string.indexOf(":") + 1) + "</b>" + string.substring(string.indexOf(":") + 1); + } + + function showBuildHistory(result) { + var res = "<table><tr><th>#</th><th>Id</th><th>Total tests</th><th>Failed tests</th><th>Ignored tests</th>" + + "<th>Muted tests</th><th>Total issues</th><th>Total run time</th></tr>"; + + var problemCountMin = result[0].problemsCount; + var problemCountMax = problemCountMin; + var problemCounts = []; + var average = 0; + + for (var i = 0; i < result.length; i++) { + var buildStatistics = result[i]; + + problemCounts[i] = buildStatistics.problemsCount; + + if (problemCounts[i] > problemCountMax) problemCountMax = problemCounts[i]; + + if (problemCounts[i] < problemCountMin) problemCountMin = problemCounts[i]; + + average += problemCounts[i] / buildStatistics.build.testOccurrences.failed; + + res += "<tr><td>" + (i + 1) + "</td>" + + "<td><a href='" + buildStatistics.build.webUrl + "'>" + buildStatistics.build.id + "</a></td>" + + "<td>" + buildStatistics.build.testOccurrences.count + "</td>" + + "<td>" + buildStatistics.build.testOccurrences.failed + "</td>" + + "<td>" + buildStatistics.build.testOccurrences.ignored + "</td>" + + "<td>" + buildStatistics.build.testOccurrences.muted + "</td><td>"; + + if (buildStatistics.fullRes.length > 0) { + res += "<details><summary>" + formatString(buildStatistics.shortRes) + "</summary><p>"; + + for (var k = 0; k < buildStatistics.fullRes.length; k++) { + res += (k + 1) + ". " + formatString(buildStatistics.fullRes[k]) + "<br>"; + } + + res += "</p></details><br>"; + } else { + res += "<p>" + buildStatistics.shortRes + "</p>"; + } + + var issuesArr = buildStatistics.relatedIssues; + + if (issuesArr.length > 0) { + res += "<details><summary>Related issues [" + issuesArr.length + "]</summary><p>"; + + for (var j = 0; j < issuesArr.length; j++) { + var issue = issuesArr[j]; + + if (j !== 0) res += ", "; + + res += "<a href='" + issue.url + "'>" + issue.id + "</a>"; + } + + res += "</p></details>"; + } + + res += "</td><td>" + buildStatistics.durationPrintable + "</td></tr>"; + } + + res += "</table><br>min: " + problemCountMin + "; max: " + problemCountMax + + "; median: " + (Math.round(median(problemCounts) * 1000) / 1000) + + "; average: " + (Math.round(average / result.length * 1000) / 1000) + "<br><br>"; + + return res; + + } +</script> +<br> +<div id="loadStatus"></div> +<div id="statistics"></div> +<div id="version"></div> +</body> +</html> \ No newline at end of file
