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


The following commit(s) were added to refs/heads/master by this push:
     new 061bfe2  IGNITE-9848: Standalone syncer class; Find missing builds by 
refs done; Different compression types for details added - Fixes #48.
061bfe2 is described below

commit 061bfe2a8a75dff489c404910c5b8455b729c98a
Author: Dmitriy Pavlov <[email protected]>
AuthorDate: Sat Oct 27 15:22:40 2018 +0300

    IGNITE-9848: Standalone syncer class; Find missing builds by refs done; 
Different compression types for details added - Fixes #48.
    
    Signed-off-by: Dmitriy Pavlov <[email protected]>
---
 .../main/java/org/apache/ignite/ci/ITeamcity.java  |   1 -
 .../java/org/apache/ignite/ci/db/DbMigrations.java |  15 +-
 .../ignite/ci/di/scheduler/TcBotScheduler.java     |  10 +-
 .../ignite/ci/teamcity/ignited/BuildRefDao.java    |  21 +-
 .../ci/teamcity/ignited/TeamcityIgnitedImpl.java   | 192 +++-------------
 .../ci/teamcity/ignited/TeamcityIgnitedModule.java |   2 +
 .../ignited/fatbuild/FatBuildCompacted.java        |   2 +-
 .../ci/teamcity/ignited/fatbuild/FatBuildDao.java  |  17 ++
 .../ignited/fatbuild/ProactiveFatBuildSync.java    | 256 +++++++++++++++++++++
 .../teamcity/ignited/fatbuild/TestCompacted.java   | 127 ++++++++--
 .../ignite/ci/teamcity/pure/ITeamcityConn.java     |   5 +
 .../org/apache/ignite/ci/web/model/Version.java    |   2 +-
 .../ignited/IgnitedTcInMemoryIntegrationTest.java  |   2 +-
 13 files changed, 457 insertions(+), 195 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 92cdab8..12446b9 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
@@ -59,7 +59,6 @@ public interface ITeamcity extends ITeamcityConn {
 
     CompletableFuture<List<BuildType>> getProjectSuites(String projectId);
 
-    String serverId();
 
     /**
      * @param projectId Suite ID (string without spaces).
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java
index 4a16f79..8b06abb 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java
@@ -150,7 +150,9 @@ public class DbMigrations {
         });
 
         applyMigration(TESTS + "-to-" + testOccurrencesCache.getName(), () -> {
-            IgniteCache<String, TestOccurrences> tests = 
getOrCreateIgnCacheV1(TESTS);
+            IgniteCache<String, TestOccurrences> tests = 
ignite.cache(ignCacheNme(TESTS));
+            if(tests==null)
+                return;
 
             int size = tests.size();
             if (size > 0) {
@@ -402,17 +404,18 @@ public class DbMigrations {
 
         applyDestroyIgnCacheMigration(FINISHED_BUILDS_INCLUDE_FAILED);
         applyDestroyIgnCacheMigration(TEST_OCCURRENCE_FULL);
-        //todo destroy
-    //    applyDestroyIgnCacheMigration(TEAMCITY_BUILD_CACHE_NAME_OLD);
+
+        applyDestroyCacheMigration(TEAMCITY_BUILD_CACHE_NAME_OLD, 
TEAMCITY_BUILD_CACHE_NAME_OLD);
+        applyDestroyIgnCacheMigration(TESTS);
     }
 
-    public void applyDestroyIgnCacheMigration(String cacheName) {
+    private void applyDestroyIgnCacheMigration(String cacheName) {
         String ignCacheNme = ignCacheNme(cacheName);
         applyDestroyCacheMigration(cacheName, ignCacheNme);
     }
 
-    public void applyDestroyCacheMigration(String cacheName, String 
ignCacheNme) {
-        applyMigration("destroy-" + cacheName, () -> {
+    private void applyDestroyCacheMigration(String dispCacheName, String 
ignCacheNme) {
+        applyMigration("destroy-" + dispCacheName, () -> {
             IgniteCache<Object, Object> cache = ignite.cache(ignCacheNme);
 
             if (cache == null) {
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/scheduler/TcBotScheduler.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/scheduler/TcBotScheduler.java
index 2e1ef46..045ea2a 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/scheduler/TcBotScheduler.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/scheduler/TcBotScheduler.java
@@ -84,8 +84,16 @@ class TcBotScheduler implements IScheduler {
 
     /** {@inheritDoc} */
     @Override public void stop() {
-        if (executorSvc != null)
+        if (executorSvc != null) {
             executorSvc.shutdown();
+            try {
+                if(!executorSvc.awaitTermination(10, TimeUnit.SECONDS))
+                    executorSvc.shutdownNow();
+            }
+            catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        }
     }
 
     /**
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefDao.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefDao.java
index d11f148..92b1684 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefDao.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefDao.java
@@ -17,13 +17,7 @@
 
 package org.apache.ignite.ci.teamcity.ignited;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
+import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
@@ -223,4 +217,17 @@ public class BuildRefDao {
 
         return false;
     }
+
+    @AutoProfiling
+    public int[] getAllIds(int srvId) {
+        GridIntList res = new GridIntList(buildRefsCache.size());
+
+        StreamSupport.stream(buildRefsCache.spliterator(), false)
+                .map(Cache.Entry::getKey)
+                .filter(entry -> isKeyForServer(entry, srvId))
+                .map(BuildRefDao::cacheKeyToBuildId)
+                .forEach(res::add);
+
+        return res.array();
+    }
 }
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
index f32d335..5603b44 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
@@ -17,26 +17,7 @@
 package org.apache.ignite.ci.teamcity.ignited;
 
 
-import com.google.common.base.Strings;
-import com.google.common.base.Throwables;
 import com.google.common.collect.Sets;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.OptionalInt;
-import java.util.Set;
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.GuardedBy;
-import javax.inject.Inject;
 import org.apache.ignite.ci.ITeamcity;
 import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.di.MonitoredTask;
@@ -45,26 +26,30 @@ import org.apache.ignite.ci.tcbot.condition.BuildCondition;
 import org.apache.ignite.ci.tcbot.condition.BuildConditionDao;
 import org.apache.ignite.ci.tcmodel.hist.BuildRef;
 import org.apache.ignite.ci.tcmodel.result.Build;
-import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrencesFull;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildDao;
-import org.apache.ignite.ci.teamcity.ignited.fatbuild.TestCompacted;
+import org.apache.ignite.ci.teamcity.ignited.fatbuild.ProactiveFatBuildSync;
 import org.apache.ignite.ci.teamcity.pure.ITeamcityConn;
-import org.apache.ignite.ci.util.ExceptionUtil;
 import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
 public class TeamcityIgnitedImpl implements ITeamcityIgnited {
     /** Logger. */
-    private static final Logger logger = 
LoggerFactory.getLogger(TestCompacted.class);
+    private static final Logger logger = 
LoggerFactory.getLogger(TeamcityIgnitedImpl.class);
 
     /** Max build id diff to enforce reload during incremental refresh. */
     public static final int MAX_ID_DIFF_TO_ENFORCE_CONTINUE_SCAN = 3000;
-    public static final int FAT_BUILD_PROACTIVE_TASKS = 4;
 
     /** Server id. */
-    private String srvId;
+    private String srvNme;
 
     /** Pure HTTP Connection API. */
     private ITeamcityConn conn;
@@ -81,16 +66,13 @@ public class TeamcityIgnitedImpl implements 
ITeamcityIgnited {
     /** Build DAO. */
     @Inject private FatBuildDao fatBuildDao;
 
-    @Inject private IStringCompactor compactor;
+    @Inject private ProactiveFatBuildSync buildSync;
 
     /** Server ID mask for cache Entries. */
     private int srvIdMaskHigh;
 
-    @GuardedBy("this")
-    private Set<Integer> buildToLoad = new HashSet<>();
-
     public void init(String srvId, ITeamcityConn conn) {
-        this.srvId = srvId;
+        this.srvNme = srvId;
         this.conn = conn;
 
         srvIdMaskHigh = ITeamcityIgnited.serverIdToInt(srvId);
@@ -99,27 +81,10 @@ public class TeamcityIgnitedImpl implements 
ITeamcityIgnited {
         fatBuildDao.init();
     }
 
-    /**
-     * Invoke load fat builds later, re-load provided builds.
-     *
-     * @param buildsToAskFromTc Builds to ask from tc.
-     */
-    public void scheduleBuildsLoad(Collection<Integer> buildsToAskFromTc) {
-        if (buildsToAskFromTc.isEmpty())
-            return;
-
-        synchronized (this) {
-            buildToLoad.addAll(buildsToAskFromTc);
-        }
-
-        int ldrToActivate = 
ThreadLocalRandom.current().nextInt(FAT_BUILD_PROACTIVE_TASKS);
-
-        scheduler.sheduleNamed(taskName("loadFatBuilds" + ldrToActivate), () 
-> loadFatBuilds(ldrToActivate), 2, TimeUnit.MINUTES);
 
-    }
-
-    @NotNull public String taskName(String taskName) {
-        return ITeamcityIgnited.class.getSimpleName() +"." + taskName + "." + 
srvId;
+    @NotNull
+    private String taskName(String taskName) {
+        return ITeamcityIgnited.class.getSimpleName() +"." + taskName + "." + 
srvNme;
     }
 
     /** {@inheritDoc} */
@@ -132,7 +97,7 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited 
{
     @Override public List<BuildRef> getBuildHistory(
         @Nullable String buildTypeId,
         @Nullable String branchName) {
-        scheduler.sheduleNamed(taskName("actualizeRecentBuilds"), 
this::actualizeRecentBuilds, 2, TimeUnit.MINUTES);
+        scheduler.sheduleNamed(taskName("actualizeRecentBuildRefs"), 
this::actualizeRecentBuildRefs, 2, TimeUnit.MINUTES);
 
         String bracnhNameQry ;
         if (ITeamcity.DEFAULT.equals(branchName))
@@ -148,7 +113,7 @@ public class TeamcityIgnitedImpl implements 
ITeamcityIgnited {
         Build build = conn.triggerBuild(buildTypeId, branchName, cleanRebuild, 
queueAtTop);
 
         //todo may add additional parameter: load builds into DB in sync/async 
fashion
-        runActualizeBuilds(srvId, false, Sets.newHashSet(build.getId()));
+        runActualizeBuildRefs(srvNme, false, Sets.newHashSet(build.getId()));
 
         return build;
     }
@@ -172,7 +137,7 @@ public class TeamcityIgnitedImpl implements 
ITeamcityIgnited {
         if (existingBuild != null && !existingBuild.isOutdatedEntityVersion())
             return existingBuild;
 
-        FatBuildCompacted savedVer = reloadBuild(buildId, existingBuild);
+        FatBuildCompacted savedVer = buildSync.reloadBuild(conn, buildId, 
existingBuild);
 
         //build was modified, probably we need also to update reference 
accordindly
         if (savedVer != null)
@@ -181,61 +146,15 @@ public class TeamcityIgnitedImpl implements 
ITeamcityIgnited {
         return savedVer == null ? existingBuild : savedVer;
     }
 
-    /**
-     * @param buildId
-     * @param existingBuild
-     * @return new build if it was updated or null if no updates detected
-     */
-    public FatBuildCompacted reloadBuild(int buildId, @Nullable 
FatBuildCompacted existingBuild) {
-        //  System.err.println(Thread.currentThread().getName()+ ": Build " + 
buildId);
-        //todo some sort of locking to avoid double requests
-        Build build;
-        List<TestOccurrencesFull> tests = new ArrayList<>();
-        try {
-            build = conn.getBuild(buildId);
-
-            String nextHref = null;
-            do {
-                boolean testDtls = !build.isComposite(); // don't query test 
details for compoite
-                TestOccurrencesFull page = conn.getTestsPage(buildId, 
nextHref, testDtls);
-                nextHref = page.nextHref();
-
-                tests.add(page);
-            }
-            while (!Strings.isNullOrEmpty(nextHref));
-        }
-        catch (Exception e) {
-            if (Throwables.getRootCause(e) instanceof FileNotFoundException) {
-                logger.info("Loading build [" + buildId + "] for server [" + 
srvId + "] failed:" + e.getMessage(), e);
-
-                if (existingBuild != null) {
-                    build = existingBuild.toBuild(compactor);
 
-                    if(build.isRunning() || build.isQueued())
-                        build.setCancelled();
-
-                    tests = 
Collections.singletonList(existingBuild.getTestOcurrences(compactor));
-                }
-                else
-                    build = Build.createFakeStub();
-            } else {
-                logger.error("Loading build [" + buildId + "] for server [" + 
srvId + "] failed:" + e.getMessage(), e);
-
-                e.printStackTrace();
-
-                throw ExceptionUtil.propagateException(e);
-            }
-        }
-
-        //if we are here because of some sort of outdated version of build,
-        // new save will be performed with new entity version for compacted 
build
-        return fatBuildDao.saveBuild(srvIdMaskHigh, buildId, build, tests, 
existingBuild);
-    }
 
     /**
      *
      */
-    void actualizeRecentBuilds() {
+    void actualizeRecentBuildRefs() {
+        // schedule find missing later
+        buildSync.invokeLaterFindMissingByBuildRef(srvNme, conn);
+
         List<BuildRefCompacted> running = 
buildRefDao.getQueuedAndRunning(srvIdMaskHigh);
 
         Set<Integer> paginateUntil = new HashSet<>();
@@ -252,23 +171,23 @@ public class TeamcityIgnitedImpl implements 
ITeamcityIgnited {
             });
         }
         //schedule direct reload for Fat Builds for all queued too-old builds
-        scheduleBuildsLoad(directUpload);
+        buildSync.scheduleBuildsLoad(conn, directUpload);
 
-        runActualizeBuilds(srvId, false, paginateUntil);
+        runActualizeBuildRefs(srvNme, false, paginateUntil);
 
         if(!paginateUntil.isEmpty()) {
             //some builds may stuck in the queued or running, enforce loading 
as well
-            scheduleBuildsLoad(paginateUntil);
+            buildSync.scheduleBuildsLoad(conn, paginateUntil);
         }
 
         // schedule full resync later
-        scheduler.invokeLater(this::sheduleResync, 15, TimeUnit.MINUTES);
+        scheduler.invokeLater(this::sheduleResyncBuildRefs, 15, 
TimeUnit.MINUTES);
     }
 
     /**
      *
      */
-    private void sheduleResync() {
+    private void sheduleResyncBuildRefs() {
         scheduler.sheduleNamed(taskName("fullReindex"), this::fullReindex, 
120, TimeUnit.MINUTES);
     }
 
@@ -276,25 +195,27 @@ public class TeamcityIgnitedImpl implements 
ITeamcityIgnited {
      *
      */
     void fullReindex() {
-        runActualizeBuilds(srvId, true, null);
+        runActualizeBuildRefs(srvNme, true, null);
     }
 
+
     /**
-     * @param srvId Server id. todo to be added as composite name extend
+     * @param srvId Server id.
      * @param fullReindex Reindex all builds from TC history.
      * @param mandatoryToReload [in/out] Build ID should be found before end 
of sync. Ignored if fullReindex mode.
      *
      */
+    @SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
     @MonitoredTask(name = "Actualize BuildRefs(srv, full resync)", 
nameExtArgsIndexes = {0, 1})
     @AutoProfiling
-    protected String runActualizeBuilds(String srvId, boolean fullReindex,
-        @Nullable Set<Integer> mandatoryToReload) {
+    protected String runActualizeBuildRefs(String srvId, boolean fullReindex,
+                                           @Nullable Set<Integer> 
mandatoryToReload) {
         AtomicReference<String> outLinkNext = new AtomicReference<>();
         List<BuildRef> tcDataFirstPage = conn.getBuildRefs(null, outLinkNext);
 
         Set<Long> buildsUpdated = buildRefDao.saveChunk(srvIdMaskHigh, 
tcDataFirstPage);
         int totalUpdated = buildsUpdated.size();
-        scheduleBuildsLoad(cacheKeysToBuildIds(buildsUpdated));
+        buildSync.scheduleBuildsLoad(conn, cacheKeysToBuildIds(buildsUpdated));
 
         int totalChecked = tcDataFirstPage.size();
         int neededToFind = 0;
@@ -310,7 +231,7 @@ public class TeamcityIgnitedImpl implements 
ITeamcityIgnited {
             List<BuildRef> tcDataNextPage = conn.getBuildRefs(nextPageUrl, 
outLinkNext);
             Set<Long> curChunkBuildsSaved = 
buildRefDao.saveChunk(srvIdMaskHigh, tcDataNextPage);
             totalUpdated += curChunkBuildsSaved.size();
-            scheduleBuildsLoad(cacheKeysToBuildIds(curChunkBuildsSaved));
+            buildSync.scheduleBuildsLoad(conn, 
cacheKeysToBuildIds(curChunkBuildsSaved));
 
             int savedCurChunk = curChunkBuildsSaved.size();
 
@@ -332,47 +253,4 @@ public class TeamcityIgnitedImpl implements 
ITeamcityIgnited {
     @NotNull private List<Integer> cacheKeysToBuildIds(Collection<Long> 
cacheKeysUpdated) {
         return 
cacheKeysUpdated.stream().map(BuildRefDao::cacheKeyToBuildId).collect(Collectors.toList());
     }
-
-    /** */
-    private void loadFatBuilds(int ldrNo) {
-        Set<Integer> load;
-
-        synchronized (this) {
-            load = buildToLoad;
-            buildToLoad = new HashSet<>();
-        }
-
-        doLoadBuilds(ldrNo, srvId, load);
-    }
-
-    @MonitoredTask(name = "Proactive Builds Loading (agent,server)", 
nameExtArgsIndexes = {0, 1})
-    @AutoProfiling
-    protected String doLoadBuilds(int ldrNo, String srvId, Set<Integer> load) {
-        if(load.isEmpty())
-            return "Nothing to load";
-
-        AtomicInteger err = new AtomicInteger();
-        AtomicInteger ld = new AtomicInteger();
-
-        Map<Long, FatBuildCompacted> builds = 
fatBuildDao.getAllFatBuilds(srvIdMaskHigh, load);
-
-        load.forEach(
-            buildId -> {
-                try {
-                    FatBuildCompacted existingBuild = 
builds.get(FatBuildDao.buildIdToCacheKey(srvIdMaskHigh, buildId));
-
-                    FatBuildCompacted savedVer = reloadBuild(buildId, 
existingBuild);
-
-                    if (savedVer != null)
-                        ld.incrementAndGet();
-                }
-                catch (Exception e) {
-                    logger.error("", e);
-                    err.incrementAndGet();
-                }
-            }
-        );
-
-        return "Builds updated " + ld.get() + " from " + load.size() + " 
requested, errors: " + err;
-    }
 }
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedModule.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedModule.java
index e397d8f..9a5d68c 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedModule.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedModule.java
@@ -20,6 +20,7 @@ import com.google.inject.AbstractModule;
 import com.google.inject.internal.SingletonScope;
 import org.apache.ignite.ci.tcbot.condition.BuildConditionDao;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildDao;
+import org.apache.ignite.ci.teamcity.ignited.fatbuild.ProactiveFatBuildSync;
 import org.apache.ignite.ci.teamcity.pure.ITeamcityHttpConnection;
 import org.apache.ignite.ci.teamcity.restcached.TcRestCachedModule;
 import org.jetbrains.annotations.Nullable;
@@ -37,6 +38,7 @@ public class TeamcityIgnitedModule extends AbstractModule {
         bind(BuildRefDao.class).in(new SingletonScope());
         bind(BuildConditionDao.class).in(new SingletonScope());
         bind(FatBuildDao.class).in(new SingletonScope());
+        bind(ProactiveFatBuildSync.class).in(new SingletonScope());
 
         bind(IStringCompactor.class).to(IgniteStringCompactor.class).in(new 
SingletonScope());
 
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
index 59701f2..231ed5d 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
@@ -39,7 +39,7 @@ import org.jetbrains.annotations.Nullable;
 @Persisted
 public class FatBuildCompacted extends BuildRefCompacted implements 
IVersionedEntity {
     /** Latest version. */
-    private static final int LATEST_VERSION = 1;
+    private static final int LATEST_VERSION = 2;
 
     /** Default branch flag offset. */
     public static final int DEF_BR_F = 0;
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildDao.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildDao.java
index fd8b8df..afe4812 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildDao.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildDao.java
@@ -24,15 +24,20 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import javax.cache.Cache;
 import javax.inject.Inject;
 import javax.inject.Provider;
 import javax.validation.constraints.NotNull;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.ci.db.TcHelperDb;
+import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.tcmodel.result.Build;
 import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrencesFull;
+import org.apache.ignite.ci.teamcity.ignited.BuildRefDao;
 import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
+import org.apache.ignite.internal.util.GridIntList;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -120,4 +125,16 @@ public class FatBuildDao {
 
         return buildsCache.getAll(ids);
     }
+
+    /**
+     * @param key Key.
+     * @param srvId Server id.
+     */
+    private boolean isKeyForServer(Long key, int srvId) {
+        return key!=null && key >> 32 == srvId;
+    }
+
+    public boolean containsKey(int srvIdMaskHigh, int buildRefKey) {
+        return buildsCache.containsKey(buildIdToCacheKey(srvIdMaskHigh, 
buildRefKey));
+    }
 }
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/ProactiveFatBuildSync.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/ProactiveFatBuildSync.java
new file mode 100644
index 0000000..8eac870
--- /dev/null
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/ProactiveFatBuildSync.java
@@ -0,0 +1,256 @@
+/*
+ * 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.teamcity.ignited.fatbuild;
+
+import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
+import org.apache.ignite.ci.di.AutoProfiling;
+import org.apache.ignite.ci.di.MonitoredTask;
+import org.apache.ignite.ci.di.scheduler.IScheduler;
+import org.apache.ignite.ci.tcmodel.result.Build;
+import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrencesFull;
+import org.apache.ignite.ci.teamcity.ignited.BuildRefDao;
+import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
+import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnited;
+import org.apache.ignite.ci.teamcity.pure.ITeamcityConn;
+import org.apache.ignite.ci.util.ExceptionUtil;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.inject.Inject;
+import java.io.FileNotFoundException;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ProactiveFatBuildSync {
+    public static final int FAT_BUILD_PROACTIVE_TASKS = 4;
+
+    /** Logger. */
+    private static final Logger logger = 
LoggerFactory.getLogger(ProactiveFatBuildSync.class);
+
+    /** Build reference DAO. */
+    @Inject
+    private BuildRefDao buildRefDao;
+
+    /** Build DAO. */
+    @Inject private FatBuildDao fatBuildDao;
+
+    /** Scheduler. */
+    @Inject private IScheduler scheduler;
+
+    @Inject private IStringCompactor compactor;
+
+    @GuardedBy("this")
+    private Map<String, SyncTask> buildToLoad = new HashMap<>();
+
+    private static class SyncTask {
+        ITeamcityConn conn;
+        Set<Integer> ids = new HashSet<>();
+    }
+
+    /**
+     * Invoke load fat builds later, re-load provided builds.
+     * @param conn
+     * @param buildsToAskFromTc Builds to ask from tc.
+     */
+    public void scheduleBuildsLoad(ITeamcityConn conn, Collection<Integer> 
buildsToAskFromTc) {
+        if (buildsToAskFromTc.isEmpty())
+            return;
+
+
+        synchronized (this) {
+            final String serverId = conn.serverId();
+            final SyncTask syncTask = buildToLoad.computeIfAbsent(serverId, s 
-> new SyncTask());
+            syncTask.conn = conn;
+            syncTask.ids.addAll(buildsToAskFromTc);
+        }
+
+        int ldrToActivate = 
ThreadLocalRandom.current().nextInt(FAT_BUILD_PROACTIVE_TASKS);
+
+        scheduler.sheduleNamed(taskName("loadFatBuilds" + ldrToActivate, 
conn.serverId()),
+                () -> loadFatBuilds(ldrToActivate, conn.serverId()), 2, 
TimeUnit.MINUTES);
+
+    }
+
+
+    @SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
+    @MonitoredTask(name = "Find missing builds", nameExtArgsIndexes = {0})
+    @AutoProfiling
+    protected String findMissingBuildsFromBuildRef(String srvId, ITeamcityConn 
conn) {
+        int srvIdMaskHigh = ITeamcityIgnited.serverIdToInt(srvId);
+
+        final int[] buildRefKeys = buildRefDao.getAllIds(srvIdMaskHigh);
+
+        Arrays.parallelSort(buildRefKeys);
+
+        List<Integer> buildsIdsToLoad = new ArrayList<>();
+        int totalAskedToLoad = 0;
+
+        for (int buildRefKey : buildRefKeys) {
+            if (!fatBuildDao.containsKey(srvIdMaskHigh, buildRefKey))
+                buildsIdsToLoad.add(buildRefKey);
+
+            if (buildsIdsToLoad.size() >= 100) {
+                totalAskedToLoad += buildsIdsToLoad.size();
+                scheduleBuildsLoad(conn, buildsIdsToLoad);
+                buildsIdsToLoad.clear();
+            }
+        }
+
+        if (!buildsIdsToLoad.isEmpty()) {
+            totalAskedToLoad += buildsIdsToLoad.size();
+            scheduleBuildsLoad(conn, buildsIdsToLoad);
+        }
+
+        return "Invoked later load for " + totalAskedToLoad + " builds from " 
+ srvId;
+    }
+
+    /** */
+    private void loadFatBuilds(int ldrNo, String serverId) {
+        Set<Integer> load;
+        ITeamcityConn conn;
+        synchronized (this) {
+            final SyncTask syncTask = buildToLoad.get(serverId);
+            if (syncTask == null)
+                return;
+
+            if (syncTask.ids.isEmpty()) {
+                syncTask.conn = null;
+                return;
+            }
+
+            if (syncTask.conn == null)
+                return;
+
+            load = syncTask.ids;
+            syncTask.ids = new HashSet<>();
+
+            conn = syncTask.conn;
+            syncTask.conn = null;
+        }
+
+        doLoadBuilds(ldrNo, serverId, conn, load);
+    }
+
+    @SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
+    @MonitoredTask(name = "Proactive Builds Loading (srv,agent)", 
nameExtArgsIndexes = {1, 0})
+    @AutoProfiling
+    protected String doLoadBuilds(int ldrNo, String srvId, ITeamcityConn conn, 
Set<Integer> load) {
+        if(load.isEmpty())
+            return "Nothing to load";
+
+        final int srvIdMaskHigh = ITeamcityIgnited.serverIdToInt(srvId);
+
+        AtomicInteger err = new AtomicInteger();
+        AtomicInteger ld = new AtomicInteger();
+
+        Map<Long, FatBuildCompacted> builds = 
fatBuildDao.getAllFatBuilds(srvIdMaskHigh, load);
+
+        load.forEach(
+                buildId -> {
+                    try {
+                        FatBuildCompacted existingBuild = 
builds.get(FatBuildDao.buildIdToCacheKey(srvIdMaskHigh, buildId));
+
+                        FatBuildCompacted savedVer = reloadBuild(conn, 
buildId, existingBuild);
+
+                        if (savedVer != null)
+                            ld.incrementAndGet();
+                    }
+                    catch (Exception e) {
+                        logger.error("", e);
+                        err.incrementAndGet();
+                    }
+                }
+        );
+
+        return "Builds updated " + ld.get() + " from " + load.size() + " 
requested, errors: " + err;
+    }
+
+    @NotNull
+    private String taskName(String taskName, String srvName) {
+        return ProactiveFatBuildSync.class.getSimpleName() +"." + taskName + 
"." + srvName;
+    }
+
+    public void invokeLaterFindMissingByBuildRef(String srvName, ITeamcityConn 
conn) {
+        scheduler.sheduleNamed(taskName("findMissingBuildsFromBuildRef", 
srvName),
+                () -> findMissingBuildsFromBuildRef(srvName, conn), 360, 
TimeUnit.MINUTES);
+    }
+
+    /**
+     *
+     * @param conn
+     * @param buildId
+     * @param existingBuild
+     * @return new build if it was updated or null if no updates detected
+     */
+    @SuppressWarnings({"WeakerAccess"})
+    @AutoProfiling
+    public FatBuildCompacted reloadBuild(ITeamcityConn conn, int buildId, 
@Nullable FatBuildCompacted existingBuild) {
+        //  System.err.println(Thread.currentThread().getName()+ ": Build " + 
buildId);
+        //todo some sort of locking to avoid double requests
+
+        final String srvNme = conn.serverId();
+        final int srvIdMask = ITeamcityIgnited.serverIdToInt(srvNme);
+
+        Build build;
+        List<TestOccurrencesFull> tests = new ArrayList<>();
+        try {
+            build = conn.getBuild(buildId);
+
+            String nextHref = null;
+            do {
+                boolean testDtls = !build.isComposite(); // don't query test 
details for compoite
+                TestOccurrencesFull page = conn.getTestsPage(buildId, 
nextHref, testDtls);
+                nextHref = page.nextHref();
+
+                tests.add(page);
+            }
+            while (!Strings.isNullOrEmpty(nextHref));
+        }
+        catch (Exception e) {
+            if (Throwables.getRootCause(e) instanceof FileNotFoundException) {
+                logger.info("Loading build [" + buildId + "] for server [" + 
srvNme + "] failed:" + e.getMessage(), e);
+
+                if (existingBuild != null) {
+                    build = existingBuild.toBuild(compactor);
+
+                    if(build.isRunning() || build.isQueued())
+                        build.setCancelled();
+
+                    tests = 
Collections.singletonList(existingBuild.getTestOcurrences(compactor));
+                }
+                else
+                    build = Build.createFakeStub();
+            } else {
+                logger.error("Loading build [" + buildId + "] for server [" + 
srvNme + "] failed:" + e.getMessage(), e);
+
+                e.printStackTrace();
+
+                throw ExceptionUtil.propagateException(e);
+            }
+        }
+
+        //if we are here because of some sort of outdated version of build,
+        // new save will be performed with new entity version for compacted 
build
+        return fatBuildDao.saveBuild(srvIdMask, buildId, build, tests, 
existingBuild);
+    }
+}
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/TestCompacted.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/TestCompacted.java
index 13e4c5b..37a32bc 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/TestCompacted.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/TestCompacted.java
@@ -19,9 +19,16 @@ package org.apache.ignite.ci.teamcity.ignited.fatbuild;
 
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.BitSet;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
 import org.apache.ignite.ci.analysis.RunStat;
 import org.apache.ignite.ci.tcmodel.hist.BuildRef;
 import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrence;
@@ -41,6 +48,13 @@ public class TestCompacted {
     public static final int CUR_MUTED_F = 2;
     public static final int CUR_INV_F = 4;
     public static final int IGNORED_F = 6;
+    /** true when kept uncompressed */
+    public static final int COMPRESS_TYPE_FLAG1 = 8;
+    /** true when kept gzip */
+    public static final int COMPRESS_TYPE_FLAG2 = 9;
+    public static final int COMPRESS_TYPE_RFU3 = 10;
+    public static final int COMPRESS_TYPE_RFU4 = 11;
+
     /** Id in this build only. Does not idenfity test for its history */
     private int idInBuild = -1;
 
@@ -56,9 +70,8 @@ public class TestCompacted {
     /** Actual build id. */
     private int actualBuildId = -1;
 
-    /** Snappy comressed test log Details. */
-    private byte[] details;
-
+    /** Uncompressesd/ZIP/Snappy compressed test log Details. */
+    @Nullable private byte[] details;
 
     /** Logger. */
     private static final Logger logger = 
LoggerFactory.getLogger(TestCompacted.class);
@@ -77,9 +90,10 @@ public class TestCompacted {
         String testOccurrenceId = testOccurrence.getId();
         if (!Strings.isNullOrEmpty(testOccurrenceId)) {
             try {
-                idInBuild = 
RunStat.extractFullId(testOccurrenceId).getTestId();
-            }
-            catch (Exception e) {
+                final RunStat.TestId testId = 
RunStat.extractFullId(testOccurrenceId);
+                if (testId != null)
+                    idInBuild = testId.getTestId();
+            } catch (Exception e) {
                 logger.error("Failed to handle TC response: " + 
testOccurrenceId, e);
             }
         }
@@ -99,15 +113,7 @@ public class TestCompacted {
         if (testOccurrence.test != null && testOccurrence.test.id != null)
             testId = testOccurrence.test.id;
 
-        String details = testOccurrence.details;
-        if (!Strings.isNullOrEmpty(details)) {
-            try {
-                this.details = 
Snappy.compress(details.getBytes(StandardCharsets.UTF_8));
-            }
-            catch (Exception e) {
-                logger.error("Snappy.compress failed: " + e.getMessage(), e);
-            }
-        }
+        setDetails(testOccurrence.details);
     }
 
     private void setFlag(int off, Boolean val) {
@@ -177,17 +183,98 @@ public class TestCompacted {
         if (details == null)
             return "";
 
+        final boolean flag1 = flags.get(COMPRESS_TYPE_FLAG1);
+        final boolean flag2 = flags.get(COMPRESS_TYPE_FLAG2);
+        if(!flag1 && !flag2) {
+            try {
+                byte[] uncompressed = Snappy.uncompress(details);
+                return new String(uncompressed, StandardCharsets.UTF_8);
+            } catch (IOException e) {
+                logger.error("Snappy.uncompress failed: " + e.getMessage(), e);
+                return null;
+            }
+        } else if(flag1 && !flag2) {
+            return new String(details, StandardCharsets.UTF_8);
+        } else if (!flag1 && flag2) {
+            try {
+                final ByteArrayInputStream in = new 
ByteArrayInputStream(details);
+                ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                try (final GZIPInputStream gzi = new GZIPInputStream(in)) {
+                    byte[] outbuf = new byte[details.length];
+                    int len;
+                    while ((len = gzi.read(outbuf, 0, outbuf.length)) != -1) {
+                        bos.write(outbuf, 0, len);
+                    }
+                }
+
+                return new String(bos.toByteArray(), StandardCharsets.UTF_8);
+            } catch (Exception e) {
+                logger.error("GZip.uncompress failed: " + e.getMessage(), e);
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    public void setDetails(String dtlsString) {
+        if (Strings.isNullOrEmpty(dtlsString)) {
+            this.details = null;
+            return;
+        }
+
+
+        byte[] uncompressed;
+        byte[] snappy = null;
+        byte[] gzip = null;
+        try {
+            uncompressed = dtlsString.getBytes(StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            logger.error("Set details failed: " + e.getMessage(), e);
+            return;
+        }
         try {
-            byte[] uncompressed = Snappy.uncompress(details);
-            return new String(uncompressed, StandardCharsets.UTF_8);
+            snappy = Snappy.compress(uncompressed);
+        }
+        catch (Exception e) {
+            logger.error("Snappy.compress failed: " + e.getMessage(), e);
         }
-        catch (IOException e) {
 
-            logger.error("Snappy.uncompress failed: " + e.getMessage(), e);
-            return null;
+        try {
+            final ByteArrayOutputStream out = new ByteArrayOutputStream();
+            try(final GZIPOutputStream gzipOutputStream = new 
GZIPOutputStream(out)) {
+                gzipOutputStream.write(uncompressed);
+            }
+            gzip = out.toByteArray();
+        }
+        catch (Exception e) {
+            logger.error("Snappy.compress failed: " + e.getMessage(), e);
+        }
+
+        final int snappyLen = snappy!=null ? snappy.length : -1;
+        final int gzipLen = gzip!=null ? gzip.length : -1;
 
+        flags.set(COMPRESS_TYPE_FLAG1, true);
+        flags.set(COMPRESS_TYPE_FLAG2, false);
+        //uncompressed
+        details = uncompressed;
+
+        if (snappyLen > 0 && snappyLen < details.length) {
+            flags.set(COMPRESS_TYPE_FLAG1, false);
+            flags.set(COMPRESS_TYPE_FLAG2, false);
+            details = snappy;
+        }
+
+        if (gzipLen > 0 && gzipLen < details.length) {
+            flags.set(COMPRESS_TYPE_FLAG1, false);
+            flags.set(COMPRESS_TYPE_FLAG2, true);
+            details = gzip;
         }
 
+
+        logger.info("U " + uncompressed.length + " S " + snappyLen + " Z " + 
gzipLen + ": F (" +
+                flags.get(COMPRESS_TYPE_FLAG1) + ", " +
+                flags.get(COMPRESS_TYPE_FLAG2) +")");
     }
 
     public Boolean getIgnoredFlag() {
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/pure/ITeamcityConn.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/pure/ITeamcityConn.java
index 78f3d34..f832088 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/pure/ITeamcityConn.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/pure/ITeamcityConn.java
@@ -30,6 +30,11 @@ import 
org.apache.ignite.ci.tcmodel.result.tests.TestOccurrencesFull;
  */
 public interface ITeamcityConn {
     /**
+     * @return Internal server ID as string
+     */
+    String serverId();
+
+    /**
      * @return Normalized Host address, ends with '/'.
      */
     public String host();
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
index b58b699..c082100 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
@@ -23,7 +23,7 @@ package org.apache.ignite.ci.web.model;
     public static final String GITHUB_REF = 
"https://github.com/apache/ignite-teamcity-bot";;
 
     /** TC Bot Version. */
-    public static final String VERSION = "20181026";
+    public static final String VERSION = "20181027";
 
     /** TC Bot Version. */
     public String version = VERSION;
diff --git 
a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/IgnitedTcInMemoryIntegrationTest.java
 
b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/IgnitedTcInMemoryIntegrationTest.java
index b07e102..173a959 100644
--- 
a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/IgnitedTcInMemoryIntegrationTest.java
+++ 
b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/IgnitedTcInMemoryIntegrationTest.java
@@ -214,7 +214,7 @@ public class IgnitedTcInMemoryIntegrationTest {
         for (int i = queuedBuildIdx; i < tcBuilds.size(); i++)
             tcBuilds.get(i).state = BuildRef.STATE_FINISHED;
 
-        teamcityIgnited.actualizeRecentBuilds();
+        teamcityIgnited.actualizeRecentBuildRefs();
 
 
         List<BuildRef> hist = srv.getBuildHistory(buildTypeId, branchName);

Reply via email to