|  | // 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(); | 
|  | } | 
|  | } |