| // Copyright 2014 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.actions; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Splitter; |
| import com.google.common.io.Files; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; |
| import com.google.devtools.build.lib.util.ProcMeminfoParser; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.charset.Charset; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| /** |
| * This class estimates the local host's resource capacity. |
| */ |
| @ThreadCompatible |
| public final class LocalHostCapacity { |
| |
| /* If /proc/* information is not available, guess based on what the JVM thinks. Anecdotally, |
| * the JVM picks 0.22 the total available memory as maxMemory (tested on a standard Mac), so |
| * multiply by 3, and divide by 2^20 because we want megabytes. |
| */ |
| private static final ResourceSet DEFAULT_RESOURCES = ResourceSet.create( |
| 3.0 * (Runtime.getRuntime().maxMemory() >> 20), |
| Runtime.getRuntime().availableProcessors(), 1.0, |
| Integer.MAX_VALUE); |
| |
| private LocalHostCapacity() {} |
| |
| /** |
| * Estimates of the local host's resource capacity, |
| * obtained by reading /proc/cpuinfo and /proc/meminfo. |
| */ |
| private static ResourceSet localHostCapacity; |
| |
| /** |
| * Estimates of the local host's resource capacity, |
| * obtained by reading /proc/cpuinfo and /proc/meminfo. |
| */ |
| public static ResourceSet getLocalHostCapacity() { |
| if (localHostCapacity == null) { |
| localHostCapacity = getLocalHostCapacity("/proc/cpuinfo", "/proc/meminfo"); |
| } |
| return localHostCapacity; |
| } |
| |
| private static final Splitter NEWLINE_SPLITTER = Splitter.on('\n').omitEmptyStrings(); |
| |
| @VisibleForTesting |
| static int getLogicalCpuCount(String cpuinfoContent) { |
| Iterable<String> lines = NEWLINE_SPLITTER.split(cpuinfoContent); |
| int count = 0; |
| for (String line : lines) { |
| if(line.startsWith("processor")) { |
| count++; |
| } |
| } |
| if (count == 0) { |
| throw new IllegalArgumentException("Can't locate processor in the /proc/cpuinfo"); |
| } |
| return count; |
| } |
| |
| @VisibleForTesting |
| static int getPhysicalCpuCount(String cpuinfoContent, int logicalCpuCount) { |
| Iterable<String> lines = NEWLINE_SPLITTER.split(cpuinfoContent); |
| Set<String> uniq = new HashSet<>(); |
| for (String line : lines) { |
| if(line.startsWith("physical id")) { |
| uniq.add(line); |
| } |
| } |
| int physicalCpuCount = uniq.size(); |
| if (physicalCpuCount == 0) { |
| physicalCpuCount = logicalCpuCount; |
| } |
| return physicalCpuCount; |
| } |
| |
| @VisibleForTesting |
| static int getCoresPerCpu(String cpuinfoFileContent) { |
| Iterable<String> lines = NEWLINE_SPLITTER.split(cpuinfoFileContent); |
| Set<String> uniq = new HashSet<>(); |
| for (String line : lines) { |
| if(line.startsWith("core id")) { |
| uniq.add(line); |
| } |
| } |
| int coresPerCpu = uniq.size(); |
| if (coresPerCpu == 0) { |
| coresPerCpu = 1; |
| } |
| return coresPerCpu; |
| } |
| |
| @VisibleForTesting |
| static ResourceSet getLocalHostCapacity(String cpuinfoFile, String meminfoFile) { |
| try { |
| String cpuinfoContent = readContent(cpuinfoFile); |
| ProcMeminfoParser memInfo = new ProcMeminfoParser(meminfoFile); |
| int logicalCpuCount = getLogicalCpuCount(cpuinfoContent); |
| int physicalCpuCount = getPhysicalCpuCount(cpuinfoContent, logicalCpuCount); |
| int coresPerCpu = getCoresPerCpu(cpuinfoContent); |
| int totalCores = coresPerCpu * physicalCpuCount; |
| boolean hyperthreading = (logicalCpuCount != totalCores); |
| double ramMb = ProcMeminfoParser.kbToMb(memInfo.getTotalKb()); |
| final double EFFECTIVE_CPUS_PER_HYPERTHREADED_CPU = 0.6; |
| return ResourceSet.create( |
| ramMb, |
| logicalCpuCount * (hyperthreading ? EFFECTIVE_CPUS_PER_HYPERTHREADED_CPU : 1.0), |
| 1.0, |
| Integer.MAX_VALUE); |
| } catch (IOException | IllegalArgumentException e) { |
| return DEFAULT_RESOURCES; |
| } |
| } |
| |
| /** |
| * For testing purposes only. Do not use it. |
| */ |
| @VisibleForTesting |
| static void setLocalHostCapacity(ResourceSet resources) { |
| localHostCapacity = resources; |
| } |
| |
| private static String readContent(String filename) throws IOException { |
| return Files.toString(new File(filename), Charset.defaultCharset()); |
| } |
| |
| } |