| // 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.exec; | |
| import static com.google.devtools.build.lib.util.StringEncoding.internalToUnicode; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableSortedMap; | |
| import com.google.common.hash.HashCode; | |
| import com.google.devtools.build.lib.actions.AbstractAction; | |
| import com.google.devtools.build.lib.actions.ActionContext; | |
| import com.google.devtools.build.lib.actions.ActionInput; | |
| import com.google.devtools.build.lib.actions.Artifact.SourceArtifact; | |
| import com.google.devtools.build.lib.actions.ExecException; | |
| import com.google.devtools.build.lib.actions.FileArtifactValue; | |
| import com.google.devtools.build.lib.actions.InputMetadataProvider; | |
| import com.google.devtools.build.lib.actions.Spawn; | |
| import com.google.devtools.build.lib.actions.SpawnMetrics; | |
| import com.google.devtools.build.lib.actions.SpawnResult; | |
| import com.google.devtools.build.lib.actions.UserExecException; | |
| import com.google.devtools.build.lib.actions.VirtualActionInput; | |
| import com.google.devtools.build.lib.analysis.platform.PlatformUtils; | |
| import com.google.devtools.build.lib.exec.Protos.Digest; | |
| import com.google.devtools.build.lib.exec.Protos.EnvironmentVariable; | |
| import com.google.devtools.build.lib.exec.Protos.Platform; | |
| import com.google.devtools.build.lib.remote.options.RemoteOptions; | |
| import com.google.devtools.build.lib.remote.util.DigestUtil; | |
| import com.google.devtools.build.lib.vfs.DigestHashFunction; | |
| import com.google.devtools.build.lib.vfs.DigestUtils; | |
| import com.google.devtools.build.lib.vfs.FileStatus; | |
| import com.google.devtools.build.lib.vfs.FileSystem; | |
| import com.google.devtools.build.lib.vfs.Path; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.build.lib.vfs.XattrProvider; | |
| import com.google.protobuf.util.Durations; | |
| import com.google.protobuf.util.Timestamps; | |
| import java.io.IOException; | |
| import java.time.Duration; | |
| import java.util.Map; | |
| import java.util.SortedMap; | |
| import java.util.function.Predicate; | |
| import java.util.function.Supplier; | |
| import javax.annotation.Nullable; | |
| /** An {@link ActionContext} providing the ability to log executed spawns. */ | |
| public abstract class SpawnLogContext implements ActionContext { | |
| protected final Predicate<Spawn> logSpawnPredicate; | |
| protected SpawnLogContext(Predicate<Spawn> logSpawnPredicate) { | |
| this.logSpawnPredicate = logSpawnPredicate; | |
| } | |
| /** Returns true if the spawn should be logged. */ | |
| protected boolean shouldLog(Spawn spawn) { | |
| return logSpawnPredicate.test(spawn); | |
| } | |
| /** | |
| * Logs an executed spawn. | |
| * | |
| * <p>May be called concurrently. | |
| * | |
| * @param spawn the spawn to log | |
| * @param inputMetadataProvider provides metadata for the spawn inputs | |
| * @param inputMap the mapping from input paths to action inputs (built lazily) | |
| * @param fileSystem the filesystem containing the spawn inputs and outputs, which might be an | |
| * action filesystem when building without the bytes | |
| * @param timeout the timeout the spawn was run under | |
| * @param result the spawn result | |
| */ | |
| public abstract void logSpawn( | |
| Spawn spawn, | |
| InputMetadataProvider inputMetadataProvider, | |
| Supplier<SortedMap<PathFragment, ActionInput>> inputMap, | |
| FileSystem fileSystem, | |
| Duration timeout, | |
| SpawnResult result) | |
| throws IOException, InterruptedException, ExecException; | |
| @VisibleForTesting | |
| void logSpawn( | |
| Spawn spawn, | |
| InputMetadataProvider inputMetadataProvider, | |
| SortedMap<PathFragment, ActionInput> inputMap, | |
| FileSystem fileSystem, | |
| Duration timeout, | |
| SpawnResult result) | |
| throws IOException, InterruptedException, ExecException { | |
| logSpawn(spawn, inputMetadataProvider, () -> inputMap, fileSystem, timeout, result); | |
| } | |
| /** | |
| * Logs an internal symlink action, which is not backed by a spawn. | |
| * | |
| * <p>May be called concurrently. | |
| * | |
| * @param action the action to log | |
| */ | |
| public abstract void logSymlinkAction(AbstractAction action) | |
| throws IOException, InterruptedException; | |
| /** Finishes writing the log and performs any required post-processing. */ | |
| public abstract void close() throws IOException; | |
| /** Whether the log should be published to the build event protocol. */ | |
| public abstract boolean shouldPublish(); | |
| /** Computes the environment variables. */ | |
| protected ImmutableList<EnvironmentVariable> getEnvironmentVariables(Spawn spawn) { | |
| ImmutableMap<String, String> environment = spawn.getEnvironment(); | |
| ImmutableList.Builder<EnvironmentVariable> builder = | |
| ImmutableList.builderWithExpectedSize(environment.size()); | |
| for (Map.Entry<String, String> entry : ImmutableSortedMap.copyOf(environment).entrySet()) { | |
| builder.add( | |
| EnvironmentVariable.newBuilder() | |
| .setName(internalToUnicode(entry.getKey())) | |
| .setValue(internalToUnicode(entry.getValue())) | |
| .build()); | |
| } | |
| return builder.build(); | |
| } | |
| /** Computes the execution platform. */ | |
| @Nullable | |
| protected Platform getPlatform(Spawn spawn, RemoteOptions remoteOptions) | |
| throws UserExecException { | |
| var execPlatform = PlatformUtils.getPlatformProto(spawn, remoteOptions); | |
| if (execPlatform == null) { | |
| return null; | |
| } | |
| Platform.Builder builder = Platform.newBuilder(); | |
| for (var p : execPlatform.getPropertiesList()) { | |
| builder.addPropertiesBuilder().setName(p.getName()).setValue(p.getValue()); | |
| } | |
| return builder.build(); | |
| } | |
| /** | |
| * Determines whether an action input is a directory, avoiding I/O if possible. | |
| * | |
| * <p>Do not call for action outputs. | |
| */ | |
| protected boolean isInputDirectory(ActionInput input, InputMetadataProvider inputMetadataProvider) | |
| throws IOException { | |
| if (input.isDirectory()) { | |
| return true; | |
| } | |
| if (input.isSymlink()) { | |
| return false; | |
| } | |
| // Virtual action inputs are always files. | |
| if (input instanceof VirtualActionInput) { | |
| return false; | |
| } | |
| // Source artifacts are always of file type, but may be directories in the filesystem. | |
| if (input instanceof SourceArtifact) { | |
| FileArtifactValue metadata = inputMetadataProvider.getInputMetadata(input); | |
| return metadata != null && metadata.getType().isDirectory(); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Computes the digest of an ActionInput or its path. | |
| * | |
| * <p>Will try to obtain the digest from cached metadata first, falling back to digesting the | |
| * contents manually. | |
| */ | |
| protected Digest computeDigest( | |
| @Nullable ActionInput input, | |
| Path path, | |
| @Nullable InputMetadataProvider inputMetadataProvider, | |
| XattrProvider xattrProvider, | |
| DigestHashFunction digestHashFunction, | |
| boolean includeHashFunctionName) | |
| throws IOException { | |
| Digest.Builder builder = Digest.newBuilder(); | |
| if (includeHashFunctionName) { | |
| builder.setHashFunctionName(digestHashFunction.toString()); | |
| } | |
| if (input != null) { | |
| if (input instanceof VirtualActionInput virtualActionInput) { | |
| build.bazel.remote.execution.v2.Digest digest = | |
| DigestUtil.compute(virtualActionInput, digestHashFunction.getHashFunction()); | |
| return builder.setHash(digest.getHash()).setSizeBytes(digest.getSizeBytes()).build(); | |
| } | |
| if (inputMetadataProvider != null) { | |
| // Try to obtain a digest from the input metadata. | |
| try { | |
| FileArtifactValue metadata = inputMetadataProvider.getInputMetadata(input); | |
| if (metadata != null && metadata.getDigest() != null) { | |
| return builder | |
| .setHash(HashCode.fromBytes(metadata.getDigest()).toString()) | |
| .setSizeBytes(metadata.getSize()) | |
| .build(); | |
| } | |
| } catch (IOException | IllegalStateException e) { | |
| // Pass through to local computation. | |
| } | |
| } | |
| } | |
| // Obtain a digest from the filesystem. | |
| FileStatus status = path.stat(); | |
| return builder | |
| .setHash( | |
| HashCode.fromBytes(DigestUtils.getDigestWithManualFallback(path, xattrProvider, status)) | |
| .toString()) | |
| .setSizeBytes(status.getSize()) | |
| .build(); | |
| } | |
| protected static Protos.SpawnMetrics getSpawnMetricsProto(SpawnResult result) { | |
| SpawnMetrics metrics = result.getMetrics(); | |
| Protos.SpawnMetrics.Builder builder = Protos.SpawnMetrics.newBuilder(); | |
| if (metrics.totalTimeInMs() != 0L) { | |
| builder.setTotalTime(millisToProto(metrics.totalTimeInMs())); | |
| } | |
| if (metrics.parseTimeInMs() != 0L) { | |
| builder.setParseTime(millisToProto(metrics.parseTimeInMs())); | |
| } | |
| if (metrics.networkTimeInMs() != 0L) { | |
| builder.setNetworkTime(millisToProto(metrics.networkTimeInMs())); | |
| } | |
| if (metrics.fetchTimeInMs() != 0L) { | |
| builder.setFetchTime(millisToProto(metrics.fetchTimeInMs())); | |
| } | |
| if (metrics.queueTimeInMs() != 0L) { | |
| builder.setQueueTime(millisToProto(metrics.queueTimeInMs())); | |
| } | |
| if (metrics.setupTimeInMs() != 0L) { | |
| builder.setSetupTime(millisToProto(metrics.setupTimeInMs())); | |
| } | |
| if (metrics.uploadTimeInMs() != 0L) { | |
| builder.setUploadTime(millisToProto(metrics.uploadTimeInMs())); | |
| } | |
| if (metrics.executionWallTimeInMs() != 0L) { | |
| builder.setExecutionWallTime(millisToProto(metrics.executionWallTimeInMs())); | |
| } | |
| if (metrics.processOutputsTimeInMs() != 0L) { | |
| builder.setProcessOutputsTime(millisToProto(metrics.processOutputsTimeInMs())); | |
| } | |
| if (metrics.retryTimeInMs() != 0L) { | |
| builder.setRetryTime(millisToProto(metrics.retryTimeInMs())); | |
| } | |
| builder.setInputBytes(metrics.inputBytes()); | |
| builder.setInputFiles(metrics.inputFiles()); | |
| builder.setMemoryEstimateBytes(metrics.memoryEstimate()); | |
| builder.setMeasuredMemoryPeakBytes(metrics.measuredMemoryPeak()); | |
| builder.setInputBytesLimit(metrics.inputBytesLimit()); | |
| builder.setInputFilesLimit(metrics.inputFilesLimit()); | |
| builder.setOutputBytesLimit(metrics.outputBytesLimit()); | |
| builder.setOutputFilesLimit(metrics.outputFilesLimit()); | |
| builder.setMemoryBytesLimit(metrics.memoryLimit()); | |
| if (metrics.timeLimitInMs() != 0L) { | |
| builder.setTimeLimit(millisToProto(metrics.timeLimitInMs())); | |
| } | |
| if (result.getStartTime() != null) { | |
| builder.setStartTime(Timestamps.fromMillis(result.getStartTime().toEpochMilli())); | |
| } | |
| return builder.build(); | |
| } | |
| @VisibleForTesting | |
| static com.google.protobuf.Duration millisToProto(int t) { | |
| return Durations.fromMillis(t); | |
| } | |
| } |