You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by dp...@apache.org on 2018/10/11 18:59:22 UTC

[ignite-teamcity-bot] 02/02: IGNITE-9848: Compressed fat build development

This is an automated email from the ASF dual-hosted git repository.

dpavlov pushed a commit to branch ignite-9848-load-all-builds
in repository https://gitbox.apache.org/repos/asf/ignite-teamcity-bot.git

commit 4e2b0c46b5a2b2683c11fec49283dd274fe48c59
Author: Dmitriy Pavlov <dp...@apache.org>
AuthorDate: Thu Oct 11 21:59:01 2018 +0300

    IGNITE-9848: Compressed fat build development
---
 .../apache/ignite/ci/analysis/MultBuildRunCtx.java |   3 -
 .../org/apache/ignite/ci/jobs/CheckQueueJob.java   |   2 +-
 .../apache/ignite/ci/tcmodel/conf/BuildType.java   |  21 +++++
 .../org/apache/ignite/ci/tcmodel/result/Build.java |  58 +++++++++---
 .../ci/teamcity/ignited/BuildRefCompacted.java     |   8 +-
 .../ci/teamcity/ignited/FatBuildCompacted.java     |  67 +++++++++++++
 .../ignite/ci/teamcity/ignited/FatBuildDao.java    | 105 +++++++++++++++++++++
 .../ci/teamcity/ignited/IStringCompactor.java      |   8 +-
 .../ci/teamcity/ignited/ITeamcityIgnited.java      |   9 ++
 .../ci/teamcity/ignited/IgniteStringCompactor.java |   3 +-
 .../ci/teamcity/ignited/TeamcityIgnitedImpl.java   |  12 +--
 .../org/apache/ignite/ci/util/ObjectInterner.java  |   8 +-
 .../java/org/apache/ignite/ci/util/XmlUtil.java    |  36 ++++---
 .../ignited/IgnitedTcInMemoryIntegrationTest.java  |  59 +++++++++++-
 14 files changed, 354 insertions(+), 45 deletions(-)

diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/MultBuildRunCtx.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/MultBuildRunCtx.java
index 6a338a8..29ecc76 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/MultBuildRunCtx.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/MultBuildRunCtx.java
@@ -75,9 +75,6 @@ public class MultBuildRunCtx implements ISuiteResults {
         builds.add(ctx);
     }
 
-    /** Thread dump short file name */
-    @Nullable private Integer threadDumpFileIdx;
-
     /** Currently running builds */
     @Nullable private CompletableFuture<Long> runningBuildCount;
 
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
index f1cdf72..e5733d7 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
@@ -165,7 +165,7 @@ public class CheckQueueJob implements Runnable {
         int running = 0;
 
         for (Agent agent : agents) {
-            if (agent.getBuild() != null) //  || !STATE_RUNNING.equals(agent.getBuild().status)
+            if (agent.getBuild() != null) //  || !STATE_RUNNING.equals(agent.getFatBuild().status)
                 ++running;
         }
 
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/conf/BuildType.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/conf/BuildType.java
index a7998a6..234b441 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/conf/BuildType.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/conf/BuildType.java
@@ -48,4 +48,25 @@ public class BuildType {
     public String getProjectId() {
         return projectId;
     }
+
+    /**
+     * @param id New id.
+     */
+    public void id(String id) {
+        this.id = id;
+    }
+
+    /**
+     * @param name Name.
+     */
+    public void name(String name) {
+        this.name = name;
+    }
+
+    /**
+     * @param projectId Project id.
+     */
+    public void projectId(String projectId) {
+        this.projectId = projectId;
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Build.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Build.java
index 443610c..fc43474 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Build.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Build.java
@@ -28,13 +28,17 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
 import org.apache.ignite.ci.analysis.IVersionedEntity;
 import org.apache.ignite.ci.tcmodel.changes.ChangesList;
 import org.apache.ignite.ci.tcmodel.changes.ChangesListRef;
 import org.apache.ignite.ci.tcmodel.conf.BuildType;
 import org.apache.ignite.ci.tcmodel.hist.BuildRef;
+import org.apache.ignite.ci.util.ExceptionUtil;
 import org.jetbrains.annotations.NotNull;
 
+import static org.apache.ignite.ci.util.ExceptionUtil.propagateException;
+
 /**
  * Build from history with test and problems references
  */
@@ -42,11 +46,16 @@ import org.jetbrains.annotations.NotNull;
 @XmlAccessorType(XmlAccessType.FIELD)
 public class Build extends BuildRef implements IVersionedEntity {
     public static final int LATEST_VERSION = 2;
-    @XmlElement(name = "buildType") BuildType buildType;
+
+    /** Format local. */
+    @XmlTransient private static ThreadLocal<SimpleDateFormat> fmtLoc
+        = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd'T'HHmmssZ"));
+
+    @XmlElement(name = "buildType") private BuildType buildType;
 
     @XmlElement public String queuedDate;
-    @XmlElement public String startDate;
-    @XmlElement public String finishDate;
+    @XmlElement private String startDate;
+    @XmlElement private String finishDate;
 
     @XmlElement(name = "build")
     @XmlElementWrapper(name = "snapshot-dependencies")
@@ -69,6 +78,7 @@ public class Build extends BuildRef implements IVersionedEntity {
     /** Information about build triggering. */
     @XmlElement(name = "triggered") private Triggered triggered;
 
+    @XmlTransient
     @SuppressWarnings("FieldCanBeLocal") public Integer _version = LATEST_VERSION;
 
     @NotNull public static Build createFakeStub() {
@@ -83,28 +93,43 @@ public class Build extends BuildRef implements IVersionedEntity {
         return buildType == null ? null : buildType.getName();
     }
 
-    public String getFinishDateDdMmYyyy() {
-        Date parse = getFinishDate();
-        return new SimpleDateFormat("dd.MM.yyyy").format(parse);
-    }
-
+    /**
+     *
+     */
     public Date getFinishDate() {
         return getDate(finishDate);
     }
 
+    /**
+     *
+     */
     public Date getStartDate() {
         return getDate(startDate);
     }
 
+    /**
+     * @param ts Timestamp.
+     */
+    public void setStartDateTs(long ts) {
+        startDate = ts < 0 ? null : fmtLoc.get().format(new Date(ts));
+    }
+
+    /**
+     * @param ts Timestamp.
+     */
+    public void setFinishDateTs(long ts) {
+        finishDate = ts < 0 ? null : fmtLoc.get().format(new Date(ts));
+    }
+
+    /**
+     * @param date Date as string.
+     */
     private Date getDate(String date) {
         try {
-            if (date == null)
-                return null;
-            SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
-            return f.parse(date);
+            return date == null ? null : fmtLoc.get().parse(date);
         }
         catch (ParseException e) {
-            throw new IllegalStateException(e);
+            throw propagateException(e);
         }
     }
 
@@ -137,4 +162,11 @@ public class Build extends BuildRef implements IVersionedEntity {
     public void setTriggered(Triggered triggered) {
         this.triggered = triggered;
     }
+
+    /**
+     * @param type Type.
+     */
+    public void setBuildType(BuildType type) {
+        buildType = type;
+    }
 }
\ No newline at end of file
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefCompacted.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefCompacted.java
index 77eec6d..3becbe4 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefCompacted.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefCompacted.java
@@ -61,13 +61,17 @@ public class BuildRefCompacted {
     public BuildRef toBuildRef(IStringCompactor compactor) {
         BuildRef res = new BuildRef();
 
+        fillBuildRefFields(compactor, res);
+
+        return res;
+    }
+
+    protected void fillBuildRefFields(IStringCompactor compactor, BuildRef res) {
         res.setId(id < 0 ? null : id);
         res.buildTypeId = compactor.getStringFromId(buildTypeId);
         res.branchName = compactor.getStringFromId(branchName);
         res.status = compactor.getStringFromId(status);
         res.state = compactor.getStringFromId(state);
-
-        return res;
     }
 
     /** {@inheritDoc} */
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/FatBuildCompacted.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/FatBuildCompacted.java
index 10fd975..7254abe 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/FatBuildCompacted.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/FatBuildCompacted.java
@@ -18,6 +18,9 @@ package org.apache.ignite.ci.teamcity.ignited;
 
 import org.apache.ignite.ci.analysis.IVersionedEntity;
 import org.apache.ignite.ci.db.Persisted;
+import org.apache.ignite.ci.tcmodel.conf.BuildType;
+import org.apache.ignite.ci.tcmodel.hist.BuildRef;
+import org.apache.ignite.ci.tcmodel.result.Build;
 
 /**
  *
@@ -30,6 +33,15 @@ public class FatBuildCompacted extends BuildRefCompacted implements IVersionedEn
     /** Entity fields version. */
     private short _ver;
 
+    /** Start date. The number of milliseconds since January 1, 1970, 00:00:00 GMT */
+    private long startDate;
+
+    /** Finish date. The number of milliseconds since January 1, 1970, 00:00:00 GMT */
+    private long finishDate;
+
+    private int projectId = -1;
+    private int name = -1;
+
     /** {@inheritDoc} */
     @Override public int version() {
         return _ver;
@@ -39,4 +51,59 @@ public class FatBuildCompacted extends BuildRefCompacted implements IVersionedEn
     @Override public int latestVersion() {
         return LATEST_VERSION;
     }
+
+    /**
+     * Default constructor.
+     */
+    public FatBuildCompacted() {
+    }
+
+    /**
+     * @param compactor Compactor.
+     * @param ref Reference.
+     */
+    public FatBuildCompacted(IStringCompactor compactor, Build ref) {
+        super(compactor, ref);
+
+        startDate = ref.getStartDate() == null ? -1L : ref.getStartDate().getTime();
+        finishDate = ref.getFinishDate() == null ? -1L : ref.getFinishDate().getTime();
+
+        BuildType type = ref.getBuildType();
+        if (type != null) {
+            projectId = compactor.getStringId(type.getProjectId());
+            name = compactor.getStringId(type.getName());
+        }
+    }
+
+    /**
+     * @param compactor Compacter.
+     */
+    public Build toBuild(IStringCompactor compactor) {
+        Build res = new Build();
+
+        fillBuildRefFields(compactor, res);
+
+        fillBuildFields(compactor, res);
+
+        return res;
+    }
+
+    /**
+     * @param compactor Compactor.
+     * @param res Response.
+     */
+    private void fillBuildFields(IStringCompactor compactor, Build res) {
+        if (startDate > 0)
+            res.setStartDateTs(startDate);
+
+        if (finishDate > 0)
+            res.setFinishDateTs(finishDate);
+
+        BuildType type = new BuildType();
+        type.id(res.buildTypeId());
+        type.name(compactor.getStringFromId(name));
+        type.projectId(compactor.getStringFromId(projectId));
+        res.setBuildType(type);
+    }
+
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/FatBuildDao.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/FatBuildDao.java
new file mode 100644
index 0000000..e0706d1
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/FatBuildDao.java
@@ -0,0 +1,105 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.ci.db.TcHelperDb;
+import org.apache.ignite.ci.tcmodel.hist.BuildRef;
+import org.apache.ignite.ci.tcmodel.result.Build;
+import org.apache.ignite.configuration.CacheConfiguration;
+
+/**
+ *
+ */
+public class FatBuildDao {
+    /** Cache name */
+    public static final String TEAMCITY_FAT_BUILD_CACHE_NAME = "teamcityFatBuild";
+
+    /** Ignite provider. */
+    @Inject private Provider<Ignite> igniteProvider;
+
+    /** Builds cache. */
+    private IgniteCache<Long, FatBuildCompacted> buildsCache;
+
+    /** Compactor. */
+    @Inject private IStringCompactor compactor;
+
+    /**
+     *
+     */
+    public void init() {
+        buildsCache = igniteProvider.get().getOrCreateCache(TcHelperDb.getCacheV2Config(TEAMCITY_FAT_BUILD_CACHE_NAME));
+    }
+
+    /**
+     * @param srvIdMaskHigh Server id mask high.
+     * @param ghData Gh data.
+     */
+    public int saveChunk(long srvIdMaskHigh, List<Build> ghData) {
+        Set<Long> ids = ghData.stream().map(BuildRef::getId)
+            .filter(Objects::nonNull)
+            .map(buildId -> buildIdToCacheKey(srvIdMaskHigh, buildId))
+            .collect(Collectors.toSet());
+
+        Map<Long, FatBuildCompacted> existingEntries = buildsCache.getAll(ids);
+        Map<Long, FatBuildCompacted> entriesToPut = new TreeMap<>();
+
+        List<FatBuildCompacted> collect = ghData.stream()
+            .map(ref -> new FatBuildCompacted(compactor, ref))
+            .collect(Collectors.toList());
+
+        for (FatBuildCompacted next : collect) {
+            long cacheKey = buildIdToCacheKey(srvIdMaskHigh, next.id());
+            FatBuildCompacted buildPersisted = existingEntries.get(cacheKey);
+
+            if (buildPersisted == null || !buildPersisted.equals(next))
+                entriesToPut.put(cacheKey, next);
+        }
+
+        int size = entriesToPut.size();
+        if (size != 0)
+            buildsCache.putAll(entriesToPut);
+
+        return size;
+    }
+
+    /**
+     * @param srvIdMaskHigh Server id mask high.
+     * @param buildId Build id.
+     */
+    private long buildIdToCacheKey(long srvIdMaskHigh, int buildId) {
+        return (long)buildId | srvIdMaskHigh << 32;
+    }
+
+    /**
+     * @param srvIdMaskHigh Server id mask high.
+     * @param buildId Build id.
+     */
+    public FatBuildCompacted getFatBuild(int srvIdMaskHigh, int buildId) {
+        return buildsCache.get(buildIdToCacheKey(srvIdMaskHigh, buildId));
+    }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/IStringCompactor.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/IStringCompactor.java
index 59c2986..0ab6d96 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/IStringCompactor.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/IStringCompactor.java
@@ -16,6 +16,9 @@
  */
 package org.apache.ignite.ci.teamcity.ignited;
 
+/**
+ *
+ */
 public interface IStringCompactor {
     /**
      * @param val Value.
@@ -27,5 +30,8 @@ public interface IStringCompactor {
      */
     public String getStringFromId(int id);
 
-    public Integer getStringIdIfPresent(String id);
+    /**
+     * @param val Id.
+     */
+    public Integer getStringIdIfPresent(String val);
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java
index 470d5d1..cfba852 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java
@@ -48,4 +48,13 @@ public interface ITeamcityIgnited {
      * @param queueAtTop Put at the top of the build queue.
      */
     public Build triggerBuild(String buildTypeId, String branchName, boolean cleanRebuild, boolean queueAtTop);
+
+
+    /**
+     * @param srvId Server id.
+     * @return integer representation of server ID.
+     */
+    public static int serverIdToInt(String srvId) {
+        return Math.abs(srvId.hashCode());
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/IgniteStringCompactor.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/IgniteStringCompactor.java
index 5cd2a0b..906b0d5 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/IgniteStringCompactor.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/IgniteStringCompactor.java
@@ -34,6 +34,7 @@ import org.apache.ignite.cache.query.SqlQuery;
 import org.apache.ignite.cache.query.annotations.QuerySqlField;
 import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.util.ExceptionUtil;
+import org.apache.ignite.ci.util.ObjectInterner;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.jetbrains.annotations.NotNull;
 
@@ -147,7 +148,7 @@ public class IgniteStringCompactor implements IStringCompactor {
 
         qryCursor.close();
 
-        return next.getValue().val;
+        return ObjectInterner.internString(next.getValue().val);
     }
 
     /** {@inheritDoc} */
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 746ef38..d8b295d 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
@@ -36,10 +36,10 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
     /** Pure HTTP Connection API. */
     private ITeamcityConn conn;
 
-
     /** Scheduler. */
     @Inject private IScheduler scheduler;
 
+    /** Build reference DAO. */
     @Inject private BuildRefDao buildRefDao;
 
     /** Server ID mask for cache Entries. */
@@ -50,7 +50,7 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
         this.srvId = srvId;
         this.conn = conn;
 
-        srvIdMaskHigh = Math.abs(srvId.hashCode());
+        srvIdMaskHigh = ITeamcityIgnited.serverIdToInt(srvId);
         buildRefDao.init(); //todo init somehow in auto
     }
 
@@ -81,7 +81,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
-        runAсtualizeBuilds(srvId, false, build.getId());
+        runActializeBuildRefs(srvId, false, build.getId());
 
         return build;
     }
@@ -90,7 +90,7 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
      *
      */
     private void actualizeRecentBuilds() {
-        runAсtualizeBuilds(srvId, false, null);
+        runActializeBuildRefs(srvId, false, null);
 
         // schedule full resync later
         scheduler.invokeLater(this::sheduleResync, 60, TimeUnit.SECONDS);
@@ -108,7 +108,7 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
      *
      */
     private void fullReindex() {
-        runAсtualizeBuilds(srvId, true, null);
+        runActializeBuildRefs(srvId, true, null);
     }
 
     /**
@@ -118,7 +118,7 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
      */
     @MonitoredTask(name = "Actualize BuildRefs, full resync", nameExtArgIndex = 1)
     @AutoProfiling
-    protected String runAсtualizeBuilds(String srvId, boolean fullReindex,
+    protected String runActializeBuildRefs(String srvId, boolean fullReindex,
         @Nullable Integer buildIdCanFinish) {
         AtomicReference<String> outLinkNext = new AtomicReference<>();
         List<BuildRef> tcDataFirstPage = conn.getBuildRefs(null, outLinkNext);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/ObjectInterner.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/ObjectInterner.java
index b58fe96..9834d5c 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/ObjectInterner.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/ObjectInterner.java
@@ -32,6 +32,7 @@ import java.util.concurrent.atomic.AtomicInteger;
  * Class with util method for custom strings deduplication (intern analogue).
  */
 public class ObjectInterner {
+    /** String cache. */
     private static final LoadingCache<String, String> stringCache
         = CacheBuilder
         .<String, String>newBuilder()
@@ -39,13 +40,16 @@ public class ObjectInterner {
         .initialCapacity(67537)
         .build(
             new CacheLoader<String, String>() {
-                @Override public String load(String key) throws Exception {
+                @Override public String load(String key) {
                     return key;
                 }
             }
         );
 
-    private static String internString(String str) {
+    /**
+     * @param str String.
+     */
+    public static String internString(String str) {
         if (str == null)
             return null;
 
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java
index 5eb1510..e1e058a 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java
@@ -18,9 +18,11 @@
 package org.apache.ignite.ci.util;
 
 import java.io.Reader;
+import java.io.StringWriter;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
 import javax.xml.bind.Unmarshaller;
 
 /**
@@ -31,24 +33,34 @@ public class XmlUtil {
     private static ConcurrentHashMap<Class, JAXBContext> cachedCtx = new ConcurrentHashMap<>();
 
     public static <T> T load(Class<T> tCls, Reader reader) throws JAXBException {
-        final JAXBContext ctx = cachedCtx.computeIfAbsent(tCls, c -> {
-            try {
-                return JAXBContext.newInstance(tCls);
-            }
-            catch (JAXBException e) {
-                throw new RuntimeException(e);
-            }
-        });
-        Unmarshaller unmarshaller = ctx.createUnmarshaller();
+        Unmarshaller unmarshaller = getContext(tCls).createUnmarshaller();
         T unmarshal = (T)unmarshaller.unmarshal(reader);
 
-        int interned = ObjectInterner.internFields(unmarshal);
-       // if (interned > 0)
-       //     System.out.println("Strings saved: " + interned);
+        ObjectInterner.internFields(unmarshal);
 
         return unmarshal;
     }
 
+    public static String save(Object obj) throws JAXBException {
+        Marshaller marshaller = getContext(obj.getClass()).createMarshaller();
+        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+        StringWriter writer = new StringWriter();
+        marshaller.marshal(obj, writer);
+
+        return writer.toString();
+    }
+
+    private static <T> JAXBContext getContext(Class<T> tCls) {
+        return cachedCtx.computeIfAbsent(tCls, c -> {
+                try {
+                    return JAXBContext.newInstance(tCls);
+                }
+                catch (JAXBException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+    }
+
     /**
      * @param t Text to process.
      */
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 0cd34d5..7a7809e 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
@@ -20,15 +20,24 @@ import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Collections;
 import java.util.List;
+import javax.xml.bind.JAXBException;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.Ignition;
 import org.apache.ignite.ci.di.scheduler.DirectExecNoWaitSheduler;
 import org.apache.ignite.ci.di.scheduler.IScheduler;
+import org.apache.ignite.ci.tcmodel.conf.BuildType;
 import org.apache.ignite.ci.tcmodel.hist.BuildRef;
+import org.apache.ignite.ci.tcmodel.result.Build;
 import org.apache.ignite.ci.teamcity.pure.ITeamcityHttpConnection;
 import org.apache.ignite.ci.user.ICredentialsProv;
+import org.apache.ignite.ci.util.XmlUtil;
 import org.jetbrains.annotations.NotNull;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -54,16 +63,15 @@ public class IgnitedTcInMemoryIntegrationTest {
      *
      */
     @BeforeClass
-    public static void startIgnite(){
+    public static void startIgnite() {
         ignite = Ignition.start();
     }
 
-
     /**
      *
      */
     @AfterClass
-    public static void stopIgnite(){
+    public static void stopIgnite() {
         ignite.close();
     }
 
@@ -72,7 +80,7 @@ public class IgnitedTcInMemoryIntegrationTest {
         ITeamcityHttpConnection http = Mockito.mock(ITeamcityHttpConnection.class);
 
         when(http.sendGet(anyString(), anyString())).thenAnswer(
-            (invocationOnMock)->{
+            (invocationOnMock) -> {
                 String url = invocationOnMock.getArgument(1);
 
                 if (url.contains("/app/rest/latest/builds?locator=defaultFilter:false,count:1000,start:1000"))
@@ -132,4 +140,47 @@ public class IgnitedTcInMemoryIntegrationTest {
 
         return mock;
     }
+
+    @Test
+    public void testFatBuild() throws JAXBException, IOException {
+        InputStream stream = getClass().getResourceAsStream("/build.xml");
+        Build refBuild = XmlUtil.load(Build.class, new InputStreamReader(stream));
+        Injector injector = Guice.createInjector(new AbstractModule() {
+            @Override protected void configure() {
+                bind(Ignite.class).toInstance(ignite);
+                bind(IStringCompactor.class).to(IgniteStringCompactor.class);
+            }
+        });
+
+        FatBuildDao instance = injector.getInstance(FatBuildDao.class);
+        instance.init();
+
+        int srvIdMaskHigh = ITeamcityIgnited.serverIdToInt(APACHE);
+        int i = instance.saveChunk(srvIdMaskHigh, Collections.singletonList(refBuild));
+        assertEquals(1, i);
+
+        FatBuildCompacted fatBuild = instance.getFatBuild(srvIdMaskHigh, 2039380);
+
+        Build actBuild = fatBuild.toBuild(injector.getInstance(IStringCompactor.class));
+
+        String save = XmlUtil.save(actBuild);
+
+        System.out.println(save);
+
+        FileWriter writer = new FileWriter("src/test/resources/build2.xml");
+        writer.write(save);
+        writer.close();
+
+        assertEquals(refBuild.getId(), actBuild.getId());
+        assertEquals(refBuild.status(), actBuild.status());
+        assertEquals(refBuild.state(), actBuild.state());
+        assertEquals(refBuild.buildTypeId(), actBuild.buildTypeId());
+        assertEquals(refBuild.getStartDate(), actBuild.getStartDate());
+        assertEquals(refBuild.getFinishDate(), actBuild.getFinishDate());
+        BuildType refBt = refBuild.getBuildType();
+        BuildType actBt = actBuild.getBuildType();
+        assertEquals(refBt.getName(), actBt.getName());
+        assertEquals(refBt.getProjectId(), actBt.getProjectId());
+        assertEquals(refBt.getId(), actBt.getId());
+    }
 }