/*
* Copyright 2016-present Facebook, Inc.
*
* 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.facebook.buck.event.listener;
import static org.hamcrest.MatcherAssert.assertThat;
import com.facebook.buck.artifact_cache.CacheResult;
import com.facebook.buck.artifact_cache.CacheResultType;
import com.facebook.buck.event.ParsingEvent;
import com.facebook.buck.event.WatchmanStatusEvent;
import com.facebook.buck.log.CacheUploadInfo;
import com.facebook.buck.log.PerfTimesStats;
import com.facebook.buck.log.views.JsonViews;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleDurationTracker;
import com.facebook.buck.rules.BuildRuleEvent;
import com.facebook.buck.rules.BuildRuleKeys;
import com.facebook.buck.rules.BuildRuleStatus;
import com.facebook.buck.rules.BuildRuleSuccessType;
import com.facebook.buck.rules.FakeBuildRule;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.testutil.JsonMatcher;
import com.facebook.buck.timing.Clock;
import com.facebook.buck.timing.DefaultClock;
import com.facebook.buck.util.ObjectMappers;
import com.facebook.buck.util.autosparse.AutoSparseStateEvents;
import com.facebook.buck.util.versioncontrol.SparseSummary;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.common.hash.HashCode;
import java.io.IOException;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;
public class MachineReadableLogJsonViewTest {
private static final ObjectWriter WRITER =
ObjectMappers.legacyCreate()
.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false)
.writerWithView(JsonViews.MachineReadableLog.class);
private long timestamp;
private long nanoTime;
private long threadUserNanoTime;
private long threadId;
private BuildId buildId;
private BuildRuleDurationTracker durationTracker;
@Before
public void setUp() {
Clock clock = new DefaultClock();
timestamp = clock.currentTimeMillis();
nanoTime = clock.nanoTime();
// Not using real value as not all JVMs will support thread user time.
threadUserNanoTime = new Random().nextLong();
threadId = 0;
buildId = new BuildId("Test");
durationTracker = new BuildRuleDurationTracker();
}
@Test
public void testWatchmanEvents() throws Exception {
WatchmanStatusEvent createEvent = WatchmanStatusEvent.fileCreation("filename_new");
WatchmanStatusEvent deleteEvent = WatchmanStatusEvent.fileDeletion("filename_del");
WatchmanStatusEvent overflowEvent = WatchmanStatusEvent.overflow("reason");
// Configure the events so timestamps etc are there.
createEvent.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
deleteEvent.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
overflowEvent.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
assertJsonEquals("{%s,\"filename\":\"filename_new\"}", WRITER.writeValueAsString(createEvent));
assertJsonEquals("{%s,\"filename\":\"filename_del\"}", WRITER.writeValueAsString(deleteEvent));
assertJsonEquals("{%s,\"reason\":\"reason\"}", WRITER.writeValueAsString(overflowEvent));
}
@Test
public void testParsingEvents() throws Exception {
ParsingEvent symlink = ParsingEvent.symlinkInvalidation("target");
ParsingEvent envChange = ParsingEvent.environmentalChange("diff");
symlink.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
envChange.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
assertJsonEquals("{%s,\"path\":\"target\"}", WRITER.writeValueAsString(symlink));
assertJsonEquals("{%s,\"diff\":\"diff\"}", WRITER.writeValueAsString(envChange));
}
@Test
public void testAutosparseEvents() throws Exception {
AutoSparseStateEvents.SparseRefreshStarted startEvent =
new AutoSparseStateEvents.SparseRefreshStarted();
AutoSparseStateEvents finishedEvent =
new AutoSparseStateEvents.SparseRefreshFinished(
startEvent, SparseSummary.of(0, 5, 0, 10, 0, 0));
String expectedSummary =
"{"
+ "\"profiles_added\":0,\"include_rules_added\":5,\"exclude_rules_added\":0,"
+ "\"files_added\":10,\"files_dropped\":0,\"files_conflicting\":0}";
AutoSparseStateEvents failedEvent =
new AutoSparseStateEvents.SparseRefreshFailed(startEvent, "output string\n");
startEvent.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
finishedEvent.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
failedEvent.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
assertJsonEquals("{%s}", WRITER.writeValueAsString(startEvent));
assertJsonEquals(
"{%s,\"summary\":" + expectedSummary + "}", WRITER.writeValueAsString(finishedEvent));
assertJsonEquals(
"{%s,\"output\":\"output string\\n\"}", WRITER.writeValueAsString(failedEvent));
}
@Test
public void testBuildRuleEvent() throws IOException {
BuildRule rule = new FakeBuildRule("//fake:rule");
BuildRuleEvent.Started started = BuildRuleEvent.started(rule, durationTracker);
started.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
BuildRuleEvent.Finished event =
BuildRuleEvent.finished(
started,
BuildRuleKeys.builder()
.setRuleKey(new RuleKey("aaaa"))
.setInputRuleKey(Optional.of(new RuleKey("bbbb")))
.build(),
BuildRuleStatus.SUCCESS,
CacheResult.of(
CacheResultType.MISS,
Optional.of("my-secret-source"),
Optional.empty(),
Optional.empty(),
Optional.empty()),
Optional.empty(),
Optional.of(BuildRuleSuccessType.BUILT_LOCALLY),
Optional.of(HashCode.fromString("abcd42")),
Optional.empty(),
Optional.empty());
event.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
String message = WRITER.writeValueAsString(event);
assertJsonEquals(
"{%s,\"status\":\"SUCCESS\",\"cacheResult\":{\"type\":\"MISS\","
+ "\"cacheSource\":\"my-secret-source\"},"
+ "\"buildRule\":{\"name\":\"//fake:rule\"},"
+ "\"ruleKeys\":{\"ruleKey\":{\"hashCode\":\"aaaa\"},"
+ "\"inputRuleKey\":{\"hashCode\":\"bbbb\"}},"
+ "\"outputHash\":\"abcd42\"},",
message);
}
@Test
public void testPerfTimesStatsEvent() throws IOException {
PerfTimesEventListener.PerfTimesEvent.Complete event =
PerfTimesEventListener.PerfTimesEvent.complete(
PerfTimesStats.builder()
.setPythonTimeMs(4L)
.setInitTimeMs(8L)
.setProcessingTimeMs(15L)
.setActionGraphTimeMs(16L)
.setBuildTimeMs(23L)
.setInstallTimeMs(42L)
.build());
event.configure(timestamp, nanoTime, threadUserNanoTime, threadId, buildId);
String message = WRITER.writeValueAsString(event);
assertJsonEquals(
"{%s,"
+ "\"perfTimesStats\":{"
+ "\"pythonTimeMs\":4,"
+ "\"initTimeMs\":8,"
+ "\"parseTimeMs\":0,"
+ "\"processingTimeMs\":15,"
+ "\"actionGraphTimeMs\":16,"
+ "\"rulekeyTimeMs\":0,"
+ "\"fetchTimeMs\":0,"
+ "\"buildTimeMs\":23,"
+ "\"installTimeMs\":42}}",
message);
}
@Test
public void testCacheUploadInfo() throws IOException {
CacheUploadInfo cacheUploadInfo =
CacheUploadInfo.of(new AtomicInteger(1), new AtomicInteger(2));
assertJsonEquals(
WRITER.writeValueAsString(cacheUploadInfo),
"{\"successUploadCount\":1,\"failureUploadCount\":2}");
}
private void assertJsonEquals(String expected, String actual) throws IOException {
String commonHeader = String.format("\"timestamp\":%d", timestamp);
assertThat(actual, new JsonMatcher(String.format(expected, commonHeader)));
}
}