blob: 3c414d8126e313459ee47c5d7d8c91800a4f1e9b [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.skyframe;
15
16import com.google.common.base.Preconditions;
17import com.google.common.base.Predicate;
18import com.google.common.base.Supplier;
19import com.google.common.base.Suppliers;
20import com.google.common.base.Throwables;
21import com.google.common.collect.ImmutableList;
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000022import com.google.common.collect.Range;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.common.collect.Sets;
24import com.google.common.util.concurrent.ThreadFactoryBuilder;
25import com.google.devtools.build.lib.actions.Artifact;
Eric Fellheimer6a9d7e52015-06-18 22:08:32 +000026import com.google.devtools.build.lib.concurrent.ExecutorUtil;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.concurrent.Sharder;
28import com.google.devtools.build.lib.concurrent.ThrowableRecordingRunnableWrapper;
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +000029import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker.DirtyResult;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.util.LoggingUtil;
31import com.google.devtools.build.lib.util.Pair;
32import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
33import com.google.devtools.build.lib.vfs.BatchStat;
34import com.google.devtools.build.lib.vfs.FileStatusWithDigest;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import com.google.devtools.build.skyframe.Differencer;
36import com.google.devtools.build.skyframe.MemoizingEvaluator;
37import com.google.devtools.build.skyframe.SkyFunctionName;
38import com.google.devtools.build.skyframe.SkyKey;
39import com.google.devtools.build.skyframe.SkyValue;
40
41import java.io.IOException;
42import java.util.Collection;
43import java.util.Collections;
44import java.util.HashMap;
45import java.util.List;
46import java.util.Map;
47import java.util.Set;
48import java.util.concurrent.ConcurrentHashMap;
49import java.util.concurrent.ExecutorService;
50import java.util.concurrent.Executors;
51import java.util.concurrent.atomic.AtomicInteger;
52import java.util.logging.Level;
53import java.util.logging.Logger;
54
55import javax.annotation.Nullable;
56
57/**
58 * A helper class to find dirty values by accessing the filesystem directly (contrast with
59 * {@link DiffAwareness}).
60 */
61class FilesystemValueChecker {
62
63 private static final int DIRTINESS_CHECK_THREADS = 50;
64 private static final Logger LOG = Logger.getLogger(FilesystemValueChecker.class.getName());
65
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010066 private static final Predicate<SkyKey> ACTION_FILTER =
67 SkyFunctionName.functionIs(SkyFunctions.ACTION_EXECUTION);
68
69 private final TimestampGranularityMonitor tsgm;
Nathan Harmata9b38b2c2015-08-27 16:11:07 +000070 @Nullable
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000071 private final Range<Long> lastExecutionTimeRange;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010072 private final Supplier<Map<SkyKey, SkyValue>> valuesSupplier;
73 private AtomicInteger modifiedOutputFilesCounter = new AtomicInteger(0);
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000074 private AtomicInteger modifiedOutputFilesIntraBuildCounter = new AtomicInteger(0);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010075
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +000076 FilesystemValueChecker(Supplier<Map<SkyKey, SkyValue>> valuesSupplier,
Nathan Harmata9b38b2c2015-08-27 16:11:07 +000077 TimestampGranularityMonitor tsgm, @Nullable Range<Long> lastExecutionTimeRange) {
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +000078 this.valuesSupplier = valuesSupplier;
79 this.tsgm = tsgm;
80 this.lastExecutionTimeRange = lastExecutionTimeRange;
81 }
82
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000083 FilesystemValueChecker(final MemoizingEvaluator evaluator, TimestampGranularityMonitor tsgm,
Nathan Harmata9b38b2c2015-08-27 16:11:07 +000084 @Nullable Range<Long> lastExecutionTimeRange) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010085 this.tsgm = tsgm;
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000086 this.lastExecutionTimeRange = lastExecutionTimeRange;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010087
88 // Construct the full map view of the entire graph at most once ("memoized"), lazily. If
89 // getDirtyFilesystemValues(Iterable<SkyKey>) is called on an empty Iterable, we avoid having
90 // to create the Map of value keys to values. This is useful in the case where the graph
91 // getValues() method could be slow.
92 this.valuesSupplier = Suppliers.memoize(new Supplier<Map<SkyKey, SkyValue>>() {
93 @Override
94 public Map<SkyKey, SkyValue> get() {
95 return evaluator.getValues();
96 }
97 });
98 }
99
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100100 /**
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000101 * Returns a {@link Differencer.DiffWithDelta} containing keys from the backing graph (of the
102 * {@link MemoizingEvaluator} given at construction time) that are dirty according to the
103 * passed-in {@code dirtinessChecker}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100104 */
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000105 Differencer.DiffWithDelta getDirtyKeys(SkyValueDirtinessChecker dirtinessChecker)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100106 throws InterruptedException {
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000107 return getDirtyValues(valuesSupplier.get().keySet(), dirtinessChecker,
108 /*checkMissingValues=*/false);
109 }
110
111 /**
112 * Returns a {@link Differencer.DiffWithDelta} containing keys that are dirty according to the
113 * passed-in {@code dirtinessChecker}.
114 */
115 Differencer.DiffWithDelta getNewAndOldValues(Iterable<SkyKey> keys,
116 SkyValueDirtinessChecker dirtinessChecker) throws InterruptedException {
117 return getDirtyValues(keys, dirtinessChecker, /*checkMissingValues=*/true);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100118 }
119
120 /**
121 * Return a collection of action values which have output files that are not in-sync with
122 * the on-disk file value (were modified externally).
123 */
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000124 Collection<SkyKey> getDirtyActionValues(@Nullable final BatchStat batchStatter)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100125 throws InterruptedException {
126 // CPU-bound (usually) stat() calls, plus a fudge factor.
127 LOG.info("Accumulating dirty actions");
128 final int numOutputJobs = Runtime.getRuntime().availableProcessors() * 4;
129 final Set<SkyKey> actionSkyKeys =
130 Sets.filter(valuesSupplier.get().keySet(), ACTION_FILTER);
131 final Sharder<Pair<SkyKey, ActionExecutionValue>> outputShards =
132 new Sharder<>(numOutputJobs, actionSkyKeys.size());
133
134 for (SkyKey key : actionSkyKeys) {
135 outputShards.add(Pair.of(key, (ActionExecutionValue) valuesSupplier.get().get(key)));
136 }
137 LOG.info("Sharded action values for batching");
138
139 ExecutorService executor = Executors.newFixedThreadPool(
140 numOutputJobs,
141 new ThreadFactoryBuilder().setNameFormat("FileSystem Output File Invalidator %d").build());
142
143 Collection<SkyKey> dirtyKeys = Sets.newConcurrentHashSet();
144 ThrowableRecordingRunnableWrapper wrapper =
145 new ThrowableRecordingRunnableWrapper("FileSystemValueChecker#getDirtyActionValues");
146
147 modifiedOutputFilesCounter.set(0);
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000148 modifiedOutputFilesIntraBuildCounter.set(0);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100149 for (List<Pair<SkyKey, ActionExecutionValue>> shard : outputShards) {
150 Runnable job = (batchStatter == null)
151 ? outputStatJob(dirtyKeys, shard)
152 : batchStatJob(dirtyKeys, shard, batchStatter);
153 executor.submit(wrapper.wrap(job));
154 }
155
Eric Fellheimer6a9d7e52015-06-18 22:08:32 +0000156 boolean interrupted = ExecutorUtil.interruptibleShutdown(executor);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100157 Throwables.propagateIfPossible(wrapper.getFirstThrownError());
158 LOG.info("Completed output file stat checks");
159 if (interrupted) {
160 throw new InterruptedException();
161 }
162 return dirtyKeys;
163 }
164
165 private Runnable batchStatJob(final Collection<SkyKey> dirtyKeys,
166 final List<Pair<SkyKey, ActionExecutionValue>> shard,
167 final BatchStat batchStatter) {
168 return new Runnable() {
169 @Override
170 public void run() {
171 Map<Artifact, Pair<SkyKey, ActionExecutionValue>> artifactToKeyAndValue = new HashMap<>();
172 for (Pair<SkyKey, ActionExecutionValue> keyAndValue : shard) {
173 ActionExecutionValue actionValue = keyAndValue.getSecond();
174 if (actionValue == null) {
175 dirtyKeys.add(keyAndValue.getFirst());
176 } else {
177 for (Artifact artifact : actionValue.getAllOutputArtifactData().keySet()) {
178 artifactToKeyAndValue.put(artifact, keyAndValue);
179 }
180 }
181 }
182
183 List<Artifact> artifacts = ImmutableList.copyOf(artifactToKeyAndValue.keySet());
184 List<FileStatusWithDigest> stats;
185 try {
186 stats = batchStatter.batchStat(/*includeDigest=*/true, /*includeLinks=*/true,
187 Artifact.asPathFragments(artifacts));
188 } catch (IOException e) {
189 // Batch stat did not work. Log an exception and fall back on system calls.
190 LoggingUtil.logToRemote(Level.WARNING, "Unable to process batch stat", e);
191 outputStatJob(dirtyKeys, shard).run();
192 return;
193 } catch (InterruptedException e) {
194 // We handle interrupt in the main thread.
195 return;
196 }
197
198 Preconditions.checkState(artifacts.size() == stats.size(),
199 "artifacts.size() == %s stats.size() == %s", artifacts.size(), stats.size());
200 for (int i = 0; i < artifacts.size(); i++) {
201 Artifact artifact = artifacts.get(i);
202 FileStatusWithDigest stat = stats.get(i);
203 Pair<SkyKey, ActionExecutionValue> keyAndValue = artifactToKeyAndValue.get(artifact);
204 ActionExecutionValue actionValue = keyAndValue.getSecond();
205 SkyKey key = keyAndValue.getFirst();
206 FileValue lastKnownData = actionValue.getAllOutputArtifactData().get(artifact);
207 try {
Janak Ramakrishnana5c1f962015-04-03 23:06:31 +0000208 FileValue newData = ActionMetadataHandler.fileValueFromArtifact(artifact, stat, tsgm);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100209 if (!newData.equals(lastKnownData)) {
Miguel Alcon Pintobdfdd092015-04-10 19:04:49 +0000210 updateIntraBuildModifiedCounter(stat != null ? stat.getLastChangeTime() : -1,
211 lastKnownData.isSymlink(), newData.isSymlink());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100212 modifiedOutputFilesCounter.getAndIncrement();
213 dirtyKeys.add(key);
214 }
215 } catch (IOException e) {
216 // This is an unexpected failure getting a digest or symlink target.
217 modifiedOutputFilesCounter.getAndIncrement();
218 dirtyKeys.add(key);
219 }
220 }
221 }
222 };
223 }
224
Miguel Alcon Pintobdfdd092015-04-10 19:04:49 +0000225 private void updateIntraBuildModifiedCounter(long time, boolean oldWasSymlink,
Nathan Harmata7a344272015-06-05 20:52:38 +0000226 boolean newIsSymlink) {
Miguel Alcon Pintobdfdd092015-04-10 19:04:49 +0000227 if (lastExecutionTimeRange != null
228 && lastExecutionTimeRange.contains(time)
229 && !(oldWasSymlink && newIsSymlink)) {
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000230 modifiedOutputFilesIntraBuildCounter.incrementAndGet();
231 }
232 }
233
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100234 private Runnable outputStatJob(final Collection<SkyKey> dirtyKeys,
235 final List<Pair<SkyKey, ActionExecutionValue>> shard) {
236 return new Runnable() {
237 @Override
238 public void run() {
239 for (Pair<SkyKey, ActionExecutionValue> keyAndValue : shard) {
240 ActionExecutionValue value = keyAndValue.getSecond();
241 if (value == null || actionValueIsDirtyWithDirectSystemCalls(value)) {
242 dirtyKeys.add(keyAndValue.getFirst());
243 }
244 }
245 }
246 };
247 }
248
249 /**
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000250 * Returns the number of modified output files inside of dirty actions.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100251 */
252 int getNumberOfModifiedOutputFiles() {
253 return modifiedOutputFilesCounter.get();
254 }
255
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000256 /** Returns the number of modified output files that occur during the previous build. */
257 int getNumberOfModifiedOutputFilesDuringPreviousBuild() {
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000258 return modifiedOutputFilesIntraBuildCounter.get();
259 }
260
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100261 private boolean actionValueIsDirtyWithDirectSystemCalls(ActionExecutionValue actionValue) {
262 boolean isDirty = false;
263 for (Map.Entry<Artifact, FileValue> entry :
264 actionValue.getAllOutputArtifactData().entrySet()) {
265 Artifact artifact = entry.getKey();
266 FileValue lastKnownData = entry.getValue();
267 try {
Janak Ramakrishnana5c1f962015-04-03 23:06:31 +0000268 FileValue fileValue = ActionMetadataHandler.fileValueFromArtifact(artifact, null, tsgm);
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000269 if (!fileValue.equals(lastKnownData)) {
270 updateIntraBuildModifiedCounter(fileValue.exists()
271 ? fileValue.realRootedPath().asPath().getLastModifiedTime()
Miguel Alcon Pintobdfdd092015-04-10 19:04:49 +0000272 : -1, lastKnownData.isSymlink(), fileValue.isSymlink());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100273 modifiedOutputFilesCounter.getAndIncrement();
274 isDirty = true;
275 }
276 } catch (IOException e) {
277 // This is an unexpected failure getting a digest or symlink target.
278 modifiedOutputFilesCounter.getAndIncrement();
279 isDirty = true;
280 }
281 }
282 return isDirty;
283 }
284
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000285 private BatchDirtyResult getDirtyValues(
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000286 Iterable<SkyKey> values, final SkyValueDirtinessChecker checker,
287 final boolean checkMissingValues) throws InterruptedException {
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000288 ExecutorService executor =
289 Executors.newFixedThreadPool(
290 DIRTINESS_CHECK_THREADS,
291 new ThreadFactoryBuilder().setNameFormat("FileSystem Value Invalidator %d").build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100292
293 final BatchDirtyResult batchResult = new BatchDirtyResult();
294 ThrowableRecordingRunnableWrapper wrapper =
295 new ThrowableRecordingRunnableWrapper("FilesystemValueChecker#getDirtyValues");
296 for (final SkyKey key : values) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100297 final SkyValue value = valuesSupplier.get().get(key);
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000298 executor.execute(
299 wrapper.wrap(
300 new Runnable() {
301 @Override
302 public void run() {
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000303 if (value != null || checkMissingValues) {
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000304 DirtyResult result = checker.maybeCheck(key, value, tsgm);
305 if (result != null && result.isDirty()) {
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000306 batchResult.add(key, value, result.getNewValue());
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000307 }
308 }
309 }
310 }));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100311 }
312
Eric Fellheimer6a9d7e52015-06-18 22:08:32 +0000313 boolean interrupted = ExecutorUtil.interruptibleShutdown(executor);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100314 Throwables.propagateIfPossible(wrapper.getFirstThrownError());
315 if (interrupted) {
316 throw new InterruptedException();
317 }
318 return batchResult;
319 }
320
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100321 /**
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000322 * Result of a batch call to {@link SkyValueDirtinessChecker#maybeCheck}. Partitions the dirty
323 * values based on whether we have a new value available for them or not.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100324 */
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000325 private static class BatchDirtyResult implements Differencer.DiffWithDelta {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100326
327 private final Set<SkyKey> concurrentDirtyKeysWithoutNewValues =
328 Collections.newSetFromMap(new ConcurrentHashMap<SkyKey, Boolean>());
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000329 private final ConcurrentHashMap<SkyKey, Delta> concurrentDirtyKeysWithNewAndOldValues =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100330 new ConcurrentHashMap<>();
331
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000332 private void add(SkyKey key, @Nullable SkyValue oldValue, @Nullable SkyValue newValue) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100333 if (newValue == null) {
334 concurrentDirtyKeysWithoutNewValues.add(key);
335 } else {
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000336 if (oldValue == null) {
337 concurrentDirtyKeysWithNewAndOldValues.put(key, new Delta(newValue));
338 } else {
339 concurrentDirtyKeysWithNewAndOldValues.put(key, new Delta(oldValue, newValue));
340 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100341 }
342 }
343
344 @Override
Michajlo Matijkiw2b71efe2015-06-19 19:23:16 +0000345 public Collection<SkyKey> changedKeysWithoutNewValues() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100346 return concurrentDirtyKeysWithoutNewValues;
347 }
348
349 @Override
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000350 public Map<SkyKey, Delta> changedKeysWithNewAndOldValues() {
351 return concurrentDirtyKeysWithNewAndOldValues;
352 }
353
354 @Override
355 public Map<SkyKey, SkyValue> changedKeysWithNewValues() {
356 return Delta.newValues(concurrentDirtyKeysWithNewAndOldValues);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100357 }
358 }
359
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100360}