Time consuming builds detection, including timed out suites
diff --git a/ignite-tc-helper-web/src/main/webapp/buildtime.html b/ignite-tc-helper-web/src/main/webapp/buildtime.html
index 2e1dcc4..7090beb 100644
--- a/ignite-tc-helper-web/src/main/webapp/buildtime.html
+++ b/ignite-tc-helper-web/src/main/webapp/buildtime.html
@@ -50,6 +50,7 @@
         methods: {
             setBuildTimeStat(data) {
                 this.byBuildType = data.byBuildType;
+                this.timedOutByBuildType = data.timedOutByBuildType;
 
                 $("#loadStatus").html("");
             },
@@ -85,6 +86,7 @@
 
 
 <div class="formgroup"  id="app">
+
     <v-app id="readyForReview">
         <!--<v-expansion-panel>-->
             <!--<v-expansion-panel-content-->
@@ -95,6 +97,7 @@
                     <div>Build types longest avg.duration</div>
                 </template>
                 <v-card>
+                    <div>Longest average duration (more than 90 minutes)</div>
                     <v-data-table
                             :headers="headers"
                             :items="byBuildType"
@@ -111,6 +114,19 @@
                             <td class="text-xs-right">{{ props.item.totalDuration }}</td>
                         </template>
                     </v-data-table>
+
+                    <div>Timed out suites average duration (more than 60 minutes)</div>
+                    <v-data-table
+                            :headers="headers"
+                            :items="timedOutByBuildType"
+                            class="elevation-1"
+                    >
+                        <template v-slot:items="props">
+                            <td class="text-xs-right">{{ props.item.buildType }}</td>
+                            <td class="text-xs-right">{{ props.item.averageDuration }}</td>
+                            <td class="text-xs-right">{{ props.item.totalDuration }}</td>
+                        </template>
+                    </v-data-table>
                 </v-card>
             <!--</v-expansion-panel-content>-->
         <!--</v-expansion-panel>-->
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/buildtime/BuildTimeService.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/buildtime/BuildTimeService.java
index 7001e35..6e47757 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/buildtime/BuildTimeService.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/buildtime/BuildTimeService.java
@@ -76,26 +76,31 @@
 
         BuildTimeResultUi resultUi = new BuildTimeResultUi();
 
-        long minDuration = Duration.ofMinutes(60).toMillis();
+        long minDuration = Duration.ofMinutes(90).toMillis();
+        long minDurationTimeout = Duration.ofMinutes(60).toMillis();
         int cntToInclude = 50;
-        BuildTimeResult  res = lastRes1d;
-        List<Map.Entry<Long, BuildTimeRecord>> entries = res.topByBuildTypes(availableServers, minDuration, cntToInclude);
+        BuildTimeResult res = lastRes1d;
 
-        entries.forEach(e -> {
-            BuildTimeRecordUi buildTimeRecordUi = new BuildTimeRecordUi();
-            Long key = e.getKey();
-            int btId = BuildTimeResult.cacheKeyToBuildType(key);
-            buildTimeRecordUi.buildType = compactor.getStringFromId(btId);
+        res.topByBuildTypes(availableServers, minDuration, cntToInclude)
+                .stream().map(this::convertToUi).forEach(e -> resultUi.byBuildType.add(e));
 
-            buildTimeRecordUi.averageDuration = TimeUtil.millisToDurationPrintable(e.getValue().avgDuration());
-            buildTimeRecordUi.totalDuration =  TimeUtil.millisToDurationPrintable(e.getValue().totalDuration());
-
-            resultUi.byBuildType.add(buildTimeRecordUi);
-        });
+        res.topTimeoutsByBuildTypes(availableServers, minDurationTimeout, cntToInclude)
+                .stream().map(this::convertToUi).forEach(e -> resultUi.timedOutByBuildType.add(e));
 
         return resultUi;
     }
 
+    public BuildTimeRecordUi convertToUi(Map.Entry<Long, BuildTimeRecord> e) {
+        BuildTimeRecordUi buildTimeRecordUi = new BuildTimeRecordUi();
+        Long key = e.getKey();
+        int btId = BuildTimeResult.cacheKeyToBuildType(key);
+        buildTimeRecordUi.buildType = compactor.getStringFromId(btId);
+
+        buildTimeRecordUi.averageDuration = TimeUtil.millisToDurationPrintable(e.getValue().avgDuration());
+        buildTimeRecordUi.totalDuration =  TimeUtil.millisToDurationPrintable(e.getValue().totalDuration());
+        return buildTimeRecordUi;
+    }
+
     @SuppressWarnings("WeakerAccess")
     @MonitoredTask(name = "Load Build Time Analytics")
     protected void loadAnalytics() {
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeResultUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeResultUi.java
index 5bcebc9..295a141 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeResultUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeResultUi.java
@@ -25,4 +25,5 @@
 @SuppressWarnings({"WeakerAccess", "PublicField"})
 public class BuildTimeResultUi {
     public List<BuildTimeRecordUi> byBuildType = new ArrayList<>();
+    public List<BuildTimeRecordUi> timedOutByBuildType = new ArrayList<>();
 }
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/FatBuildDao.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/FatBuildDao.java
index d3ef135..5b4740d 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/FatBuildDao.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/FatBuildDao.java
@@ -240,6 +240,7 @@
     public BuildTimeResult loadBuildTimeResult(int ageDays, List<Long> idsToCheck) {
         int stateRunning = compactor.getStringId(BuildRef.STATE_RUNNING);
         Integer buildDurationId = compactor.getStringIdIfPresent(Statistics.BUILD_DURATION);
+        int timeoutProblemCode = compactor.getStringId(ProblemOccurrence.TC_EXECUTION_TIMEOUT);
 
         BuildTimeResult res = new BuildTimeResult();
 
@@ -258,7 +259,9 @@
                             System.err.println("Running " + runningTime + " BT: " + buildTypeId);
 
                             int srvId = BuildRefDao.cacheKeyToSrvId(key);
-                            res.add(srvId, buildTypeId, runningTime);
+                            boolean hasTimeout = build.hasBuildProblemType(timeoutProblemCode);
+
+                            res.add(srvId, buildTypeId, runningTime, hasTimeout);
                         }
                     });
                 }
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeResult.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeResult.java
index 0a05e74..c5a68df 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeResult.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeResult.java
@@ -3,13 +3,18 @@
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class BuildTimeResult {
     private Map<Long, BuildTimeRecord> btByBuildType = new HashMap<>();
+    private Map<Long, BuildTimeRecord> timedOutByBuildType = new HashMap<>();
 
-    public void add(int srvId, int buildTypeId, long runningTime) {
-        btByBuildType.computeIfAbsent(buildTypeToCacheKey(srvId, buildTypeId), k->new BuildTimeRecord())
-                .addInvocation(runningTime);
+    public void add(int srvId, int buildTypeId, long runningTime, boolean hasTimeout) {
+        long cacheKey = buildTypeToCacheKey(srvId, buildTypeId);
+        btByBuildType.computeIfAbsent(cacheKey, k -> new BuildTimeRecord()).addInvocation(runningTime);
+
+        if (hasTimeout)
+            timedOutByBuildType.computeIfAbsent(cacheKey, k -> new BuildTimeRecord()).addInvocation(runningTime);
     }
 
     public static long buildTypeToCacheKey(long srvId, int btId) {
@@ -29,17 +34,32 @@
     public List<Map.Entry<Long, BuildTimeRecord>> topByBuildTypes(Set<Integer> availableServers,
                                                                   long minAvgDurationMs,
                                                                   int maxCnt) {
-        return btByBuildType.entrySet().stream()
-                .filter(e -> {
-                    Long key = e.getKey();
-                    int srvId = cacheKeyToSrvId(key);
-                    return availableServers.contains(srvId);
-                })
-                .filter(e -> e.getValue().avgDuration() > minAvgDurationMs)
+        return filtered(btByBuildType, availableServers, minAvgDurationMs)
                 .sorted(Comparator.comparing(
                         (Function<Map.Entry<Long, BuildTimeRecord>, Long>) entry -> entry.getValue().avgDuration())
                         .reversed())
                 .limit(maxCnt)
                 .collect(Collectors.toList());
     }
+
+    public List<Map.Entry<Long, BuildTimeRecord>> topTimeoutsByBuildTypes(Set<Integer> availableServers,
+                                                                  long minAvgDurationMs,
+                                                                  int maxCnt) {
+        return filtered(timedOutByBuildType, availableServers, minAvgDurationMs)
+                .sorted(Comparator.comparing(
+                        (Function<Map.Entry<Long, BuildTimeRecord>, Long>) entry -> entry.getValue().avgDuration())
+                        .reversed())
+                .limit(maxCnt)
+                .collect(Collectors.toList());
+    }
+
+    private Stream<Map.Entry<Long, BuildTimeRecord>> filtered(Map<Long, BuildTimeRecord> map, Set<Integer> availableServers, long minAvgDurationMs) {
+        return map.entrySet().stream()
+                .filter(e -> {
+                    Long key = e.getKey();
+                    int srvId = cacheKeyToSrvId(key);
+                    return availableServers.contains(srvId);
+                })
+                .filter(e -> e.getValue().avgDuration() > minAvgDurationMs);
+    }
 }