blob: 737789bdbb62884f5e5b4c3e1c6ed38803487acd [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed 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 com.google.devtools.build.lib.runtime;
import com.google.common.collect.ConcurrentHashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multiset;
import com.google.devtools.build.lib.actions.ActionResult;
import com.google.devtools.build.lib.actions.SpawnResult;
import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.concurrent.ThreadSafe;
/** Collects results from SpawnResult. */
@ThreadSafe
public class SpawnStats {
private static final ImmutableList<String> REPORT_FIRST =
ImmutableList.of("disk cache hit", "remote cache hit");
private final ConcurrentHashMultiset<String> runners = ConcurrentHashMultiset.create();
private final ConcurrentHashMap<String, String> runnerExecKinds = new ConcurrentHashMap<>();
private final AtomicLong totalWallTimeMillis = new AtomicLong();
private final AtomicInteger totalNumberOfActions = new AtomicInteger();
private int actionCacheHitCount = 0;
public void countActionResult(ActionResult actionResult) {
for (SpawnResult r : actionResult.spawnResults()) {
storeRunnerExecKind(r);
countRunnerName(r.getRunnerName());
totalWallTimeMillis.addAndGet(r.getMetrics().executionWallTimeInMs());
}
}
public void countRunnerName(String runner) {
runners.add(runner);
}
private void storeRunnerExecKind(SpawnResult r) {
String name = r.getRunnerName();
String execKind = r.getMetrics().execKind().toString();
runnerExecKinds.put(name, execKind);
}
public void incrementActionCount() {
totalNumberOfActions.incrementAndGet();
}
public long getTotalWallTimeMillis() {
return totalWallTimeMillis.get();
}
public void recordActionCacheStats(ActionCacheStatistics actionCacheStatistics) {
actionCacheHitCount = actionCacheStatistics.getHits();
}
/*
* Returns a human-readable summary of spawns counted.
*/
public ImmutableMap<String, Integer> getSummary() {
return getSummary(REPORT_FIRST);
}
/*
* Returns a human-readable summary of spawns counted.
*/
public ImmutableMap<String, Integer> getSummary(ImmutableList<String> reportFirst) {
ImmutableMap.Builder<String, Integer> result = ImmutableMap.builder();
int numActionsWithoutInternal = runners.size();
int numActionsTotal = totalNumberOfActions.get();
result.put("total", numActionsTotal);
// First report cache results.
if (actionCacheHitCount > 0) {
result.put("action cache hit", actionCacheHitCount);
}
for (String s : reportFirst) {
int count = runners.setCount(s, 0);
if (count > 0) {
result.put(s, count);
}
}
// Account for internal actions such as SymlinkTree.
if (numActionsWithoutInternal < numActionsTotal) {
result.put("internal", numActionsTotal - numActionsWithoutInternal);
}
// Sort the rest alphabetically
ArrayList<Multiset.Entry<String>> list = new ArrayList<>(runners.entrySet());
Collections.sort(list, Comparator.comparing(e -> e.getElement()));
for (Multiset.Entry<String> e : list) {
result.put(e.getElement(), e.getCount());
}
return result.buildOrThrow();
}
public String getExecKindFor(String runnerName) {
return runnerExecKinds.getOrDefault(runnerName, null);
}
public static String convertSummaryToString(ImmutableMap<String, Integer> spawnSummary) {
Integer total = spawnSummary.get("total");
if (total == 0) {
return "0 processes.";
}
StringBuilder stringSummary = new StringBuilder();
stringSummary.append(total).append(" process");
if (total > 1) {
stringSummary.append("es");
}
String separator = ": ";
for (Map.Entry<String, Integer> runnerStats : spawnSummary.entrySet()) {
if ("total".equals(runnerStats.getKey())) {
continue;
}
stringSummary.append(separator);
separator = ", ";
stringSummary.append(runnerStats.getValue()).append(' ').append(runnerStats.getKey());
}
stringSummary.append('.');
return stringSummary.toString();
}
}