blob: 8d1d3e8a6b3d58f11ba05d002eff6f93404ac1eb [file] [log] [blame]
// Copyright 2020 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.profiler;
import com.google.auto.value.AutoValue;
import com.google.devtools.build.lib.profiler.statistics.PhaseSummaryStatistics;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.zip.GZIPInputStream;
import javax.annotation.Nullable;
/**
* Utility class to handle parsing the JSON trace profiles.
*
* <p>The format itself is documented in
* https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
*/
public final class JsonProfile {
private BuildMetadata buildMetadata;
private PhaseSummaryStatistics phaseSummaryStatistics;
private List<TraceEvent> traceEvents;
public JsonProfile(File profileFile) throws IOException {
this(getInputStream(profileFile));
}
public JsonProfile(InputStream inputStream) throws IOException {
try (JsonReader reader =
new JsonReader(
new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)))) {
if (reader.peek() == JsonToken.BEGIN_OBJECT) {
reader.beginObject();
while (reader.hasNext()) {
String objectKey = reader.nextName();
if ("otherData".equals(objectKey)) {
buildMetadata = parseBuildMetadata(reader);
} else if ("traceEvents".equals(objectKey)) {
traceEvents = TraceEvent.parseTraceEvents(reader);
phaseSummaryStatistics = new PhaseSummaryStatistics();
TraceEvent lastPhaseEvent = null;
Duration maxEndTime = Duration.ZERO;
for (TraceEvent traceEvent : traceEvents) {
if (traceEvent.timestamp() != null) {
Duration curEndTime = traceEvent.timestamp();
if (traceEvent.duration() != null) {
curEndTime = curEndTime.plus(traceEvent.duration());
}
if (curEndTime.compareTo(maxEndTime) > 0) {
maxEndTime = curEndTime;
}
}
if (ProfilerTask.PHASE.description.equals(traceEvent.category())) {
if (lastPhaseEvent != null) {
phaseSummaryStatistics.addProfilePhase(
ProfilePhase.getPhaseFromDescription(lastPhaseEvent.name()),
traceEvent.timestamp().minus(lastPhaseEvent.timestamp()));
}
lastPhaseEvent = traceEvent;
}
}
if (lastPhaseEvent != null) {
phaseSummaryStatistics.addProfilePhase(
ProfilePhase.getPhaseFromDescription(lastPhaseEvent.name()),
maxEndTime.minus(lastPhaseEvent.timestamp()));
}
} else {
reader.skipValue();
}
}
}
}
if (traceEvents == null) {
throw new IOException("Corrupted profile file: couldn't find 'traceEvents'.");
}
}
private static InputStream getInputStream(File profileFile) throws IOException {
InputStream inputStream = new FileInputStream(profileFile);
if (profileFile.getName().endsWith(".gz")) {
inputStream = new GZIPInputStream(inputStream);
}
return inputStream;
}
private static BuildMetadata parseBuildMetadata(JsonReader reader) throws IOException {
reader.beginObject();
String buildId = null;
String date = null;
String outputBase = null;
while (reader.hasNext()) {
switch (reader.nextName()) {
case "build_id":
buildId = reader.nextString();
break;
case "date":
date = reader.nextString();
break;
case "output_base":
outputBase = reader.nextString();
break;
default:
reader.skipValue();
}
}
reader.endObject();
return BuildMetadata.create(buildId, date, outputBase);
}
public PhaseSummaryStatistics getPhaseSummaryStatistics() {
return phaseSummaryStatistics;
}
public List<TraceEvent> getTraceEvents() {
return traceEvents;
}
@Nullable
public BuildMetadata getBuildMetadata() {
return buildMetadata;
}
/** Value class to hold build metadata (id, date, output base) if available. */
@AutoValue
public abstract static class BuildMetadata {
public static BuildMetadata create(
@Nullable String buildId, @Nullable String date, @Nullable String outputBase) {
return new AutoValue_JsonProfile_BuildMetadata(buildId, date, outputBase);
}
@Nullable
public abstract String buildId();
@Nullable
public abstract String date();
@Nullable
public abstract String outputBase();
}
}