| // Copyright 2023 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.metrics.criticalpath; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static org.junit.Assert.assertThrows; |
| import static org.junit.Assert.fail; |
| import static org.mockito.Mockito.mock; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.devtools.build.lib.actions.Action; |
| import com.google.devtools.build.lib.actions.ActionCompletionEvent; |
| import com.google.devtools.build.lib.actions.ActionKeyContext; |
| import com.google.devtools.build.lib.actions.ActionLookupData; |
| import com.google.devtools.build.lib.actions.ActionMiddlemanEvent; |
| import com.google.devtools.build.lib.actions.ActionStartedEvent; |
| import com.google.devtools.build.lib.actions.AggregatedSpawnMetrics; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact; |
| import com.google.devtools.build.lib.actions.ArtifactRoot; |
| import com.google.devtools.build.lib.actions.ArtifactRoot.RootType; |
| import com.google.devtools.build.lib.actions.CachedActionEvent; |
| import com.google.devtools.build.lib.actions.DiscoveredInputsEvent; |
| import com.google.devtools.build.lib.actions.ResourceSet; |
| import com.google.devtools.build.lib.actions.SimpleSpawn; |
| import com.google.devtools.build.lib.actions.Spawn; |
| import com.google.devtools.build.lib.actions.SpawnExecutedEvent; |
| import com.google.devtools.build.lib.actions.SpawnExecutedEvent.ChangePhase; |
| import com.google.devtools.build.lib.actions.SpawnMetrics; |
| import com.google.devtools.build.lib.actions.SpawnResult; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil.MockAction; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil.NullAction; |
| import com.google.devtools.build.lib.clock.BlazeClock; |
| import com.google.devtools.build.lib.clock.BlazeClock.NanosToMillisSinceEpochConverter; |
| import com.google.devtools.build.lib.skyframe.rewinding.ActionRewoundEvent; |
| import com.google.devtools.build.lib.testutil.FoundationTestCase; |
| import com.google.devtools.build.lib.testutil.ManualClock; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.Root; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.time.Duration; |
| import java.time.Instant; |
| import java.util.Collections; |
| import java.util.List; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Unit tests for {@link CriticalPathComputer}. */ |
| @RunWith(JUnit4.class) |
| public class CriticalPathComputerTest extends FoundationTestCase { |
| |
| private ManualClock clock; |
| private CriticalPathComputer computer; |
| private ArtifactRoot artifactRoot; |
| private ArtifactRoot derivedArtifactRoot; |
| private ArtifactRoot middlemanRoot; |
| |
| @Before |
| public final void initializeRoots() { |
| Path workspaceRoot = scratch.resolve("/workspace"); |
| derivedArtifactRoot = ArtifactRoot.asDerivedRoot(workspaceRoot, RootType.Output, "test"); |
| artifactRoot = ArtifactRoot.asSourceRoot(Root.fromPath(workspaceRoot)); |
| middlemanRoot = |
| ArtifactRoot.asDerivedRoot( |
| scratch.resolve("/exec"), RootType.Output, PathFragment.create("out")); |
| } |
| |
| @Before |
| public void createComputer() { |
| clock = new ManualClock(); |
| computer = new CriticalPathComputer(new ActionKeyContext(), clock); |
| } |
| |
| private static void assertActionMatches(Action action, CriticalPathComponent component) { |
| if (!actionMatches(action, component)) { |
| fail("Action " + action + " did not match one in " + component); |
| } |
| } |
| |
| private static boolean actionMatches(Action action, CriticalPathComponent component) { |
| return component.getAction() == action; |
| } |
| |
| @Test |
| public void testNoSpawnMetrics() { |
| CriticalPathComponent cp = new CriticalPathComponent(1, new NullAction(), 0); |
| assertThat(cp.getSpawnMetrics()).isEqualTo(AggregatedSpawnMetrics.EMPTY); |
| assertThat(cp.getLongestPhaseSpawnRunnerName()).isNull(); |
| } |
| |
| @Test |
| public void testMultipleSpawnMetrics() { |
| CriticalPathComponent cp = new CriticalPathComponent(1, new NullAction(), 0); |
| cp.addSpawnResult( |
| SpawnMetrics.Builder.forRemoteExec().setTotalTimeInMs(10 * 1000).build(), |
| "first", |
| "", |
| false); |
| cp.addSpawnResult( |
| SpawnMetrics.Builder.forRemoteExec().setTotalTimeInMs(30 * 1000).build(), |
| "second", |
| "", |
| false); |
| cp.addSpawnResult( |
| SpawnMetrics.Builder.forRemoteExec().setTotalTimeInMs(20 * 1000).build(), |
| "third", |
| "", |
| false); |
| cp.finishActionExecution(0, 40, "test"); |
| // The current implementation keeps the maximum spawn metrics because we do not differentiate |
| // between sequential or parallel spawn invocations within a single Bazel action. So while it is |
| // still 'incorrect', it is more fair than keeping the latest invocation data. |
| assertThat(cp.getSpawnMetrics().getRemoteMetrics().totalTimeInMs()).isEqualTo(30 * 1000); |
| assertThat(cp.getLongestPhaseSpawnRunnerName()).isEqualTo("second"); |
| } |
| |
| /** |
| * Test that 'other' time is correctly computed as any time not measured by the rest of the stats. |
| */ |
| @Test |
| public void testSpawnMetricsOtherTimeComputed() { |
| SpawnMetrics spawnMetrics = |
| SpawnMetrics.Builder.forRemoteExec() |
| .setTotalTimeInMs(100 * 1000) |
| .setParseTimeInMs(1 * 1000) |
| .setNetworkTimeInMs(2 * 1000) |
| .setFetchTimeInMs(3 * 1000) |
| .setQueueTimeInMs(4 * 1000) |
| .setSetupTimeInMs(5 * 1000) |
| .setUploadTimeInMs(6 * 1000) |
| .setExecutionWallTimeInMs(7 * 1000) |
| .setRetryTimeInMs(ImmutableMap.of(1, 8 * 1000)) |
| .setProcessOutputsTimeInMs(9 * 1000) |
| .build(); |
| assertThat(spawnMetrics.otherTimeInMs()).isEqualTo(55 * 1000); |
| } |
| |
| @Test |
| public void testCriticalPathOneAction() throws Exception { |
| simulateActionExec(new NullAction(), 2 * 1000, 1 * 1000, true); |
| checkCriticalPath( |
| Duration.ofSeconds(2), |
| Duration.ofSeconds(1), |
| Duration.ofSeconds(1), |
| "2.00", |
| "50.00", |
| "50.00"); |
| checkTopComponentsTimes(computer, 2000L); |
| } |
| |
| @Test |
| public void testCriticalPathQueueTimeWithoutRetries() throws Exception { |
| SpawnResult.Builder spawnResult = |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setTotalTimeInMs(4 * 1000) |
| .setExecutionWallTimeInMs(1 * 1000) |
| .setQueueTimeInMs(1 * 1000) |
| .build()); |
| simulateActionExec(new NullAction(), 8 * 1000, spawnResult.build()); |
| AggregatedCriticalPath stats = |
| checkCriticalPath( |
| Duration.ofSeconds(8), |
| Duration.ofSeconds(4), |
| Duration.ofSeconds(1), |
| "8.00", |
| "50.00", |
| "12.50"); |
| assertThat(stats.getSpawnMetrics().getRemoteMetrics().queueTimeInMs()).isEqualTo(1 * 1000); |
| } |
| |
| /** |
| * Test that if an action depends on a middleman artifact we get the correct critical path: |
| * |
| * <p>a --> b(5 seconds) \--> c1 [MIDDLEMAN] --> c2 [MIDDLEMAN] --> d (1 second) --> e (6 seconds) |
| * |
| * <p>Note : 'a --> b' means that a need the outputs of b for being executed. |
| */ |
| @Test |
| public void testCriticalPathMiddleman() throws Exception { |
| MockAction actionE = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("e.out"))); |
| |
| MockAction actionD = |
| new MockAction( |
| Collections.singleton(artifact("e.out")), ImmutableSet.of(artifact("d.out"))); |
| |
| MockAction actionC1 = |
| new MockAction( |
| Collections.singleton(middlemanArtifact("c2.out")), |
| ImmutableSet.of(middlemanArtifact("c1.out")), |
| true); |
| |
| MockAction actionC2 = |
| new MockAction( |
| Collections.singleton(artifact("d.out")), |
| ImmutableSet.of(middlemanArtifact("c2.out")), |
| true); |
| |
| MockAction sharedActionC2 = |
| new MockAction( |
| Collections.singleton(artifact("d.out")), |
| ImmutableSet.of(middlemanArtifact("c2.out")), |
| true); |
| |
| MockAction actionB = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("b.out"))); |
| |
| MockAction actionA = |
| new MockAction( |
| Lists.newArrayList(artifact("b.out"), middlemanArtifact("c1.out")), |
| ImmutableSet.of(artifact("a.out"))); |
| |
| // Executing the leaf node that is not part of the critical path first to make sure gaps do not |
| // affect the total critical path run time. |
| simulateActionExec(actionB, 5 * 1000, 5 * 1000, true); |
| simulateActionExec(actionE, 6 * 1000, 6 * 1000, true); |
| simulateActionExec(actionD, 1 * 1000, 1 * 1000, true); |
| simulateActionExec(actionC2, 0, 0, true); |
| // Check that we do not crash if we execute a shareable middleman twice. |
| simulateActionExec(sharedActionC2, 0); |
| simulateActionExec(actionC1, 0, 0, true); |
| simulateActionExec(actionA, 1 * 1000, 1 * 1000, true); |
| |
| // 8s = 1s (a) + 1s (d) + 6s (e) |
| checkCriticalPath( |
| Duration.ofSeconds(8), |
| Duration.ofSeconds(8), |
| Duration.ofSeconds(8), |
| "8.00", |
| "100.00", |
| "100.00"); |
| |
| checkTopComponentsTimes(computer, 6000L, 5000L, 1000, 1000, 0L, 0L); |
| } |
| |
| /** |
| * Check that the timing stats are printed correctly, that the printed values correctly match |
| * their label. |
| */ |
| @Test |
| public void testCriticalPathToString() throws Exception { |
| MockAction actionA = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("a.out"))); |
| SpawnResult.Builder spawnResult = |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setParseTimeInMs(5 * 1000) |
| .setNetworkTimeInMs(6 * 1000) |
| .setFetchTimeInMs(7 * 1000) |
| .setQueueTimeInMs(8 * 1000) |
| .setSetupTimeInMs(9 * 1000) |
| .setUploadTimeInMs(10 * 1000) |
| .setProcessOutputsTimeInMs(4 * 1000) |
| .setExecutionWallTimeInMs(40 * 1000) |
| .setTotalTimeInMs(100 * 1000) |
| .build()); |
| simulateActionExec(actionA, spawnResult); |
| AggregatedCriticalPath stats = computer.aggregate(); |
| assertThat(stats).isNotNull(); |
| |
| String toString = stats.toString(); |
| assertThat(toString).contains("parse: 5.00%"); |
| assertThat(toString).contains("network: 6.00%"); |
| assertThat(toString).contains("fetch: 7.00%"); |
| assertThat(toString).contains("queue: 8.00%"); |
| assertThat(toString).contains("setup: 9.00%"); |
| assertThat(toString).contains("upload: 10.00%"); |
| assertThat(toString).contains("processOutputs: 4.00%"); |
| assertThat(toString).contains("process: 40.00%"); |
| assertThat(toString).contains("other: 11.00%"); |
| } |
| |
| /** |
| * Check that we only print certain critical parts of the timing stats when they are below a |
| * certain threshold, to avoid spamming the user. |
| */ |
| @Test |
| public void testCriticalPathToStringSummary() throws Exception { |
| MockAction actionA = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("a.out"))); |
| MockAction actionB = |
| new MockAction( |
| Collections.singleton(artifact("a.out")), ImmutableSet.of(artifact("b.out"))); |
| |
| SpawnResult.Builder spawnResult = |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setNetworkTimeInMs(10 * 1000) |
| .setParseTimeInMs(10 * 1000) |
| .setFetchTimeInMs(10 * 1000) |
| .setQueueTimeInMs(10 * 1000) |
| .setSetupTimeInMs(10 * 1000) |
| .setProcessOutputsTimeInMs(10 * 1000) |
| .setExecutionWallTimeInMs(20 * 1000) |
| .setUploadTimeInMs(10 * 1000) |
| .setTotalTimeInMs(100 * 1000) |
| .build()); |
| simulateActionExec(actionA, spawnResult); |
| AggregatedCriticalPath stats = computer.aggregate(); |
| assertThat(stats).isNotNull(); |
| String summary = stats.toString(); |
| assertThat(summary).contains("network: 10.00%"); |
| assertThat(summary).contains("parse: 10.00%"); |
| assertThat(summary).contains("queue: 10.00%"); |
| assertThat(summary).contains("upload: 10.00%"); |
| assertThat(summary).contains("setup: 10.00%"); |
| assertThat(summary).contains("processOutputs: 10.00%"); |
| assertThat(summary).contains("process: 20.00%"); |
| assertThat(summary).contains("fetch: 10.00%"); |
| assertThat(summary).contains("other: 10.00%"); |
| |
| // Add another action execution so that now the critical path is A + B, and the 10 second stats |
| // each are bumped below 10%, bringing them below the "summary" threshold. |
| spawnResult = createSpawnResult(10 * 1000); |
| simulateActionExec(actionB, spawnResult); |
| stats = computer.aggregate(); |
| assertThat(stats).isNotNull(); |
| summary = stats.toStringSummary(); |
| assertThat(summary).doesNotContain("network:"); |
| assertThat(summary).doesNotContain("parse:"); |
| assertThat(summary).contains("queue:"); |
| assertThat(summary).doesNotContain("upload:"); |
| assertThat(summary).contains("setup:"); |
| assertThat(summary).contains("process:"); |
| assertThat(summary).doesNotContain("fetch:"); |
| assertThat(summary).doesNotContain("processOutputs:"); |
| assertThat(summary).doesNotContain("other:"); |
| } |
| |
| // The real value of durations are not important for the test, using the same unit for all |
| // declarations makes it easier to verify the aggregated values are correct. |
| @SuppressWarnings("CanonicalDuration") |
| @Test |
| public void testAggregateMetrics() throws Exception { |
| MockAction actionA = new MockAction(ImmutableList.of(), ImmutableSet.of(artifact("a.out"))); |
| MockAction actionB = |
| new MockAction(ImmutableList.of(artifact("a.out")), ImmutableSet.of(artifact("b.out"))); |
| MockAction actionC = |
| new MockAction(ImmutableList.of(artifact("b.out")), ImmutableSet.of(artifact("c.out"))); |
| MockAction actionD = |
| new MockAction(ImmutableList.of(artifact("c.out")), ImmutableSet.of(artifact("d.out"))); |
| |
| simulateActionExec( |
| actionA, |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setNetworkTimeInMs(1 * 1000) |
| .setParseTimeInMs(2 * 1000) |
| .setFetchTimeInMs(3 * 1000) |
| .setQueueTimeInMs(4 * 1000) |
| .setSetupTimeInMs(5 * 1000) |
| .setProcessOutputsTimeInMs(6 * 1000) |
| .setExecutionWallTimeInMs(7 * 1000) |
| .setUploadTimeInMs(8 * 1000) |
| .setTotalTimeInMs(100 * 1000) |
| .build())); |
| |
| simulateActionExec( |
| actionB, |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setNetworkTimeInMs(20 * 1000) |
| .setParseTimeInMs(30 * 1000) |
| .setFetchTimeInMs(40 * 1000) |
| .setQueueTimeInMs(50 * 1000) |
| .setSetupTimeInMs(60 * 1000) |
| .setProcessOutputsTimeInMs(70 * 1000) |
| .setExecutionWallTimeInMs(80 * 1000) |
| .setUploadTimeInMs(90 * 1000) |
| .setTotalTimeInMs(1000 * 1000) |
| .build())); |
| |
| simulateActionExec( |
| actionC, |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forWorkerExec() |
| .setNetworkTimeInMs(10 * 1000) |
| .setParseTimeInMs(20 * 1000) |
| .setFetchTimeInMs(30 * 1000) |
| .setQueueTimeInMs(40 * 1000) |
| .setSetupTimeInMs(50 * 1000) |
| .setProcessOutputsTimeInMs(60 * 1000) |
| .setExecutionWallTimeInMs(70 * 1000) |
| .setUploadTimeInMs(80 * 1000) |
| .setTotalTimeInMs(1000 * 1000) |
| .build())); |
| |
| simulateActionExec( |
| actionD, |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forWorkerExec() |
| .setNetworkTimeInMs(200 * 1000) |
| .setParseTimeInMs(300 * 1000) |
| .setFetchTimeInMs(400 * 1000) |
| .setQueueTimeInMs(500 * 1000) |
| .setSetupTimeInMs(600 * 1000) |
| .setProcessOutputsTimeInMs(700 * 1000) |
| .setExecutionWallTimeInMs(800 * 1000) |
| .setUploadTimeInMs(900 * 1000) |
| .setTotalTimeInMs(10000 * 1000) |
| .build())); |
| |
| AggregatedSpawnMetrics aggregated = computer.aggregate().getSpawnMetrics(); |
| SpawnMetrics remoteMetrics = aggregated.getMetrics(SpawnMetrics.ExecKind.REMOTE); |
| assertThat(remoteMetrics.networkTimeInMs()).isEqualTo(21 * 1000); |
| assertThat(remoteMetrics.parseTimeInMs()).isEqualTo(32 * 1000); |
| assertThat(remoteMetrics.fetchTimeInMs()).isEqualTo(43 * 1000); |
| assertThat(remoteMetrics.queueTimeInMs()).isEqualTo(54 * 1000); |
| assertThat(remoteMetrics.setupTimeInMs()).isEqualTo(65 * 1000); |
| assertThat(remoteMetrics.processOutputsTimeInMs()).isEqualTo(76 * 1000); |
| assertThat(remoteMetrics.executionWallTimeInMs()).isEqualTo(87 * 1000); |
| assertThat(remoteMetrics.uploadTimeInMs()).isEqualTo(98 * 1000); |
| assertThat(remoteMetrics.totalTimeInMs()).isEqualTo(1100 * 1000); |
| |
| SpawnMetrics workerMetrics = aggregated.getMetrics(SpawnMetrics.ExecKind.WORKER); |
| assertThat(workerMetrics.networkTimeInMs()).isEqualTo(210 * 1000); |
| assertThat(workerMetrics.parseTimeInMs()).isEqualTo(320 * 1000); |
| assertThat(workerMetrics.fetchTimeInMs()).isEqualTo(430 * 1000); |
| assertThat(workerMetrics.queueTimeInMs()).isEqualTo(540 * 1000); |
| assertThat(workerMetrics.setupTimeInMs()).isEqualTo(650 * 1000); |
| assertThat(workerMetrics.processOutputsTimeInMs()).isEqualTo(760 * 1000); |
| assertThat(workerMetrics.executionWallTimeInMs()).isEqualTo(870 * 1000); |
| assertThat(workerMetrics.uploadTimeInMs()).isEqualTo(980 * 1000); |
| assertThat(workerMetrics.totalTimeInMs()).isEqualTo(11000 * 1000); |
| } |
| |
| @Test |
| public void testEmptyCriticalPath() { |
| AggregatedCriticalPath empty = computer.aggregate(); |
| assertThat(empty.components()).isEmpty(); |
| assertThat(empty.totalTimeInMs()).isEqualTo(0); |
| checkTopComponentsTimes(computer); |
| } |
| |
| /** Tests that we only record the top slowest components and that we drop the rest. */ |
| @Test |
| public void testTopComponentsOverflow() throws Exception { |
| for (int i = 0; i <= 1000; i++) { |
| MockAction action = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact(i + ".out"))); |
| simulateActionExec(action, i); |
| } |
| long[] topTimes = new long[CriticalPathComputer.SLOWEST_COMPONENTS_SIZE]; |
| for (int i = 0; i < CriticalPathComputer.SLOWEST_COMPONENTS_SIZE; i++) { |
| topTimes[i] = 1000L - i; |
| } |
| checkTopComponentsTimes(computer, topTimes); |
| } |
| |
| @Test |
| public void testLargestMemoryComponentsOverflow() throws Exception { |
| for (int i = 0; i < 1000; i++) { |
| MockAction action = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact(i + ".out"))); |
| // the largest actions are in the middle |
| simulateActionExec( |
| action, |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setMemoryEstimateBytes(500 < i && i < 600 ? i : 0) |
| .setExecutionWallTimeInMs(1 * 1000) |
| .setTotalTimeInMs(i * 1000) |
| .build())); |
| } |
| |
| List<CriticalPathComponent> result = computer.getLargestMemoryComponents(); |
| |
| assertThat(result).hasSize(20); |
| assertThat(result.get(0).getSpawnMetrics().getRemoteMetrics().memoryEstimate()).isEqualTo(599); |
| } |
| |
| @Test |
| public void testLargestInputSizeComponentsOverflow() throws Exception { |
| for (int i = 0; i < 1000; i++) { |
| MockAction action = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact(i + ".out"))); |
| simulateActionExec( |
| action, |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setInputBytes(500 < i && i < 600 ? i : 0) |
| .setExecutionWallTimeInMs(1 * 1000) |
| .setTotalTimeInMs(i * 1000) |
| .build())); |
| } |
| |
| List<CriticalPathComponent> result = computer.getLargestInputSizeComponents(); |
| |
| assertThat(result).hasSize(20); |
| assertThat(result.get(0).getSpawnMetrics().getRemoteMetrics().inputBytes()).isEqualTo(599); |
| } |
| |
| @Test |
| public void testLargestInputCountComponentsOverflow() throws Exception { |
| for (int i = 0; i < 1000; i++) { |
| MockAction action = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact(i + ".out"))); |
| simulateActionExec( |
| action, |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setInputFiles(500 < i && i < 600 ? i : 0) |
| .setExecutionWallTimeInMs(1 * 1000) |
| .setTotalTimeInMs(i * 1000) |
| .build())); |
| } |
| |
| List<CriticalPathComponent> result = computer.getLargestInputCountComponents(); |
| |
| assertThat(result).hasSize(20); |
| assertThat(result.get(0).getSpawnMetrics().getRemoteMetrics().inputFiles()).isEqualTo(599); |
| } |
| |
| @Test |
| public void testActionCached() throws Exception { |
| MockAction cachedAction = |
| new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("cached.out"))); |
| |
| MockAction topLevelAction = |
| new MockAction( |
| Collections.singleton(artifact("cached.out")), ImmutableSet.of(artifact("top.out"))); |
| |
| computer.actionCached(new CachedActionEvent(cachedAction, clock.nanoTime())); |
| simulateActionExec(topLevelAction, 1000); |
| |
| AggregatedCriticalPath aggregated = computer.aggregate(); |
| |
| assertThat(aggregated.components()).hasSize(2); |
| assertActionMatches(topLevelAction, aggregated.components().get(0)); |
| assertActionMatches(cachedAction, aggregated.components().get(1)); |
| assertThat(aggregated.components().get(0).getElapsedTime()).isEqualTo(Duration.ofSeconds(1)); |
| assertThat(aggregated.components().get(1).getElapsedTime()).isEqualTo(Duration.ZERO); |
| |
| checkTopComponentsTimes(computer, 1000, 0L); |
| } |
| |
| /** Test that wall time is not computed using nanotime. */ |
| @Test |
| public void testWallTime() throws Exception { |
| simulateActionExec(new NullAction(), 2000); |
| checkCriticalPath(2000, "2.00"); |
| checkTopComponentsTimes(computer, 2000L); |
| NanosToMillisSinceEpochConverter converter = |
| BlazeClock.createNanosToMillisSinceEpochConverter(clock); |
| assertThat(computer.getMaxCriticalPath().getStartTimeMillisSinceEpoch(converter)).isEqualTo(0L); |
| } |
| |
| /** |
| * When running shared actions concurrently we might end up receiving multiple events, one per |
| * shared action. In that case we record a single component and we update the time of the maximum |
| * elapsed time. |
| */ |
| @Test |
| public void testConcurrentSharedActions() throws Exception { |
| MockAction shared1 = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("shared.out"))); |
| MockAction shared2 = new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("shared.out"))); |
| |
| MockAction action1 = |
| new MockAction( |
| Collections.singleton(artifact("shared.out")), |
| ImmutableSet.of(middlemanArtifact("action1.out"))); |
| |
| MockAction action2 = |
| new MockAction( |
| Collections.singleton(artifact("shared.out")), |
| ImmutableSet.of(middlemanArtifact("action2.out"))); |
| |
| long shared1Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(shared1, shared1Start)); |
| clock.advanceMillis(1000); |
| long shared2Start = clock.nanoTime(); |
| // We concurrently execute shared2 before shared1 could finish. But we record it as a cache hit. |
| computer.actionCached(new CachedActionEvent(shared2, shared2Start)); |
| clock.advanceMillis(1); |
| // Action2 depends on shared2, so it can start executing without waiting to shared1. This will |
| // prevent us from identifying the critical path in some circumstance, but we are OK with that. |
| simulateActionExec(action2, 11); |
| |
| computer.actionComplete( |
| new ActionCompletionEvent(shared1Start, shared1, mock(ActionLookupData.class))); |
| simulateActionExec(action1, 10); |
| AggregatedCriticalPath criticalPath = computer.aggregate(); |
| |
| // Yes, this is not correct but expected. While action2.time > action1.time, because |
| // action2 executed before shared1 finishes it incorrectly gets the time set by shared2. |
| assertActionMatches(action1, criticalPath.components().get(0)); |
| // We expect that the component used for any critical path is shared1, as it is the first that |
| // was started. |
| assertActionMatches(shared1, criticalPath.components().get(1)); |
| assertThat(criticalPath.components().get(1).getElapsedTime()) |
| .isEqualTo(Duration.ofMillis(1012)); |
| |
| List<CriticalPathComponent> slowest = computer.getSlowestComponents(); |
| assertThat(slowest).hasSize(3); |
| for (CriticalPathComponent cpath : slowest) { |
| if (actionMatches(shared1, cpath)) { |
| assertThat(cpath.getElapsedTime()).isEqualTo(Duration.ofMillis(1012)); |
| } |
| // While shared2 was a cache hit, because it was executed concurrently with shared1 we |
| // keep one component with the maximum time. |
| if (actionMatches(shared2, cpath)) { |
| assertThat(cpath.getElapsedTime()).isEqualTo(Duration.ofMillis(1012)); |
| } |
| if (actionMatches(action1, cpath)) { |
| assertThat(cpath.getElapsedTime()).isEqualTo(Duration.ofMillis(10)); |
| } |
| if (actionMatches(action2, cpath)) { |
| assertThat(cpath.getElapsedTime()).isEqualTo(Duration.ofMillis(11)); |
| assertThat(cpath.getChild().getElapsedTime()).isEqualTo(Duration.ofMillis(1012)); |
| } |
| } |
| } |
| |
| @Test |
| public void testTotalAggregateRunTimeWithGaps() throws Exception { |
| MockAction action1 = |
| new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("action1.out"))); |
| MockAction action2 = |
| new MockAction( |
| ImmutableSet.of(artifact("action1.out")), ImmutableSet.of(artifact("action2.out"))); |
| MockAction action3 = |
| new MockAction( |
| ImmutableSet.of(artifact("action2.out")), ImmutableSet.of(artifact("action3.out"))); |
| |
| long action1Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action1, action1Start)); |
| clock.advanceMillis(1000); |
| computer.actionComplete( |
| new ActionCompletionEvent(action1Start, action1, mock(ActionLookupData.class))); |
| |
| clock.advanceMillis(2000); |
| long action2Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action2, action2Start)); |
| clock.advanceMillis(3000); |
| computer.actionComplete( |
| new ActionCompletionEvent(action2Start, action2, mock(ActionLookupData.class))); |
| |
| clock.advanceMillis(2000); |
| long action3Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action3, action3Start)); |
| clock.advanceMillis(4000); |
| computer.actionComplete( |
| new ActionCompletionEvent(action3Start, action3, mock(ActionLookupData.class))); |
| |
| // The runtime of the critical path ignoring gaps is 8 seconds. |
| assertThat(computer.getMaxCriticalPath().getAggregatedElapsedTime()) |
| .isEqualTo(Duration.ofSeconds(8)); |
| assertThat(Duration.ofNanos(clock.nanoTime() - action1Start)).isEqualTo(Duration.ofSeconds(12)); |
| } |
| |
| @Test |
| public void testTotalAggregateRunTimeWithOverlappingTimes() throws Exception { |
| MockAction action1 = |
| new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("action1.out"))); |
| MockAction action2 = |
| new MockAction( |
| ImmutableSet.of(artifact("action1.out")), ImmutableSet.of(artifact("action2.out"))); |
| |
| long action1Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action1, action1Start)); |
| clock.advanceMillis(1000); |
| long action2Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action2, action2Start)); |
| clock.advanceMillis(2000); |
| computer.actionComplete( |
| new ActionCompletionEvent(action1Start, action1, mock(ActionLookupData.class))); |
| clock.advanceMillis(2000); |
| computer.actionComplete( |
| new ActionCompletionEvent(action2Start, action2, mock(ActionLookupData.class))); |
| |
| // The total run time of all actions in the critical path is 5 seconds. |
| assertThat(computer.getMaxCriticalPath().getAggregatedElapsedTime()) |
| .isEqualTo(Duration.ofSeconds(5)); |
| AggregatedCriticalPath criticalPath = computer.aggregate(); |
| assertThat(criticalPath.components()).hasSize(2); |
| // Action 2 has a run time of 4 seconds |
| assertThat(criticalPath.components().get(0).getElapsedTime()).isEqualTo(Duration.ofSeconds(4)); |
| // Action 1 has a run time of 3 seconds |
| assertThat(criticalPath.components().get(1).getElapsedTime()).isEqualTo(Duration.ofSeconds(3)); |
| } |
| |
| @Test |
| public void testTotalAggregateRunTimeWithParallelRuns() throws Exception { |
| MockAction action1 = |
| new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("action1.out"))); |
| MockAction action2 = |
| new MockAction( |
| ImmutableSet.of(artifact("action1.out")), ImmutableSet.of(artifact("action2.out"))); |
| |
| long action2Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action2, action2Start)); |
| clock.advanceMillis(1000); |
| long action1Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action1, action1Start)); |
| clock.advanceMillis(2000); |
| computer.actionComplete( |
| new ActionCompletionEvent(action1Start, action1, mock(ActionLookupData.class))); |
| clock.advanceMillis(2000); |
| computer.actionComplete( |
| new ActionCompletionEvent(action2Start, action2, mock(ActionLookupData.class))); |
| |
| // The total run time of all actions in the critical path is 5 seconds. |
| assertThat(computer.getMaxCriticalPath().getAggregatedElapsedTime()) |
| .isEqualTo(Duration.ofSeconds(5)); |
| AggregatedCriticalPath criticalPath = computer.aggregate(); |
| assertThat(criticalPath.components()).hasSize(2); |
| // Action 2 has a run time of 5 seconds |
| assertThat(criticalPath.components().get(0).getElapsedTime()).isEqualTo(Duration.ofSeconds(5)); |
| // Action 1 has a run time of 2 seconds |
| assertThat(criticalPath.components().get(1).getElapsedTime()).isEqualTo(Duration.ofSeconds(2)); |
| } |
| |
| @Test |
| public void testLongestTotalTime() throws Exception { |
| MockAction action1 = |
| new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("action1.out"))); |
| MockAction action2 = |
| new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("action2.out"))); |
| MockAction action3 = |
| new MockAction( |
| ImmutableList.of(artifact("action1.out"), artifact("action2.out")), |
| ImmutableSet.of(artifact("action3.out"))); |
| |
| // Action 1 - 0s - 3s |
| long action1Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action1, action1Start)); |
| clock.advanceMillis(3000); |
| computer.actionComplete( |
| new ActionCompletionEvent(action1Start, action1, mock(ActionLookupData.class))); |
| // Action 2 - 3s - 7s |
| long action2Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action2, action2Start)); |
| clock.advanceMillis(1000); |
| // Action 3 - 4s - 7s |
| long action3Start = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action3, action3Start)); |
| clock.advanceMillis(3000); |
| computer.actionComplete( |
| new ActionCompletionEvent(action2Start, action2, mock(ActionLookupData.class))); |
| computer.actionComplete( |
| new ActionCompletionEvent(action3Start, action3, mock(ActionLookupData.class))); |
| |
| // The total run time should be 6s (Action 1 + Action 3) since Action 2 overlaps with |
| // action 3, they will not be aggregated. |
| assertThat(computer.getMaxCriticalPath().getAggregatedElapsedTime()) |
| .isEqualTo(Duration.ofSeconds(6)); |
| AggregatedCriticalPath criticalPath = computer.aggregate(); |
| assertThat(criticalPath.components()).hasSize(2); |
| // Action 3 has a run time of 3 seconds |
| assertThat(criticalPath.components().get(0).getElapsedTime()).isEqualTo(Duration.ofSeconds(3)); |
| // Action 1 has a run time of 3 seconds |
| assertThat(criticalPath.components().get(1).getElapsedTime()).isEqualTo(Duration.ofSeconds(3)); |
| } |
| |
| @Test |
| public void rewoundActionMayStartTwice() throws Exception { |
| // This test demonstrates that a rewound action can cause two ActionStartedEvents to be emitted, |
| // one paired with an ActionRewoundEvent and the other with an ActionCompletedEvent, and the |
| // CriticalPathComputer handles it. |
| MockAction producer = |
| new MockAction(ImmutableSet.of(), ImmutableSet.of(artifact("shared.out"))); |
| MockAction consumer = |
| new MockAction( |
| Collections.singleton(artifact("shared.out")), |
| ImmutableSet.of(artifact("consumer.out"))); |
| |
| simulateActionExec(producer, 10); |
| long consumerFirstStart = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(consumer, consumerFirstStart)); |
| clock.advanceMillis(5); |
| computer.actionRewound(new ActionRewoundEvent(consumerFirstStart, consumer)); |
| |
| // In a real rewinding case, "producer" would be re-evaluated, and the events for that |
| // re-evaluation would be suppressed. This statement simulates that process by advancing the |
| // clock without any associated events. |
| clock.advanceMillis(10); |
| simulateActionExec(consumer, 20); |
| |
| AggregatedCriticalPath criticalPath = computer.aggregate(); |
| |
| assertActionMatches(consumer, criticalPath.components().get(0)); |
| assertActionMatches(producer, criticalPath.components().get(1)); |
| |
| assertThat(criticalPath.components().get(0).getElapsedTime()).isEqualTo(Duration.ofMillis(20)); |
| assertThat(criticalPath.components().get(1).getElapsedTime()).isEqualTo(Duration.ofMillis(10)); |
| |
| List<CriticalPathComponent> slowest = computer.getSlowestComponents(); |
| assertThat(slowest).hasSize(2); |
| for (CriticalPathComponent cpath : slowest) { |
| if (actionMatches(producer, cpath)) { |
| assertThat(cpath.getElapsedTime()).isEqualTo(Duration.ofMillis(10)); |
| } |
| if (actionMatches(consumer, cpath)) { |
| assertThat(cpath.getElapsedTime()).isEqualTo(Duration.ofMillis(20)); |
| } |
| } |
| } |
| |
| /** |
| * Check that the slowest components list does not duplicate entries when an action has multiple |
| * outputs. |
| */ |
| @Test |
| public void testSlowestComponentsNoDuplicates() throws Exception { |
| MockAction action = |
| new MockAction(ImmutableList.of(), ImmutableSet.of(artifact("a.out"), artifact("b.out"))); |
| simulateActionExec(action, 123); |
| |
| List<CriticalPathComponent> slowest = computer.getSlowestComponents(); |
| assertThat(slowest).hasSize(1); |
| } |
| |
| @Test |
| public void testSequentialActionExec() throws Exception { |
| simulateSequentialAndParallelActionExec( |
| new MockAction(ImmutableList.of(), ImmutableSet.of(artifact("a.out")), false), |
| ImmutableList.of( |
| ImmutableList.of(2 * 1000), ImmutableList.of(3 * 1000), ImmutableList.of(4 * 1000))); |
| SpawnMetrics metrics = computer.getMaxCriticalPath().getSpawnMetrics().getRemoteMetrics(); |
| assertThat(metrics.totalTimeInMs()).isEqualTo(9 * 1000); |
| } |
| |
| @Test |
| public void testMaximumSequentialAndParallelActionMetrics() throws Exception { |
| MockAction action = |
| new MockAction(ImmutableList.of(), ImmutableSet.of(artifact("a.out")), false); |
| |
| ImmutableList<ImmutableList<Integer>> seqAndParallelSeries = |
| ImmutableList.of( |
| ImmutableList.of(5 * 1000), // +5 |
| ImmutableList.of(1 * 1000, 3 * 1000), // +3 |
| ImmutableList.of(7 * 1000) // +7 |
| ); |
| |
| simulateSequentialAndParallelActionExec(action, seqAndParallelSeries); |
| SpawnMetrics metrics = computer.getMaxCriticalPath().getSpawnMetrics().getRemoteMetrics(); |
| assertThat(metrics.totalTimeInMs()).isEqualTo(15 * 1000); |
| } |
| |
| @Test |
| public void testInputDiscoveryAndAction() throws Exception { |
| Action action = new MockAction(ImmutableList.of(), ImmutableSet.of(artifact("a.out")), false); |
| simulateActionExec(action, 2 * 1000, 2 * 1000, true, 5 * 1000); |
| SpawnMetrics metrics = computer.getMaxCriticalPath().getSpawnMetrics().getRemoteMetrics(); |
| assertThat(metrics.parseTimeInMs()).isEqualTo(5 * 1000); |
| assertThat(metrics.executionWallTimeInMs()).isEqualTo(2 * 1000); |
| assertThat(metrics.totalTimeInMs()).isEqualTo(7 * 1000); |
| } |
| |
| @Test |
| public void testInputDiscoveryBeforeActionStarted() throws Exception { |
| Artifact artifact = artifact("a.out"); |
| Action action = new MockAction(ImmutableList.of(), ImmutableSet.of(artifact), false); |
| computer.discoverInputs( |
| new DiscoveredInputsEvent( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setParseTimeInMs(5 * 1000) |
| .setTotalTimeInMs(5 * 1000) |
| .build(), |
| action, |
| /* startTimeNanos= */ 0)); |
| |
| computer.actionComplete(new ActionCompletionEvent(0, action, mock(ActionLookupData.class))); |
| SpawnMetrics metrics = computer.getMaxCriticalPath().getSpawnMetrics().getRemoteMetrics(); |
| assertThat(metrics.parseTimeInMs()).isEqualTo(5 * 1000); |
| assertThat(metrics.totalTimeInMs()).isEqualTo(5 * 1000); |
| } |
| |
| @Test |
| public void testTryAddComponentShouldAddNonSharedActions() throws Exception { |
| Artifact artifact = artifact("a.out"); |
| MockAction sharedAction = new MockAction(ImmutableList.of(), ImmutableSet.of(artifact)); |
| MockAction nonSharedAction = |
| new MockAction( |
| ImmutableList.of(), |
| ImmutableSet.of(artifact), |
| /* middleman= */ false, |
| /* isShareable= */ false); |
| computer.actionStarted(new ActionStartedEvent(sharedAction, clock.nanoTime())); |
| IllegalStateException exception = |
| assertThrows( |
| IllegalStateException.class, |
| () -> |
| computer.actionStarted(new ActionStartedEvent(nonSharedAction, clock.nanoTime()))); |
| assertThat(exception) |
| .hasMessageThat() |
| .contains("Duplicate output artifact found for unsharable actions."); |
| } |
| |
| @Test |
| public void toleratesCriticalPathInconsistency() throws Exception { |
| Artifact depArtifact = derivedArtifact("test/a.out"); |
| Artifact parentArtifact = derivedArtifact("test/b.out"); |
| MockAction depAction = new MockAction(ImmutableList.of(), ImmutableSet.of(depArtifact)); |
| MockAction parentAction = |
| new MockAction(ImmutableList.of(depArtifact), ImmutableSet.of(parentArtifact)); |
| |
| computer.actionStarted(new ActionStartedEvent(depAction, clock.nanoTime())); |
| clock.advanceMillis(1000); |
| computer.actionStarted(new ActionStartedEvent(parentAction, clock.nanoTime())); |
| |
| // Complete the parent action while the dep action is still running and check that the resulting |
| // critical path ignores the still-running dep. |
| computer.actionComplete( |
| new ActionCompletionEvent(clock.nanoTime(), parentAction, mock(ActionLookupData.class))); |
| assertThat(Iterables.getOnlyElement(computer.aggregate().components()).getAction()) |
| .isEqualTo(parentAction); |
| } |
| |
| private void simulateActionExec(Action action, int totalTime) throws InterruptedException { |
| long nanoTimeStart = clock.nanoTime(); |
| if (action.getActionType().isMiddleman()) { |
| clock.advanceMillis(totalTime); |
| computer.middlemanAction(new ActionMiddlemanEvent(action, nanoTimeStart)); |
| } else { |
| computer.actionStarted(new ActionStartedEvent(action, nanoTimeStart)); |
| clock.advanceMillis(totalTime); |
| computer.actionComplete( |
| new ActionCompletionEvent(nanoTimeStart, action, mock(ActionLookupData.class))); |
| } |
| } |
| |
| private void simulateActionExec( |
| Action action, |
| int totalTimeInMs, |
| int processTimeInMs, |
| boolean completeAction, |
| int discoverInputsDurationInMs) |
| throws InterruptedException { |
| computer.discoverInputs( |
| new DiscoveredInputsEvent( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setParseTimeInMs(discoverInputsDurationInMs) |
| .setTotalTimeInMs(discoverInputsDurationInMs) |
| .build(), |
| action, |
| /* startTimeNanos= */ 0)); |
| simulateActionExec(action, totalTimeInMs, processTimeInMs, completeAction); |
| } |
| |
| private void simulateActionExec( |
| Action action, int totalTimeInMs, int processTimeInMs, boolean completeAction) |
| throws InterruptedException { |
| SpawnResult spawnResult = |
| createSpawnResult() |
| .setSpawnMetrics( |
| SpawnMetrics.Builder.forRemoteExec() |
| .setTotalTimeInMs(processTimeInMs) |
| .setExecutionWallTimeInMs(processTimeInMs) |
| .build()) |
| .build(); |
| simulateActionExec(action, totalTimeInMs, spawnResult, completeAction); |
| } |
| |
| private void simulateActionExec(Action action, SpawnResult.Builder spawnResult) |
| throws InterruptedException { |
| simulateActionExec( |
| action, spawnResult.build().getMetrics().totalTimeInMs(), spawnResult.build()); |
| } |
| |
| private void simulateActionExec(Action action, int totalTimeInMs, SpawnResult spawnResult) |
| throws InterruptedException { |
| simulateActionExec(action, totalTimeInMs, spawnResult, true); |
| } |
| |
| private void simulateActionExec( |
| Action action, int totalTimeInMs, SpawnResult spawnResult, boolean completeAction) |
| throws InterruptedException { |
| long startTime = clock.nanoTime(); |
| computer.actionStarted(new ActionStartedEvent(action, startTime)); |
| clock.advanceMillis(totalTimeInMs); |
| Spawn spawn = |
| new SimpleSpawn( |
| action, |
| /* arguments= */ ImmutableList.of(), |
| /* environment= */ ImmutableMap.of(), |
| /* executionInfo= */ ImmutableMap.of(), |
| action.getInputs(), |
| action.getOutputs(), |
| ResourceSet.ZERO); |
| computer.spawnExecuted(new SpawnExecutedEvent(spawn, spawnResult, Instant.now())); |
| if (completeAction) { |
| computer.actionComplete( |
| new ActionCompletionEvent(startTime, action, mock(ActionLookupData.class))); |
| } |
| } |
| |
| private void simulateSequentialAndParallelActionExec( |
| Action action, ImmutableList<ImmutableList<Integer>> totalTimesInMs) |
| throws InterruptedException { |
| long startTime = clock.nanoTime(); |
| for (ImmutableList<Integer> parallelDuration : totalTimesInMs) { |
| for (Integer phaseDuration : parallelDuration) { |
| simulateActionExec(action, phaseDuration, phaseDuration, false); |
| } |
| computer.nextCriticalPathPhase(new ChangePhase(action)); |
| } |
| computer.actionComplete( |
| new ActionCompletionEvent(startTime, action, mock(ActionLookupData.class))); |
| } |
| |
| private Artifact derivedArtifact(String path) { |
| DerivedArtifact artifact = |
| (DerivedArtifact) |
| ActionsTestUtil.createArtifactWithExecPath( |
| derivedArtifactRoot, PathFragment.create(path)); |
| artifact.setGeneratingActionKey(ActionsTestUtil.NULL_ACTION_LOOKUP_DATA); |
| return artifact; |
| } |
| |
| private Artifact artifact(String path) { |
| return ActionsTestUtil.createArtifactWithExecPath(artifactRoot, PathFragment.create(path)); |
| } |
| |
| private Artifact middlemanArtifact(String path) { |
| return ActionsTestUtil.createArtifact(middlemanRoot, path); |
| } |
| |
| private void checkCriticalPath(int totalWallTimeInMillis, String totalWallTimeStr) { |
| AggregatedCriticalPath criticalPath = computer.aggregate(); |
| |
| assertThat(criticalPath).isNotNull(); |
| assertThat(criticalPath.totalTimeInMs()).isEqualTo(totalWallTimeInMillis); |
| |
| String summary = criticalPath.toStringSummary(); |
| assertThat(summary).contains("Critical Path: " + totalWallTimeStr + "s"); |
| } |
| |
| @CanIgnoreReturnValue |
| private AggregatedCriticalPath checkCriticalPath( |
| Duration totalWallTime, |
| Duration totalTime, |
| Duration totalProcessTime, |
| final String totalWallTimeStr, |
| final String totalTimePercent, |
| final String totalProcessPercent) { |
| AggregatedCriticalPath criticalPath = computer.aggregate(); |
| |
| assertThat(criticalPath).isNotNull(); |
| assertThat(criticalPath.totalTimeInMs()).isEqualTo(totalWallTime.toMillis()); |
| assertThat(criticalPath.getSpawnMetrics().getRemoteMetrics().totalTimeInMs()) |
| .isEqualTo(totalTime.toMillis()); |
| assertThat(criticalPath.getSpawnMetrics().getRemoteMetrics().executionWallTimeInMs()) |
| .isEqualTo(totalProcessTime.toMillis()); |
| |
| String summary = criticalPath.toStringSummary(); |
| assertThat(summary).contains("Critical Path: " + totalWallTimeStr + "s"); |
| assertThat(summary).contains("Remote (" + totalTimePercent + "% of the time)"); |
| assertThat(summary).contains("process: " + totalProcessPercent + "%"); |
| |
| return criticalPath; |
| } |
| |
| private static void checkTopComponentsTimes(CriticalPathComputer computer, long... times) { |
| List<CriticalPathComponent> topComponents = computer.getSlowestComponents(); |
| assertThat(topComponents).hasSize(times.length); |
| |
| for (int i = 0; i < times.length; i++) { |
| assertThat(topComponents.get(i).getElapsedTime()).isEqualTo(Duration.ofMillis(times[i])); |
| } |
| } |
| |
| private static SpawnResult.Builder createSpawnResult(int processTimeInMs) { |
| SpawnResult.Builder spawnResult = new SpawnResult.Builder(); |
| spawnResult.setStatus(SpawnResult.Status.SUCCESS); |
| spawnResult.setExitCode(0); |
| spawnResult.setWallTimeInMs(processTimeInMs); |
| spawnResult.setRunnerName("test"); |
| return spawnResult; |
| } |
| |
| private static SpawnResult.Builder createSpawnResult() { |
| return createSpawnResult(0); |
| } |
| } |