You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ignite.apache.org by GitBox <gi...@apache.org> on 2018/09/25 13:10:26 UTC

[GitHub] asfgit closed pull request #9: IGNITE-9541 Add the comparison for two general statistics "RunAll" for master in the date interval

asfgit closed pull request #9: IGNITE-9541 Add the comparison for two general statistics "RunAll" for master in the date interval
URL: https://github.com/apache/ignite-teamcity-bot/pull/9
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

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 6be0446..4151793 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
@@ -18,6 +18,7 @@
 package org.apache.ignite.ci;
 
 import java.io.File;
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
@@ -54,6 +55,7 @@
 public interface ITeamcity extends AutoCloseable {
 
     String DEFAULT = "<default>";
+
     long DEFAULT_BUILDS_COUNT = 1000;
 
     CompletableFuture<List<BuildType>> getProjectSuites(String projectId);
@@ -61,45 +63,45 @@
     String serverId();
 
     /**
-     * @param projectId suite ID (string without spaces)
-     * @param branch
-     * @return list of builds in historical order, recent builds coming last
+     * @param projectId Suite ID (string without spaces).
+     * @param branch Branch in TC identification.
+     * @return List of builds in historical order, recent builds coming last.
      */
     default List<BuildRef> getFinishedBuilds(String projectId, String branch) {
-        return getFinishedBuilds(projectId, branch, null, null);
+        return getFinishedBuilds(projectId, branch, null, null, null);
     };
 
     /**
      * @param projectId suite ID (string without spaces).
      * @param branch Branch name in TC identification.
-     * @param cnt builds count.
+     * @param sinceDate Since date.
+     * @param untilDate Until date.
      * @param sinceBuildId Some build ID in the past to to use as minimal build to export.
      * @return list of builds in historical order, recent builds coming last.
      */
-    List<BuildRef> getFinishedBuilds(String projectId, String branch, Long cnt, Integer sinceBuildId);
+    List<BuildRef> getFinishedBuilds(String projectId, String branch, Date sinceDate, Date untilDate, Integer sinceBuildId);
 
     /**
-     * Includes snapshot dependencies failed builds into list
+     * Includes snapshot dependencies failed builds into list.
      *
-     * @param projectId suite ID (string without spaces)
-     * @param branch branch in TC identification
-     * @return list of builds in historical order, recent builds coming last
+     * @param projectId suite ID (string without spaces).
+     * @param branch branch in TC identification.
+     * @return list of builds in historical order, recent builds coming last.
      */
     default List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch){
-        return getFinishedBuildsIncludeSnDepFailed(projectId, branch, null, null);
+        return getFinishedBuildsIncludeSnDepFailed(projectId, branch, null);
     };
 
     /**
      * Includes 'snapshot dependencies failed' builds into list.
      * loads build history with following parameter: defaultFilter:false,state:finished
      *
-     * @param projectId suite ID (string without spaces)
-     * @param branch branch in TC identification
-     * @param cnt builds count
-     * @param sinceBuildId limit builds export with some build number, not operational for Persistent connection
-     * @return list of builds in historical order, recent builds coming last
+     * @param projectId suite ID (string without spaces).
+     * @param branch branch in TC identification.
+     * @param sinceBuildId limit builds export with some build number, not operational for Persistent connection.
+     * @return list of builds in historical order, recent builds coming last.
      */
-    List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch, Long cnt, Integer sinceBuildId);
+    List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch, Integer sinceBuildId);
 
     /**   */
     CompletableFuture<List<BuildRef>> getRunningBuilds(@Nullable String branch);
@@ -107,8 +109,24 @@
     /**   */
     CompletableFuture<List<BuildRef>> getQueuedBuilds(@Nullable String branch);
 
-    default int[] getBuildNumbersFromHistory(String projectId, String branchNameForHist, Long cnt) {
-        return getFinishedBuilds(projectId, branchNameForHist, cnt, null).stream().mapToInt(BuildRef::getId).toArray();
+    /**
+     * @param projectId Suite ID (string without spaces).
+     * @param branchNameForHist Branch in TC identification.
+     * @return List of build numbers in historical order, recent builds coming last.
+     */
+    default int[] getBuildNumbersFromHistory(String projectId, String branchNameForHist) {
+        return getBuildNumbersFromHistory(projectId, branchNameForHist, null, null);
+    }
+
+    /**
+     * @param projectId Suite ID (string without spaces).
+     * @param branchNameForHist Branch in TC identification.
+     * @param sinceDate Since date.
+     * @param untilDate Until date.
+     * @return List of build numbers in historical order in date interval, recent builds coming last.
+     */
+    default int[] getBuildNumbersFromHistory(String projectId, String branchNameForHist, Date sinceDate, Date untilDate) {
+        return getFinishedBuilds(projectId, branchNameForHist, sinceDate, untilDate, null).stream().mapToInt(BuildRef::getId).toArray();
     }
 
     Build getBuild(String href);
@@ -142,13 +160,18 @@ default Build getBuild(int id) {
 
     ChangesList getChangesList(String href);
 
+    /**
+     * List of build's related issues.
+     *
+     * @param href IssuesUsagesList href.
+     */
     IssuesUsagesList getIssuesUsagesList(String href);
 
     /**
-     * Runs deep collection of all related statistics for particular build
+     * Runs deep collection of all related statistics for particular build.
      *
-     * @param build build from history with references to tests
-     * @return full context
+     * @param build Build from history with references to tests.
+     * @return Full context.
      */
     @Nonnull default MultBuildRunCtx loadTestsAndProblems(@Nonnull Build build) {
         MultBuildRunCtx ctx = new MultBuildRunCtx(build);
@@ -221,9 +244,9 @@ default SingleBuildRunCtx loadTestsAndProblems(@Nonnull Build build, @Deprecated
     CompletableFuture<File> downloadBuildLogZip(int id);
 
     /**
-     * Returns log analysis. Does not keep not zipped logs on disk
-     * @param buildId biuld ID
-     * @param ctx build results
+     * Returns log analysis. Does not keep not zipped logs on disk.
+     * @param buildId Build ID.
+     * @param ctx Build results.
      * @return
      */
     CompletableFuture<LogCheckResult> analyzeBuildLog(Integer buildId, SingleBuildRunCtx ctx);
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 2982f78..7df675c 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
@@ -24,6 +24,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Date;
 import java.util.List;
 import java.util.SortedMap;
 import java.util.TreeMap;
@@ -33,6 +34,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.Lock;
 import java.util.function.BiFunction;
 import java.util.function.Function;
@@ -244,7 +246,6 @@ public void init(String serverId) {
         return getOrCreateCacheV2Tx(ignCacheNme(BUILD_HIST_FINISHED_OR_FAILED));
     }
 
-
     /** {@inheritDoc} */
     @AutoProfiling
     @Override public CompletableFuture<List<BuildType>> getProjectSuites(String projectId) {
@@ -284,14 +285,12 @@ public void init(String serverId) {
 
     protected List<BuildRef> loadBuildHistory(IgniteCache<SuiteInBranch, Expirable<List<BuildRef>>> cache,
                                               int seconds,
-                                              Long cnt,
                                               SuiteInBranch key,
                                               BiFunction<SuiteInBranch, Integer, List<BuildRef>> realLoad) {
         @Nullable Expirable<List<BuildRef>> persistedBuilds = readBuildHistEntry(cache, key);
 
         if (persistedBuilds != null
-                && (isHistoryAgeLessThanSecs(key, seconds, persistedBuilds)
-                && (cnt == null || persistedBuilds.hasCounterGreaterThan(cnt)))) {
+                && (isHistoryAgeLessThanSecs(key, seconds, persistedBuilds))) {
             ObjectInterner.internFields(persistedBuilds);
 
             return persistedBuilds.getData();
@@ -302,8 +301,7 @@ public void init(String serverId) {
         try {
             if (!noLocks) {
                 if (persistedBuilds != null
-                        && (isHistoryAgeLessThanSecs(key, seconds, persistedBuilds)
-                        && (cnt == null || persistedBuilds.hasCounterGreaterThan(cnt)))) {
+                        && (isHistoryAgeLessThanSecs(key, seconds, persistedBuilds))) {
                     ObjectInterner.internFields(persistedBuilds);
 
                     return persistedBuilds.getData();
@@ -342,7 +340,7 @@ public void init(String serverId) {
             final List<BuildRef> persistedList = persistedBuilds != null ? persistedBuilds.getData() : null;
             final List<BuildRef> buildRefs = mergeHistoryMaps(persistedList, dataFromRest);
 
-            final Expirable<List<BuildRef>> newVal = new Expirable<>(0L, buildRefs.size(), buildRefs);
+            final Expirable<List<BuildRef>> newVal = new Expirable<>(0L, buildRefs);
 
             if (persistedList != null && persistedList.equals(buildRefs)) {
                 lastQueuedHistory.put(key, System.currentTimeMillis());
@@ -401,19 +399,155 @@ private boolean isHistoryAgeLessThanSecs(SuiteInBranch key, int seconds, Expirab
     @AutoProfiling
     @Override public List<BuildRef> getFinishedBuilds(String projectId,
                                                       String branch,
-                                                      Long cnt,
+                                                      Date sinceDate,
+                                                      Date untilDate,
                                                       Integer ignored) {
         final SuiteInBranch suiteInBranch = new SuiteInBranch(projectId, branch);
 
-        List<BuildRef> buildRefs = loadBuildHistory(buildHistCache(), 90, cnt, suiteInBranch,
-            (key, sinceBuildId) -> teamcity.getFinishedBuilds(projectId, branch, cnt, sinceBuildId));
+        final List<BuildRef> buildsFromRest = new ArrayList<>();
 
-        if (cnt == null)
-            return buildRefs;
+        List<BuildRef> buildRefs = loadBuildHistory(buildHistCache(), 90, suiteInBranch,
+            (key, sinceBuildId) -> {
+            buildsFromRest.addAll(teamcity.getFinishedBuilds(projectId, branch, sinceDate, untilDate, sinceBuildId));
+
+            return buildsFromRest;
+        });
+
+        if (sinceDate != null || untilDate != null) {
+            if (!buildsFromRest.isEmpty() && sinceDate != null){
+                int firstBuildId = buildRefs.indexOf(buildsFromRest.get(buildsFromRest.size() - 1));
+
+                if (firstBuildId == 0)
+                    return buildsFromRest;
+
+                int prevFirstBuildId = firstBuildId - 1;
+
+                Build prevFirstBuild = getBuild((buildRefs.get(prevFirstBuildId).href));
+
+                if (prevFirstBuild != null
+                    && !prevFirstBuild.isFakeStub()
+                    && prevFirstBuild.getStartDate().before(sinceDate))
+                    return buildsFromRest;
+            }
+
+            int idSince = 0;
+            int idUntil = buildRefs.size() - 1;
+
+            if (sinceDate != null) {
+                idSince = binarySearchDate(buildRefs, 0, buildRefs.size(), sinceDate, true);
+                idSince = idSince == -2 ? 0 : idSince;
+            }
+
+            if (untilDate != null) {
+                idUntil = idSince < 0 ? -1 : binarySearchDate(buildRefs, idSince, buildRefs.size(), untilDate, false);
+                idUntil = idUntil == -2 ? buildRefs.size() - 1 : idUntil;
+            }
+
+            if (idSince == -1 || idUntil == -1)
+                return Collections.emptyList();
+            else if (idSince == -3 || idUntil == -3) {
+                AtomicBoolean stopFilter = new AtomicBoolean();
+                AtomicBoolean addBuild = new AtomicBoolean();
+
+                    return buildRefs.stream()
+                        .filter(b -> {
+                            if (stopFilter.get())
+                                return addBuild.get();
+
+                            Build build = getBuild(b.href);
+
+                            if (build == null || build.isFakeStub())
+                                return false;
+
+                            Date date = build.getFinishDate();
+
+                            if (sinceDate != null && untilDate != null)
+                                return (date.after(sinceDate) || date.equals(sinceDate)) &&
+                                    (date.before(untilDate) || date.equals(untilDate));
+                            else if (sinceDate != null) {
+                                if (date.after(sinceDate) || date.equals(sinceDate)) {
+                                    stopFilter.set(true);
+                                    addBuild.set(true);
+
+                                    return true;
+                                }
+
+                                return false;
+                            }
+                            else {
+                                if (date.after(untilDate)) {
+                                    stopFilter.set(true);
+                                    addBuild.set(false);
+
+                                    return false;
+                                }
+
+                                return true;
+                            }
+                        })
+                        .collect(Collectors.toList());
+                }
+            else
+                return buildRefs.subList(idSince, idUntil + 1);
+        }
 
-        return buildRefs.stream()
-            .skip(cnt < buildRefs.size() ? buildRefs.size() - cnt : 0)
-            .collect(Collectors.toList());
+        return buildRefs;
+    }
+
+    /**
+     * @param buildRefs Build refs list.
+     * @param fromIdx From index.
+     * @param toIdx To index.
+     * @param key Key.
+     * @param since {@code true} If key is sinceDate, {@code false} is untilDate.
+     *
+     * @return {@value >= 0} Build id from list with min interval between key. If since {@code true}, min interval
+     * between key and same day or later. If since {@code false}, min interval between key and same day or earlier;
+     * {@value -1} If sinceDate after last list element date or untilDate before first list element;
+     * {@value -2} If sinceDate before first list element or untilDate after last list element;
+     * {@value -3} If method get null or fake stub build.
+     */
+    private int binarySearchDate(List<BuildRef> buildRefs, int fromIdx, int toIdx, Date key, boolean since){
+        int low = fromIdx;
+        int high = toIdx - 1;
+        long minDiff = key.getTime();
+        int minDiffId = since ? low : high;
+        long temp;
+        Build highBuild = getBuild(buildRefs.get(high).href);
+        Build lowBuild = getBuild(buildRefs.get(low).href);
+
+        if (highBuild != null && !highBuild.isFakeStub()){
+            if (highBuild.getStartDate().before(key))
+                return since ? -1 : -2;
+        }
+
+        if (lowBuild != null && !lowBuild.isFakeStub()){
+            if (lowBuild.getStartDate().after(key))
+                return since ? -2 : -1;
+        }
+
+        while (low <= high) {
+            int mid = (low + high) >>> 1;
+            Build midVal = getBuild(buildRefs.get(mid).href);
+
+            if (midVal != null && !midVal.isFakeStub()) {
+                if (midVal.getStartDate().after(key))
+                    high = mid - 1;
+                else if (midVal.getStartDate().before(key))
+                    low = mid + 1;
+                else
+                    return mid;
+
+                temp = midVal.getStartDate().getTime() - key.getTime();
+
+                if ((temp > 0 == since) && (Math.abs(temp) < minDiff)) {
+                    minDiff = Math.abs(temp);
+                    minDiffId = mid;
+                }
+            } else
+                return -3;
+        }
+        return minDiffId;
     }
 
     @NotNull
@@ -434,12 +568,11 @@ private boolean isHistoryAgeLessThanSecs(SuiteInBranch key, int seconds, Expirab
     @AutoProfiling
     @Override public List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId,
                                                                         String branch,
-                                                                        Long cnt,
                                                                         Integer ignored) {
         final SuiteInBranch suiteInBranch = new SuiteInBranch(projectId, branch);
 
-        return loadBuildHistory(buildHistIncFailedCache(), 91, cnt, suiteInBranch,
-            (key, sinceBuildId) -> teamcity.getFinishedBuildsIncludeSnDepFailed(projectId, branch, cnt, sinceBuildId));
+        return loadBuildHistory(buildHistIncFailedCache(), 91, suiteInBranch,
+            (key, sinceBuildId) -> teamcity.getFinishedBuildsIncludeSnDepFailed(projectId, branch, sinceBuildId));
     }
 
 
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 28ece3c..23b8c1b 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
@@ -29,7 +29,9 @@
 import java.io.InputStreamReader;
 import java.io.StringReader;
 import java.io.UncheckedIOException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.Properties;
 import java.util.concurrent.CompletableFuture;
@@ -466,25 +468,43 @@ public String basicAuthToken() {
     }
 
     private List<BuildRef> getBuildHistory(@Nullable String buildTypeId,
-                                           @Nullable String branchName,
-                                           boolean dfltFilter,
-                                           @Nullable String state,
-                                           @Nullable Long cnt,
-                                           @Nullable Integer sinceBuildId) {
-        String btFilter = isNullOrEmpty(buildTypeId) ? "" : ",buildType:" + buildTypeId;
+        @Nullable String branchName,
+        boolean dfltFilter,
+        @Nullable String state){
+
+        return getBuildHistory(buildTypeId, branchName, dfltFilter, state, null, null, null);
+    }
+
+    private List<BuildRef> getBuildHistory(@Nullable String buildTypeId,
+        @Nullable String branchName,
+        boolean dfltFilter,
+        @Nullable String state,
+        @Nullable Date sinceDate,
+        @Nullable Date untilDate,
+        @Nullable Integer sinceBuildId)  {
+        String btFilter = isNullOrEmpty(buildTypeId) ? "" : ",buildType:" + buildTypeId + "";
         String stateFilter = isNullOrEmpty(state) ? "" : (",state:" + state);
-        String branchFilter = isNullOrEmpty(branchName) ? "" : ",branch:" + branchName;
+        String branchFilter = isNullOrEmpty(branchName) ? "" :",branch:" + branchName;
+        String sinceDateFilter = sinceDate == null ? "" : ",sinceDate:" + getDateYyyyMmDdTHhMmSsZ(sinceDate);
+        String untilDateFilter = untilDate == null ? "" : ",untilDate:" + getDateYyyyMmDdTHhMmSsZ(untilDate);
         String buildNoFilter = sinceBuildId == null ? "" : ",sinceBuild:(id:" + sinceBuildId + ")";
-        long cntFilter = cnt == null ? DEFAULT_BUILDS_COUNT : cnt;
 
         return sendGetXmlParseJaxb(host + "app/rest/latest/builds"
-                + "?locator="
-                + "defaultFilter:" + dfltFilter
-                + btFilter
-                + stateFilter
-                + branchFilter
-                + buildNoFilter
-                + ",count:" + cntFilter, Builds.class).getBuildsNonNull();
+            + "?locator="
+            + "defaultFilter:" + dfltFilter
+            + btFilter
+            + stateFilter
+            + branchFilter
+            + buildNoFilter
+            + ",count:" + DEFAULT_BUILDS_COUNT
+            + sinceDateFilter
+            + untilDateFilter, Builds.class).getBuildsNonNull();
+    }
+
+    public String getDateYyyyMmDdTHhMmSsZ(Date date){
+        return new SimpleDateFormat("yyyyMMdd'T'HHmmssZ")
+            .format(date)
+            .replace("+", "%2B");
     }
 
     @AutoProfiling
@@ -543,6 +563,7 @@ public ChangesList getChangesList(String href) {
         return getJaxbUsingHref(href, ChangesList.class);
     }
 
+    /** {@inheritDoc} */
     @Override
     @AutoProfiling
     public IssuesUsagesList getIssuesUsagesList(String href) { return getJaxbUsingHref(href, IssuesUsagesList.class); }
@@ -559,7 +580,7 @@ public ChangesList getChangesList(String href) {
     @Override public List<BuildRef> getFinishedBuilds(String projectId,
         String branch) {
 
-        return getFinishedBuilds(projectId, branch, null, null);
+        return getFinishedBuilds(projectId, branch, null, null, null);
     }
 
     /** {@inheritDoc} */
@@ -567,13 +588,15 @@ public ChangesList getChangesList(String href) {
     @AutoProfiling
     public List<BuildRef> getFinishedBuilds(String projectId,
                                             String branch,
-                                            Long cnt,
+                                            Date sinceDate,
+                                            Date untilDate,
                                             @Nullable Integer sinceBuildId) {
         List<BuildRef> finished = getBuildHistory(projectId,
             UrlUtil.escape(branch),
             true,
             null,
-            cnt,
+            sinceDate,
+            untilDate,
             sinceBuildId);
 
         return finished.stream().filter(BuildRef::isNotCancelled).collect(Collectors.toList());
@@ -582,13 +605,13 @@ public ChangesList getChangesList(String href) {
     /** {@inheritDoc} */
     @Override
     @AutoProfiling public List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch) {
-        return getBuildsInState(projectId, branch, BuildRef.STATE_FINISHED, null, null);
+        return getBuildsInState(projectId, branch, BuildRef.STATE_FINISHED, null);
     }
 
     /** {@inheritDoc} */
     @Override
-    @AutoProfiling public List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch, Long cnt, Integer sinceBuildId) {
-        return getBuildsInState(projectId, branch, BuildRef.STATE_FINISHED, cnt, sinceBuildId);
+    @AutoProfiling public List<BuildRef> getFinishedBuildsIncludeSnDepFailed(String projectId, String branch, Integer sinceBuildId) {
+        return getBuildsInState(projectId, branch, BuildRef.STATE_FINISHED, sinceBuildId);
     }
 
     /** {@inheritDoc} */
@@ -607,12 +630,14 @@ public ChangesList getChangesList(String href) {
             @Nullable final String projectId,
             @Nullable final String branch,
             @Nonnull final String state,
-            @Nullable final Long cnt,
             @Nullable final Integer sinceBuildId) {
         List<BuildRef> finished = getBuildHistory(projectId,
             UrlUtil.escape(branch),
             false,
-            state, cnt, sinceBuildId);
+            state,
+            null,
+            null,
+            sinceBuildId);
         return finished.stream().filter(BuildRef::isNotCancelled).collect(Collectors.toList());
     }
 
@@ -624,7 +649,7 @@ public ChangesList getChangesList(String href) {
             @Nullable final String branch,
             @Nonnull final String state) {
 
-        return getBuildsInState(projectId, branch, state, null, null);
+        return getBuildsInState(projectId, branch, state, null);
     }
 
     @Override
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 32d176a..16a9f2b 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,16 +27,14 @@
 public class Expirable<D> {
     private final long ts;
     private final D data;
-    private final long cnt;
 
     public Expirable(D data) {
-        this(System.currentTimeMillis(), 1, data);
+        this(System.currentTimeMillis(), data);
     }
 
-    public Expirable(long ts, long cnt, D data) {
+    public Expirable(long ts, D data) {
         this.ts = ts;
         this.data = data;
-        this.cnt = cnt;
     }
 
     public long getTs() {
@@ -47,10 +45,6 @@ public D getData() {
         return data;
     }
 
-    public long getCnt(){
-        return cnt;
-    }
-
     public long getAgeMs() {
         return System.currentTimeMillis() - ts;
     }
@@ -58,8 +52,4 @@ public long getAgeMs() {
     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/runners/IgniteTeamcityHelperRunnerExample.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/IgniteTeamcityHelperRunnerExample.java
index bdfc34a..162fb24 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/IgniteTeamcityHelperRunnerExample.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/IgniteTeamcityHelperRunnerExample.java
@@ -57,7 +57,7 @@ public static void main(String[] args) throws Exception {
                 if (bt.getName().toLowerCase().contains("pds")
                     // || bt.getName().toLowerCase().contains("cache")
                     ) {
-                    int[] ints = helper.getBuildNumbersFromHistory(bt.getName(), branchNameForHist, null);
+                    int[] ints = helper.getBuildNumbersFromHistory(bt.getName(), branchNameForHist);
 
                     List<CompletableFuture<File>> fileFutList = helper.standardProcessLogs(ints);
                     List<File> collect = getFuturesResults(fileFutList);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueRef.java
index 09ea91a..bd96c57 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueRef.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueRef.java
@@ -29,6 +29,7 @@
     @XmlAttribute public String id;
     @XmlAttribute public String url;
 
+    /** {@inheritDoc} */
     @Override public String toString() {
         return "IssueRef{" +
             "id='" + id + '\'' +
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueUsage.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueUsage.java
index ae57c4d..190d649 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueUsage.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssueUsage.java
@@ -37,6 +37,9 @@
     @XmlElement(name = "changes")
     private ChangesList changesList;
 
+    /**
+     * Get issue.
+     */
     public IssueRef getIssue(){
         return issue;
     }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssuesUsagesList.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssuesUsagesList.java
index 1212ab6..836783e 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssuesUsagesList.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/issues/IssuesUsagesList.java
@@ -41,6 +41,7 @@
         return issuesUsages == null ? Collections.emptyList() : issuesUsages;
     }
 
+    /** {@inheritDoc} */
     @Override public String toString() {
         return "IssuesUsagesList{" +
             "issuesUsages=" + issuesUsages +
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
index 4bcc844..8a428a5 100644
--- 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
@@ -17,9 +17,12 @@
 
 package org.apache.ignite.ci.web.model.current;
 
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -27,8 +30,7 @@
 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.TestOccurrencesRef;
 import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrence;
 import org.apache.ignite.ci.util.TimeUtil;
 import org.apache.ignite.ci.web.IBackgroundUpdatable;
@@ -37,55 +39,72 @@
  * Summary of build statistics.
  */
 public class BuildStatisticsSummary extends UpdateInfo implements IBackgroundUpdatable {
+    /** Short problem names. */
+    public static final String TOTAL = "TOTAL";
+
+    private static Map<String, String> shortProblemNames = new HashMap<>();
+
+    static {
+        shortProblemNames.put(TOTAL, "TT");
+        shortProblemNames.put(ProblemOccurrence.TC_EXECUTION_TIMEOUT, "ET");
+        shortProblemNames.put(ProblemOccurrence.TC_JVM_CRASH, "JC");
+        shortProblemNames.put(ProblemOccurrence.TC_OOME, "OO");
+        shortProblemNames.put(ProblemOccurrence.TC_EXIT_CODE, "EC");
+    }
+
     /** Build with test and problems references. */
-    public Build build;
+    public Integer buildId;
 
-    /** List of problem occurrences. */
-    private List<ProblemOccurrence> problems;
+    /** Build start date. */
+    public String startDate;
 
-    /** List of related issues. */
-    public List<IssueRef> relatedIssues;
+    /** Test occurrences. */
+    public TestOccurrencesRef testOccurrences;
+
+    /** List of problem occurrences. */
+    private List<ProblemOccurrence> problemOccurrenceList;
 
     /** 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;
+    public Map<String, Long> totalProblems;
 
-    /** Build problems count. */
-    public long problemsCount;
+    /** Is fake stub. */
+    public boolean isFakeStub;
 
-    public BuildStatisticsSummary(Build build){
-        this.build = build;
+    /**
+     * @param buildId Build id.
+     */
+    public BuildStatisticsSummary(Integer buildId){
+        this.buildId = buildId;
     }
 
     /** Initialize build statistics. */
     public void initialize(@Nonnull final ITeamcity teamcity) {
+        Build build = teamcity.getBuild(buildId);
 
-        if (build.isFakeStub())
+        isFakeStub = build.isFakeStub();
+
+        if (isFakeStub)
             return;
 
-        relatedIssues = teamcity.getIssuesUsagesList(build.relatedIssuesRef.href).getIssuesUsagesNonNull().stream()
-            .map(IssueUsage::getIssue).collect(Collectors.toList());
+        DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy'T'HH:mm:ss");
+        dateFormat.format(build.getFinishDate());
+        startDate = dateFormat.format(build.getStartDate());
+
+        testOccurrences = build.testOccurrences;
 
         durationPrintable = TimeUtil
             .millisToDurationPrintable(build.getFinishDate().getTime() - build.getStartDate().getTime());
 
-        snapshotDependencies = getSnapshotDependencies(teamcity, build);
-
-        problems = getProblems(teamcity);
+        List<BuildRef> snapshotDependencies = getSnapshotDependencies(teamcity, build);
 
-        shortRes = getShortRes();
+        List<BuildRef> snapshotDependenciesWithProblems = getBuildsWithProblems(snapshotDependencies);
 
-        fullRes = getFullRes();
+        problemOccurrenceList = getProblems(teamcity, snapshotDependenciesWithProblems);
 
-        problemsCount = getAllProblemsCount(null);
+        totalProblems = getRes();
     }
 
     private long getExecutionTimeoutCount(String buildTypeId) {
@@ -104,36 +123,15 @@ 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();
-
+    private List<ProblemOccurrence> getProblems(@Nonnull final ITeamcity teamcity, List<BuildRef> builds){
         List<ProblemOccurrence> problemOccurrences = new ArrayList<>();
 
-        List<BuildRef> snapshotDependencyWithProblems = getSnapshotDependenciesWithProblems();
-
-        for (BuildRef buildRef : snapshotDependencyWithProblems)
+        for (BuildRef buildRef : builds)
             problemOccurrences.addAll(teamcity
                 .getProblems(teamcity.getBuild(buildRef.href))
                 .getProblemsNonNull());
@@ -162,13 +160,10 @@ private long getAllProblemsCount(String buildTypeId) {
     }
 
     /**
-     * Snapshot-dependencies without status "Success".
+     * Builds without status "Success".
      */
-    private List<BuildRef> getSnapshotDependenciesWithProblems(){
-        if (snapshotDependencies == null)
-            return Collections.emptyList();
-
-        return snapshotDependencies.stream()
+    private List<BuildRef> getBuildsWithProblems(List<BuildRef> builds){
+        return builds.stream()
             .filter(b -> !b.isSuccess())
             .collect(Collectors.toList());
     }
@@ -177,87 +172,40 @@ private long getAllProblemsCount(String buildTypeId) {
      * @param buildTypeId Build type id (if null - for all problems).
      */
     private Stream<ProblemOccurrence> getProblemsStream(String buildTypeId) {
-        if (problems == null)
+        if (problemOccurrenceList == null)
             return Stream.empty();
 
-        return problems.stream()
+        return problemOccurrenceList.stream()
             .filter(Objects::nonNull)
-            .filter(p -> {
-                    if (buildTypeId == null)
-                        return true;
-                    if (p.buildRef == null && buildTypeId == null)
-                        return true;
-                    if (p.buildRef != null && buildTypeId.equals(p.buildRef.buildTypeId))
-                        return true;
-                    return false;
-                }
-            );
+            .filter(p -> buildTypeId == null || buildTypeId.equals(p.buildRef.buildTypeId));
     }
 
     /**
-     * Full build run result (snapshot-dependencies printable result).
+     * Short build run result (without snapshot-dependencies 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());
+    private Map<String, Long> getRes(){
+        return getBuildTypeProblemsCount(null);
     }
 
-    /**
-     * Short build run result (without snapshot-dependencies printable result).
-     *
-     * @return printable result;
-     */
-    private String getShortRes(){
-        return getRes(null);
-    }
 
     /**
-     * Build run result for buildTypeId.
+     * BuildType problems count (EXECUTION TIMEOUT, JVM CRASH, OOMe, EXIT CODE, TOTAL PROBLEMS COUNT).
      *
-     * @param buildTypeId buildTypeId.
-     *
-     * @return printable result.
+     * @param buildTypeId Build type id.
      */
-    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));
+    private Map<String, Long> getBuildTypeProblemsCount(String buildTypeId){
+        Map<String, Long> occurrences = new HashMap<>();
 
-        res.insert(0, (buildTypeId != null ? buildTypeId : "TOTAL") + " [" + getAllProblemsCount(buildTypeId) + "]"
-            + (res.length() != 0 ? ": " : " "));
+        occurrences.put(shortProblemNames.get(ProblemOccurrence.TC_EXECUTION_TIMEOUT),
+            getExecutionTimeoutCount(buildTypeId));
+        occurrences.put(shortProblemNames.get(ProblemOccurrence.TC_JVM_CRASH), getJvmCrashProblemCount(buildTypeId));
+        occurrences.put(shortProblemNames.get(ProblemOccurrence.TC_OOME), getOomeProblemCount(buildTypeId));
+        occurrences.put(shortProblemNames.get(ProblemOccurrence.TC_EXIT_CODE), getExitCodeProblemsCount(buildTypeId));
+        occurrences.put(shortProblemNames.get(TOTAL), occurrences.values().stream().mapToLong(Long::longValue).sum());
 
-        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 + "]" : "");
-        }
+        return occurrences;
     }
 
     /** {@inheritDoc} */
@@ -275,20 +223,18 @@ private void addKnownProblemCnt(StringBuilder res, String nme, long execToCnt) {
 
         BuildStatisticsSummary that = (BuildStatisticsSummary)o;
 
-        return problemsCount == that.problemsCount &&
-            Objects.equals(build, that.build) &&
-            Objects.equals(problems, that.problems) &&
-            Objects.equals(relatedIssues, that.relatedIssues) &&
+        return isFakeStub == that.isFakeStub &&
+            Objects.equals(buildId, that.buildId) &&
+            Objects.equals(startDate, that.startDate) &&
+            Objects.equals(testOccurrences, that.testOccurrences) &&
+            Objects.equals(problemOccurrenceList, that.problemOccurrenceList) &&
             Objects.equals(durationPrintable, that.durationPrintable) &&
-            Objects.equals(getShortRes(), that.getShortRes()) &&
-            Objects.equals(getFullRes(), that.getFullRes()) &&
-            Objects.equals(snapshotDependencies, that.snapshotDependencies);
+            Objects.equals(totalProblems, that.totalProblems);
     }
 
     /** {@inheritDoc} */
     @Override public int hashCode() {
-
-        return Objects.hash(build, problems, relatedIssues, durationPrintable, getShortRes(), getFullRes(),
-            snapshotDependencies, problemsCount);
+        return Objects.hash(buildId, startDate, testOccurrences, problemOccurrenceList,
+            durationPrintable, totalProblems, isFakeStub);
     }
-}
\ No newline at end of file
+}
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 22ebd16..28dd39a 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
@@ -49,7 +49,11 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.Date;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
 
@@ -157,13 +161,14 @@ public TestFailuresSummary getBuildTestFails(
         @Nullable @QueryParam("server") String server,
         @Nullable @QueryParam("buildType") String buildType,
         @Nullable @QueryParam("branch") String branch,
-        @Nullable @QueryParam("count") Long count)
+        @Nullable @QueryParam("sinceDate") String sinceDate,
+        @Nullable @QueryParam("untilDate") String untilDate)
         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;
+        Date sinceDateFilter = isNullOrEmpty(sinceDate) ? null : dateParse(sinceDate);
+        Date untilDateFilter = isNullOrEmpty(untilDate) ? null : dateParse(untilDate);
 
         final BackgroundUpdater updater = CtxListener.getBackgroundUpdater(context);
 
@@ -173,7 +178,7 @@ public TestFailuresSummary getBuildTestFails(
 
         try (IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, prov)) {
 
-            int[] finishedBuilds = teamcity.getBuildNumbersFromHistory(buildTypeId, branchName, cnt);
+            int[] finishedBuilds = teamcity.getBuildNumbersFromHistory(buildTypeId, branchName, sinceDateFilter, untilDateFilter);
 
             List<BuildStatisticsSummary> buildsStatistics = new ArrayList<>();
 
@@ -189,7 +194,7 @@ public TestFailuresSummary getBuildTestFails(
                     BUILDS_STATISTICS_SUMMARY_CACHE_NAME, prov, param,
                     (k) -> getBuildStatisticsSummaryNoCache(srvId, buildId), false);
 
-                if (!buildsStatistic.build.isFakeStub())
+                if (!buildsStatistic.isFakeStub)
                     buildsStatistics.add(buildsStatistic);
             }
 
@@ -197,8 +202,18 @@ public TestFailuresSummary getBuildTestFails(
         }
     }
 
-    private BuildStatisticsSummary getBuildStatisticsSummaryNoCache(String server, int buildId) {
+    private Date dateParse(String date){
+        DateFormat dateFormat = new SimpleDateFormat("ddMMyyyyHHmmss");
+
+        try {
+            return dateFormat.parse(date);
+        }
+        catch (ParseException e) {
+            return null;
+        }
+    }
 
+    private BuildStatisticsSummary getBuildStatisticsSummaryNoCache(String server, int buildId) {
         String srvId = isNullOrEmpty(server) ? "apache" : server;
 
         final ITcHelper tcHelper = CtxListener.getTcHelper(context);
@@ -207,10 +222,11 @@ private BuildStatisticsSummary getBuildStatisticsSummaryNoCache(String server, i
 
         try (IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, creds)) {
 
-            BuildStatisticsSummary stat = new BuildStatisticsSummary(teamcity.getBuild(buildId));
+            BuildStatisticsSummary stat = new BuildStatisticsSummary(buildId);
+
             stat.initialize(teamcity);
 
             return stat;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/ignite-tc-helper-web/src/main/webapp/comparison.html b/ignite-tc-helper-web/src/main/webapp/comparison.html
new file mode 100644
index 0000000..909ee44
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/webapp/comparison.html
@@ -0,0 +1,348 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Ignite Teamcity - comparison master's branch in the date interval</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 type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
+    <script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
+    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
+    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
+    <script src="js/common-1.6.js"></script>
+    <script src="https://d3js.org/d3.v4.min.js"></script>
+</head>
+<body>
+<br>
+<br>
+<table class="stat"  width="100%">
+    <tr>
+        <th class="section"  width="15%">DATE INTERVAL</th>
+        <th  width="5%"></th>
+        <th style="text-align: center;" width="40%"><input type='text' name='daterange1'/></th>
+        <th style="text-align: center;" width="40%"><input type='text' name='daterange2'/></th>
+    </tr>
+    <tr><td class="section">TESTS</td><td></td><td></td></tr>
+    <tr><td class="field">COUNT</td>
+        <td><img id="clickGraphCount" src='/img/browser.png'></td>
+        <td class="data1" id="Count1" title="min - median - max"></td>
+        <td class="data2" id="Count2" title="min - median - max"></td>
+    </tr>
+    <tr id="showGraphCount" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphCount1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphCount2" width="500" height="200"></svg></td>
+    </tr>
+    <tr><td class="field">PASSED</td>
+        <td><img id="clickGraphPassed" src='/img/browser.png'></td>
+        <td class="data1" id="Passed1" title="min - median - max"></td>
+        <td class="data2" id="Passed2" title="min - median - max"></td>
+    </tr>
+    <tr id="showGraphPassed" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphPassed1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphPassed2" width="500" height="200"></svg></td>
+    </tr>
+    <tr><td class="field">FAILED</td>
+        <td><img id="clickGraphFailed" src='/img/browser.png'></td>
+        <td class="data1" id="Failed1" title="min - median - max"></td>
+        <td class="data2" id="Failed2" title="min - median - max"></td>
+    </tr>
+    <tr id="showGraphFailed" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphFailed1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphFailed2" width="500" height="200"></svg></td>
+    </tr>
+    <tr><td class="field">IGNORED</td>
+        <td><img id="clickGraphIgnored" src='/img/browser.png'></td>
+        <td class="data1" id="Ignored1" title="min - median - max"></td>
+        <td class="data2" id="Ignored2" title="min - median - max"></td>
+    </tr>
+    <tr id="showGraphIgnored" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphIgnored1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphIgnored2" width="500" height="200"></svg></td>
+    </tr>
+    <tr><td class="field">MUTED</td>
+        <td><img id="clickGraphMuted" src='/img/browser.png'></td>
+        <td class="data1" id="Muted1" title="min - median - max"></td>
+        <td class="data2" id="Muted2" title="min - median - max"></td></tr>
+    <tr id="showGraphMuted" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphMuted1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphMuted2" width="500" height="200"></svg></td>
+    </tr>
+    <tr><td class="section">PROBLEMS</td><td></td><td></td></tr>
+    <tr style="display: none;"><td></td><td></td><td></td></tr>
+    <tr><td class="field">TOTAL</td>
+        <td><img id="clickGraphTT" src='/img/browser.png'></td>
+        <td class="data1" id="TT1" title="min - median - max"></td>
+        <td class="data2" id="TT2" title="min - median - max"></td></tr>
+    <tr id="showGraphTT" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphTT1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphTT2" width="500" height="200"></svg></td>
+    </tr>
+    <tr><td class="field">EXECUTION TIMEOUT</td>
+        <td><img id="clickGraphET" src='/img/browser.png'></td>
+        <td class="data1" id="ET1" title="min - median - max"></td>
+        <td class="data2" id="ET2" title="min - median - max"></td>
+    </tr>
+    <tr id="showGraphET" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphET1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphET2" width="500" height="200"></svg></td>
+    </tr>
+    <tr><td class="field">JVM CRASH</td>
+        <td><img id="clickGraphJC" src='/img/browser.png'></td>
+        <td class="data1" id="JC1" title="min - median - max"></td>
+        <td class="data2" id="JC2" title="min - median - max"></td>
+    </tr>
+    <tr id="showGraphJC" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphJC1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphJC2" width="500" height="200"></svg></td>
+    </tr>
+    <tr><td class="field">OOME</td>
+        <td><img id="clickGraphOO" src='/img/browser.png'></td>
+        <td class="data1" id="OO1" title="min - median - max"></td>
+        <td class="data2" id="OO2" title="min - median - max"></td>
+    </tr>
+    <tr id="showGraphOO" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphOO1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphOO2" width="500" height="200"></svg></td>
+    </tr>
+    <tr><td class="field">EXIT CODE</td>
+        <td><img id="clickGraphEC" src='/img/browser.png'></td>
+        <td class="data1" id="EC1" title="min - median - max"></td>
+        <td class="data2" id="EC2" title="min - median - max"></td>
+    </tr>
+    <tr id="showGraphEC" style="display: none;"><td></td>
+        <td></td>
+        <td style="text-align: center;"><svg id="graphEC1" width="500" height="200"></svg></td>
+        <td style="text-align: center;"><svg id="graphEC2" width="500" height="200"></svg></td>
+    </tr>
+</table><br>
+<div id="version"></div>
+<script>
+    let oneWeekAgo = new Date();
+    let twoWeekAgo = new Date();
+    let g_updTimer = null;
+
+    oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
+    twoWeekAgo.setDate(twoWeekAgo.getDate() - 14);
+
+    function dateRangePickerParam(data1, data2) {
+        return {
+            "maxSpan": { "days": 7 },
+            "locale": {
+                "format": "DD/MM/YYYY", "separator": " - ", "applyLabel": "Apply", "cancelLabel": "Cancel",
+                "fromLabel": "From", "toLabel": "To", "customRangeLabel": "Custom", "weekLabel": "W",
+                "daysOfWeek": [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ],
+                "monthNames": [ "January", "February", "March", "April", "May", "June", "July",
+                    "August", "September", "October", "November", "December" ],
+                "firstDay": 1
+            },
+            "startDate": moment(data1).format("DD-MM-YYYY"), "endDate": moment(data2).format("DD-MM-YYYY")
+        }
+    }
+
+    const prOcc = ["TT", "ET", "JC", "OO", "EC"];
+    const tOcc = ["Count", "Passed", "Failed", "Ignored", "Muted"];
+
+    function getMinMaxMedian(arr) {
+        let newArr = arr.slice();
+        newArr = newArr.sort(function(a, b){ return a - b; });
+        let i = newArr.length / 2;
+        let result = {};
+        result.median = i % 1 == 0 ? (newArr[i - 1] + newArr[i]) / 2 : newArr[Math.floor(i)];
+        result.min = newArr[0];
+        result.max = newArr[newArr.length - 1];
+        return result;
+    }
+
+    function parseMedian(string) {
+        return parseFloat(string.substring(string.indexOf("-") + 2, string.lastIndexOf("-") - 1));
+    }
+
+    function printStatistics(num, map) {
+        const parseTime = d3.timeParse("%d-%m-%YT%H:%M:%S");
+
+        let statistics = {};
+        let dates = [];
+
+        for (let i = 0; i < prOcc.length; i++) {
+            statistics[prOcc[i]] = [];
+            statistics[tOcc[i]] = [];
+        }
+
+        for (let j = 0; j < map.length; j++) {
+            dates[j] = parseTime(map[j].startDate);
+
+            for (let i = 0; i < prOcc.length; i++) {
+                statistics[prOcc[i]][j] = map[j].totalProblems[prOcc[i]];
+                statistics[tOcc[i]][j] = map[j].testOccurrences[tOcc[i].toLowerCase()];
+            }
+        }
+
+        const anotherNum = (num === 1) ? 2 : 1;
+        let anotherMedian;
+        let result = {};
+
+        for (let i = 0; i < prOcc.length; i++) {
+
+            document.getElementById(prOcc[i] + 1).style.background = null;
+            document.getElementById(prOcc[i] + 2).style.background = null;
+
+            result = getMinMaxMedian(statistics[prOcc[i]]);
+            anotherMedian = parseMedian(document.getElementById(prOcc[i] + anotherNum).innerHTML);
+
+            $('#' + prOcc[i] + num).html(result.min + " - " + result.median +  " - " + result.max);
+
+            if (!isNaN(anotherMedian)){
+                if (result.median > anotherMedian){
+                    document.getElementById(prOcc[i] + num).style.backgroundColor = "#ffeee9";
+                    document.getElementById(prOcc[i] + anotherNum).style.backgroundColor = "#e5ffe8";
+                } else if (result.median < anotherMedian){
+                    document.getElementById(prOcc[i] + anotherNum).style.backgroundColor = "#ffeee9";
+                    document.getElementById(prOcc[i] + num).style.backgroundColor = "#e5ffe8";
+                }
+            }
+
+            result = getMinMaxMedian(statistics[tOcc[i]]);
+
+            $('#' + tOcc[i] + num).html(result.min + " - " + result.median + " - " + result.max);
+
+            drawGraph(prOcc[i], num, dates, statistics[prOcc[i]], prOcc[i]);
+            drawGraph(tOcc[i], num, dates, statistics[tOcc[i]], tOcc[i]);
+        }
+    }
+
+    $(document).ready(function() {
+        loadData(1, moment(oneWeekAgo).format("DDMMYYYY"), moment().format("DDMMYYYY"));
+        loadData(2, moment(twoWeekAgo).format("DDMMYYYY"), moment(oneWeekAgo).format("DDMMYYYY"));
+
+        $.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;
+        }
+
+        if (g_updTimer == null) {
+            g_updTimer = setTimeout(tstTimeout, 3200);
+        }
+    }
+
+    function loadGif(num) {
+        $('.data' + num).html("<img src='/img/loading.gif' width=15px height=15px>");
+    }
+
+    function loadData(num, sinceDate, untilDate) {
+        loadGif(num);
+        $.ajax(
+            {
+                url: 'rest/build/history?sinceDate=' + sinceDate + '000001&untilDate=' + untilDate + '235959',
+                success: function (result) {
+                    printStatistics(num, result);
+                },
+                error: showErrInLoadStatus
+            }
+        );
+    }
+
+    $(function() {
+        $('input[name="daterange1"]').daterangepicker(
+            dateRangePickerParam(oneWeekAgo, new Date()), function (start, end, label) {
+                loadData(1, start.format("DDMMYYYY"), end.format("DDMMYYYY"));
+            });
+    });
+
+    $(function() {
+        $('input[name="daterange2"]').daterangepicker(
+            dateRangePickerParam(twoWeekAgo, oneWeekAgo), function (start, end, label) {
+                loadData(2, start.format("DDMMYYYY"), end.format("DDMMYYYY"));
+            });
+    });
+
+    for (let i = 0; i < prOcc.length; i++) {
+        $("#clickGraph" + prOcc[i]).click(function() {
+            document.getElementById("showGraph" + prOcc[i]).style.display === 'none' ?
+                document.getElementById("showGraph" + prOcc[i]).style.display = null :
+                document.getElementById("showGraph" + prOcc[i]).style.display = 'none';
+        });
+
+        $("#clickGraph" + tOcc[i]).click(function() {
+            document.getElementById("showGraph" + tOcc[i]).style.display === 'none' ?
+                document.getElementById("showGraph" + tOcc[i]).style.display = null :
+                document.getElementById("showGraph" + tOcc[i]).style.display = 'none';
+        });
+    }
+
+    function drawGraph(prefix, num, dates, counts, text) {
+        let data = [];
+
+        for (let i = 0; i < dates.length; i++) {
+            data[i] = {};
+            data[i].date = dates[i];
+            data[i].count = counts[i];
+        }
+
+        d3.selectAll("#graph" + prefix + num + "> *").remove();
+        d3.selectAll('.axis').remove();
+
+        svg = d3.select("#graph" + prefix + num).append("svg:svg");
+        margin = {top: 20, right: 20, bottom: 30, left: 50};
+        width = +500 - margin.left - margin.right;
+        height = +200 - margin.top - margin.bottom;
+        g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+        x = d3.scaleTime().range([0, width]);
+        y = d3.scaleLinear().range([height, 0]);
+
+        line = d3.line()
+            .curve(d3.curveBasis)
+            .x(function(d) { return x(d.date); })
+            .y(function(d) { return y(d.count); });
+
+
+        x.domain(d3.extent(data, function(d) { return d.date; }));
+        y.domain(d3.extent(data, function(d) { return d.count; }));
+
+        g.append("svg:g")
+            .attr("transform", "translate(0," + height + ")")
+            .call(d3.axisBottom(x))
+            .select(".domain")
+            .remove();
+
+        g.append("svg:g")
+            .call(d3.axisLeft(y))
+            .append("text")
+            .attr("fill", "#000")
+            .attr("transform", "rotate(-90)")
+            .attr("y", 6)
+            .attr("dy", "0.71em")
+            .attr("text-anchor", "end")
+            .text(text);
+
+        g.append("svg:path")
+            .datum(data)
+            .attr("fill", "none")
+            .attr("stroke", "steelblue")
+            .attr("stroke-linejoin", "round")
+            .attr("stroke-linecap", "round")
+            .attr("stroke-width", 1.5)
+            .attr("d", line);
+    }
+</script>
+</body>
+</html>
diff --git a/ignite-tc-helper-web/src/main/webapp/css/style-1.5.css b/ignite-tc-helper-web/src/main/webapp/css/style-1.5.css
index f2b85e0..942e868 100644
--- a/ignite-tc-helper-web/src/main/webapp/css/style-1.5.css
+++ b/ignite-tc-helper-web/src/main/webapp/css/style-1.5.css
@@ -136,4 +136,33 @@ form li:after
 	clear:both;
 	display:block;
 	margin-top:10px;
+}
+.stat{
+	width: 80%;
+	border-collapse: collapse;
+}
+.stat td, .stat th {
+	padding: 10px 5px 10px 5px;
+}
+.data1, .data2{
+	text-align: center;
+	vertical-align: middle;
+}
+.section{
+	font-weight: bold;
+}
+
+.stat .field{
+	padding-left: 15px;
+}
+
+.stat th {
+	text-align: left;
+	padding: 5px;
+	background-color: #f5f5ff;
+	color: #000000;
+}
+
+.stat tr:nth-child(4n - 1) {
+	background-color: #fafaff;
 }
\ No newline at end of file
diff --git a/ignite-tc-helper-web/src/main/webapp/img/browser.png b/ignite-tc-helper-web/src/main/webapp/img/browser.png
new file mode 100644
index 0000000..c57f7eb
Binary files /dev/null and b/ignite-tc-helper-web/src/main/webapp/img/browser.png differ
diff --git a/ignite-tc-helper-web/src/main/webapp/img/loading.gif b/ignite-tc-helper-web/src/main/webapp/img/loading.gif
new file mode 100644
index 0000000..e1b07ea
Binary files /dev/null and b/ignite-tc-helper-web/src/main/webapp/img/loading.gif differ
diff --git a/ignite-tc-helper-web/src/main/webapp/index.html b/ignite-tc-helper-web/src/main/webapp/index.html
index ceb6aa0..4d80f65 100644
--- a/ignite-tc-helper-web/src/main/webapp/index.html
+++ b/ignite-tc-helper-web/src/main/webapp/index.html
@@ -125,7 +125,7 @@
 <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>
+<a href="comparison.html">Comparison master's branch in the date interval</a><br>
 <br>
 Check branch/PR:   <br>
 <div id="suitesForPrCheck"></div>


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services