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 2019/06/20 14:22:24 UTC

[ignite-teamcity-bot] 01/07: Trusted tests & suite history performance fixes

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

dpavlov pushed a commit to branch test-hist-performance
in repository https://gitbox.apache.org/repos/asf/ignite-teamcity-bot.git

commit e78c82ebd213425538c66bba6a25ae2ce9286257
Author: Dmitriy Pavlov <dp...@apache.org>
AuthorDate: Tue Jun 18 21:45:05 2019 +0300

    Trusted tests & suite history performance fixes
---
 .../ignite/tcbot/engine/chain/MultBuildRunCtx.java |  2 +-
 .../tcbot/engine/chain/TestCompactedMult.java      |  4 ++
 .../ignite/tcbot/engine/pr/PrChainsProcessor.java  |  4 +-
 .../apache/ignite/tcbot/engine/ui/DsSuiteUi.java   |  3 +-
 .../apache/ignite/tcignited/ITeamcityIgnited.java  |  2 +-
 .../ignite/tcignited/TeamcityIgnitedImpl.java      | 13 ++--
 .../apache/ignite/tcignited/build/FatBuildDao.java | 57 +++++++++++-----
 .../ignite/tcignited/build/SuiteHistory.java       |  7 ++
 .../ignite/tcignited/history/SuiteInvocation.java  | 47 +++++++++++++
 .../history/SuiteInvocationHistoryDao.java         | 76 ++++++++++++++++++++++
 10 files changed, 189 insertions(+), 26 deletions(-)

diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/MultBuildRunCtx.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/MultBuildRunCtx.java
index a794110..2207e2a 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/MultBuildRunCtx.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/MultBuildRunCtx.java
@@ -315,7 +315,7 @@ public class MultBuildRunCtx implements ISuiteResults {
         return CollectionUtil.top(res.values().stream(), 3, comparing).stream();
     }
 
-    public List<IMultTestOccurrence> getFailedTests() {
+    public List<TestCompactedMult> getFailedTests() {
         Map<Integer, TestCompactedMult> res = new HashMap<>();
 
         builds.forEach(singleBuildRunCtx -> {
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/TestCompactedMult.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/TestCompactedMult.java
index 908bc5f..792d5c6 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/TestCompactedMult.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/TestCompactedMult.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
+import javax.annotation.Nullable;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.TestCompacted;
 import org.apache.ignite.tcbot.persistence.IStringCompactor;
 import org.apache.ignite.tcignited.history.IRunHistSummary;
@@ -39,6 +40,9 @@ public class TestCompactedMult implements IMultTestOccurrence {
         this.compactor = compactor;
     }
 
+    @Nullable public Integer testName() {
+        return occurrences.isEmpty() ? null : occurrences.iterator().next().testName();
+    }
     /** {@inheritDoc} */
     @Override public String getName() {
         return occurrences.isEmpty() ? "" : occurrences.iterator().next().testName(compactor);
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/pr/PrChainsProcessor.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/pr/PrChainsProcessor.java
index 7d4b4d3..b3fb4bd 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/pr/PrChainsProcessor.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/pr/PrChainsProcessor.java
@@ -258,12 +258,14 @@ public class PrChainsProcessor {
             .failedChildSuites()
             .map((ctx) -> {
                 String normalizedBaseBranch = RunHistSync.normalizeBranch(baseBranch);
+                Integer baseBranchId = compactor.getStringIdIfPresent(normalizedBaseBranch);
                 IRunHistory statInBaseBranch = tcIgnited.getSuiteRunHist(ctx.suiteId(), normalizedBaseBranch);
+                Integer suiteId = compactor.getStringIdIfPresent(ctx.suiteId()); // can be inlined
 
                 String suiteComment = ctx.getPossibleBlockerComment(compactor, statInBaseBranch, tcIgnited.config());
 
                 List<DsTestFailureUi> failures = ctx.getFailedTests().stream().map(occurrence -> {
-                    IRunHistory stat = tcIgnited.getTestRunHist(occurrence.getName(), normalizedBaseBranch);
+                    IRunHistory stat = tcIgnited.getTestRunHist(occurrence.testName(), suiteId, baseBranchId);
 
                     String testBlockerComment = TestCompactedMult.getPossibleBlockerComment(stat);
 
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsSuiteUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsSuiteUi.java
index 8d25cf2..4a5ca06 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsSuiteUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsSuiteUi.java
@@ -33,6 +33,7 @@ import javax.annotation.Nullable;
 import org.apache.ignite.tcbot.common.util.UrlUtil;
 import org.apache.ignite.tcbot.engine.chain.IMultTestOccurrence;
 import org.apache.ignite.tcbot.engine.chain.MultBuildRunCtx;
+import org.apache.ignite.tcbot.engine.chain.TestCompactedMult;
 import org.apache.ignite.tcbot.engine.issue.EventTemplates;
 import org.apache.ignite.tcbot.engine.ui.BotUrls.GetBuildLog;
 import org.apache.ignite.tcignited.buildlog.ITestLogCheckResult;
@@ -187,7 +188,7 @@ public class DsSuiteUi extends DsHistoryStatUi {
         webToBuild = buildWebLinkToBuild(tcIgnited, suite);
 
         if (includeTests) {
-            List<IMultTestOccurrence> tests = suite.getFailedTests();
+            List<TestCompactedMult> tests = suite.getFailedTests();
             Function<IMultTestOccurrence, Float> function = foccur -> {
                 IRunHistory apply = tcIgnited.getTestRunHist(foccur.getName(), failRateNormalizedBranch);
 
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/ITeamcityIgnited.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/ITeamcityIgnited.java
index 91c4850..51429b9 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/ITeamcityIgnited.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/ITeamcityIgnited.java
@@ -209,7 +209,7 @@ public interface ITeamcityIgnited {
      * V.3.0 run history implementation based on scan of fat builds.
      *
      * @param testName Test name.
-     * @param suiteName Suite name.
+     * @param suiteName Suite name. Null suite name means suite not found
      * @param branchName Branch name.
      */
     @Nullable public IRunHistory getTestRunHist(int testName, @Nullable Integer suiteName, @Nullable Integer branchName);
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/TeamcityIgnitedImpl.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/TeamcityIgnitedImpl.java
index a6e37e5..eb14115 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/TeamcityIgnitedImpl.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/TeamcityIgnitedImpl.java
@@ -32,7 +32,6 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.OptionalInt;
 import java.util.Set;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -52,8 +51,6 @@ import org.apache.ignite.ci.teamcity.ignited.change.ChangeCompacted;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeDao;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeSync;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
-import org.apache.ignite.tcignited.mute.MuteDao;
-import org.apache.ignite.tcignited.mute.MuteSync;
 import org.apache.ignite.ci.teamcity.ignited.runhist.InvocationData;
 import org.apache.ignite.tcbot.common.conf.ITcServerConfig;
 import org.apache.ignite.tcbot.common.interceptor.AutoProfiling;
@@ -70,6 +67,8 @@ import org.apache.ignite.tcignited.history.IRunHistory;
 import org.apache.ignite.tcignited.history.IRunStat;
 import org.apache.ignite.tcignited.history.RunHistCompactedDao;
 import org.apache.ignite.tcignited.history.RunHistSync;
+import org.apache.ignite.tcignited.mute.MuteDao;
+import org.apache.ignite.tcignited.mute.MuteSync;
 import org.apache.ignite.tcservice.ITeamcity;
 import org.apache.ignite.tcservice.ITeamcityConn;
 import org.apache.ignite.tcservice.model.agent.Agent;
@@ -434,11 +433,15 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
         return runHistCompactedDao.getSuiteRunHist(srvIdMaskHigh, suiteId, branch);
     }
 
-    @Nullable @Override
-    public IRunHistory getTestRunHist(int testName, @Nullable Integer suiteName, @Nullable Integer branchName) {
+    /** {@inheritDoc} */
+    @Nullable @Override public IRunHistory getTestRunHist(int testName, @Nullable Integer suiteName,
+        @Nullable Integer branchName) {
         if (suiteName == null || branchName == null)
             return null;
 
+        if (testName < 0 || suiteName < 0 || branchName < 0)
+            return null;
+
         Supplier<Set<Integer>> supplier = () -> {
             String btId = compactor.getStringFromId(suiteName);
             String branchId = compactor.getStringFromId(branchName);
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 ad08fe3..f83521b 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
@@ -21,6 +21,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.cache.CacheBuilder;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -53,6 +54,8 @@ import org.apache.ignite.tcbot.common.interceptor.AutoProfiling;
 import org.apache.ignite.tcbot.persistence.CacheConfigs;
 import org.apache.ignite.tcbot.persistence.IStringCompactor;
 import org.apache.ignite.tcignited.history.IRunHistory;
+import org.apache.ignite.tcignited.history.SuiteInvocation;
+import org.apache.ignite.tcignited.history.SuiteInvocationHistoryDao;
 import org.apache.ignite.tcservice.model.changes.ChangesList;
 import org.apache.ignite.tcservice.model.result.Build;
 import org.apache.ignite.tcservice.model.result.problems.ProblemOccurrence;
@@ -78,13 +81,11 @@ public class FatBuildDao {
     /** Builds cache. */
     private IgniteCache<Long, FatBuildCompacted> buildsCache;
 
-
-    /** Suite history cache. */
-    private IgniteCache<RunHistKey, SuiteHistory> suiteHistory;
-
     /** Compactor. */
     @Inject private IStringCompactor compactor;
 
+    @Inject SuiteInvocationHistoryDao historyDao;
+
     /**
      * Non persistence cache for all suite RunHistory for particular branch.
      * RunHistKey(ServerId||BranchId||suiteId)-> Build reference
@@ -103,6 +104,8 @@ public class FatBuildDao {
     public FatBuildDao init() {
         buildsCache = igniteProvider.get().getOrCreateCache(CacheConfigs.getCacheV2Config(TEAMCITY_FAT_BUILD_CACHE_NAME));
 
+        historyDao.init();
+
         return this;
     }
 
@@ -172,7 +175,7 @@ public class FatBuildDao {
     }
 
     /**
-     * @param srvIdMaskHigh Server id mask high.
+     * @param srvIdMaskHigh Server id mask to be placed at high bits of the key.
      * @param buildId Build id.
      */
     public static long buildIdToCacheKey(int srvIdMaskHigh, int buildId) {
@@ -197,10 +200,7 @@ public class FatBuildDao {
     public Map<Long, FatBuildCompacted> getAllFatBuilds(int srvIdMaskHigh, Collection<Integer> buildsIds) {
         Preconditions.checkNotNull(buildsCache, "init() was not called");
 
-        Set<Long> ids = buildsIds.stream()
-            .filter(Objects::nonNull)
-            .map(buildId -> buildIdToCacheKey(srvIdMaskHigh, buildId))
-            .collect(Collectors.toSet());
+        Set<Long> ids = buildsIdsToCacheKeys(srvIdMaskHigh, buildsIds);
 
         return buildsCache.getAll(ids);
     }
@@ -223,19 +223,37 @@ public class FatBuildDao {
             .filter(entry -> isKeyForServer(entry.getKey(), srvId));
     }
 
+    /**
+     * @param srvIdMaskHigh Server id mask to be placed at high bits in the key.
+     * @param buildIdsSupplier Latest actual Build ids supplier.
+     * @param testName Test name.
+     * @param suiteName Suite name.
+     * @param branchName Branch name.
+     */
     public IRunHistory getTestRunHist(int srvIdMaskHigh,
         Supplier<Set<Integer>> buildIdsSupplier, int testName, int suiteName, int branchName) {
 
-
         RunHistKey runHistKey = new RunHistKey(srvIdMaskHigh, suiteName, branchName);
 
-        SuiteHistory history;
+        SuiteHistory hist;
 
         try {
-            history = runHistInMemCache.get(runHistKey,
+            hist = runHistInMemCache.get(runHistKey,
                 () -> {
+                    Map<Integer, SuiteInvocation> suiteRunHist = historyDao.getSuiteRunHist(srvIdMaskHigh, suiteName, branchName);// todo RunHistSync.normalizeBranch();
+
                     Set<Integer> buildIds = determineLatestBuilds(buildIdsSupplier);
 
+                    HashSet<Integer> missedBuilds = new HashSet<>(buildIds);
+
+                    missedBuilds.removeAll(suiteRunHist.keySet());
+
+                    if (!missedBuilds.isEmpty()) {
+
+                    }
+
+                    Set<Long> cacheKeys = buildsIdsToCacheKeys(srvIdMaskHigh, buildIds);
+
                     return calcSuiteHistory(srvIdMaskHigh, buildIds);
                 });
         }
@@ -243,12 +261,12 @@ public class FatBuildDao {
             throw ExceptionUtil.propagateException(e);
         }
 
-        return history.testsHistory.get(testName);
+        return hist.testsHistory.get(testName);
     }
 
     @AutoProfiling
     protected SuiteHistory calcSuiteHistory(int srvIdMaskHigh, Set<Integer> buildIds) {
-        Set<Long> cacheKeys = buildIds.stream().map(id -> buildIdToCacheKey(srvIdMaskHigh, id)).collect(Collectors.toSet());
+        Set<Long> cacheKeys = buildsIdsToCacheKeys(srvIdMaskHigh, buildIds);
 
         int successStatusStrId = compactor.getStringId(TestOccurrence.STATUS_SUCCESS);
 
@@ -259,13 +277,13 @@ public class FatBuildDao {
         SuiteHistory hist = new SuiteHistory();
 
         map.values().forEach(
-            res-> {
-                if(res==null)
+            res -> {
+                if (res == null)
                     return;
 
                 Map<Integer, Invocation> invocationMap = res.get();
 
-                if(invocationMap == null)
+                if (invocationMap == null)
                     return;
 
                 invocationMap.forEach((k, v) -> {
@@ -286,6 +304,11 @@ public class FatBuildDao {
         return hist;
     }
 
+    private static Set<Long> buildsIdsToCacheKeys(int srvIdMaskHigh, Collection<Integer> stream) {
+        return stream.stream()
+            .filter(Objects::nonNull).map(id -> buildIdToCacheKey(srvIdMaskHigh, id)).collect(Collectors.toSet());
+    }
+
     @AutoProfiling
     protected Set<Integer> determineLatestBuilds(Supplier<Set<Integer>> buildIdsSupplier) {
         return buildIdsSupplier.get();
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/SuiteHistory.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/SuiteHistory.java
index 8969736..e9e9b22 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/SuiteHistory.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/SuiteHistory.java
@@ -20,14 +20,21 @@ package org.apache.ignite.tcignited.build;
 import java.util.HashMap;
 import java.util.Map;
 
+import java.util.TreeMap;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.ci.teamcity.ignited.runhist.RunHistCompacted;
 import org.apache.ignite.internal.binary.BinaryObjectExImpl;
+import org.apache.ignite.tcignited.history.SuiteInvocation;
 
+/**
+ * Suite run history summary.
+ */
 public class SuiteHistory {
     /** Tests history: Test name ID->RunHistory */
     Map<Integer, RunHistCompacted> testsHistory = new HashMap<>();
 
+    Map<Integer, SuiteInvocation> invocationMap = new TreeMap<>();
+
     public int size(Ignite ignite) {
         BinaryObjectExImpl binary = ignite.binary().toBinary(this);
         return binary.length();
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/SuiteInvocation.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/SuiteInvocation.java
new file mode 100644
index 0000000..bd3aa80
--- /dev/null
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/SuiteInvocation.java
@@ -0,0 +1,47 @@
+/*
+ * 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.tcignited.history;
+
+import java.util.List;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.ci.teamcity.ignited.runhist.Invocation;
+import org.apache.ignite.tcbot.persistence.Persisted;
+
+/**
+ * Shorter verison of FatBuild with less data: created only if run history was required,
+ * has time limitation of MAX_DAYS, may have TTL.
+ */
+@Persisted
+public class SuiteInvocation {
+    /** Server ID for queries */
+    @QuerySqlField(orderedGroups = {@QuerySqlField.Group(name = "serverSuiteBranch", order = 0)})
+    private int srvId;
+
+    /** Suite name for queries */
+    @QuerySqlField(orderedGroups = {@QuerySqlField.Group(name = "serverSuiteBranch", order = 1)})
+    private int suiteName;
+
+    /** Teamcity branch name for queries */
+    @QuerySqlField(orderedGroups = {@QuerySqlField.Group(name = "serverSuiteBranch", order = 2)})
+    private int normalizedBranchName;
+
+    Invocation suite;
+
+    List<Invocation> tests;
+
+    Long buildStartTime;
+}
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/SuiteInvocationHistoryDao.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/SuiteInvocationHistoryDao.java
new file mode 100644
index 0000000..1cd066a
--- /dev/null
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/SuiteInvocationHistoryDao.java
@@ -0,0 +1,76 @@
+/*
+ * 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.tcignited.history;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.cache.Cache;
+import javax.cache.expiry.AccessedExpiryPolicy;
+import javax.cache.expiry.Duration;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.query.QueryCursor;
+import org.apache.ignite.cache.query.SqlQuery;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.tcbot.persistence.CacheConfigs;
+import org.apache.ignite.tcignited.buildref.BuildRefDao;
+
+import static java.util.concurrent.TimeUnit.HOURS;
+
+/**
+ * Suite invocation history access object.
+ */
+public class SuiteInvocationHistoryDao {
+    /** Ignite provider. */
+    @Inject
+    private Provider<Ignite> igniteProvider;
+
+    /** Suite history cache. */
+    private IgniteCache<Long, SuiteInvocation> suiteHistory;
+
+    public void init() {
+        CacheConfiguration<Long , SuiteInvocation> ccfg = CacheConfigs.getCacheV2Config("suiteHistory");
+        ccfg.setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(new Duration(HOURS, 12)));
+        ccfg.setEagerTtl(true);
+
+        ccfg.setQueryEntities(Collections.singletonList(new QueryEntity(Long.class, SuiteInvocation.class)));
+
+        Ignite ignite = igniteProvider.get();
+
+        suiteHistory = ignite.getOrCreateCache(ccfg);
+    }
+
+    public Map<Integer, SuiteInvocation> getSuiteRunHist(int srvId, int suiteName, int normalizedBranchName) {
+        java.util.Map<Integer, SuiteInvocation> map = new HashMap<>();
+        try (QueryCursor<Cache.Entry<Long, SuiteInvocation>> qryCursor = suiteHistory.query(
+            new SqlQuery<Long, SuiteInvocation>(SuiteInvocation.class, "srvId = ? and suiteName = ? and normalizedBranchName = ?")
+                .setArgs(srvId, suiteName, normalizedBranchName))) {
+
+            for (Cache.Entry<Long, SuiteInvocation> next : qryCursor) {
+                Long key = next.getKey();
+                int buildId = BuildRefDao.cacheKeyToBuildId(key);
+                map.put(buildId, next.getValue());
+            }
+        }
+
+        return map;
+    }
+}