|  | // 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.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ImmutableMap; | 
|  | import com.google.gson.stream.JsonReader; | 
|  | import com.google.gson.stream.JsonToken; | 
|  | import java.io.IOException; | 
|  | import java.time.Duration; | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  | import javax.annotation.Nullable; | 
|  |  | 
|  | /** | 
|  | * Represents a single trace event in a JSON profile. | 
|  | * | 
|  | * <p>Format is documented in | 
|  | * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview | 
|  | */ | 
|  | @AutoValue | 
|  | public abstract class TraceEvent { | 
|  | public static TraceEvent create( | 
|  | @Nullable String category, | 
|  | String name, | 
|  | @Nullable String type, | 
|  | @Nullable Duration timestamp, | 
|  | @Nullable Duration duration, | 
|  | long processId, | 
|  | long threadId, | 
|  | @Nullable ImmutableMap<String, Object> args, | 
|  | @Nullable String primaryOutputPath, | 
|  | @Nullable String targetLabel, | 
|  | @Nullable String mnemonic, | 
|  | @Nullable String configuration) { | 
|  | return new AutoValue_TraceEvent( | 
|  | category, | 
|  | name, | 
|  | type, | 
|  | timestamp, | 
|  | duration, | 
|  | processId, | 
|  | threadId, | 
|  | args, | 
|  | primaryOutputPath, | 
|  | targetLabel, | 
|  | mnemonic, | 
|  | configuration); | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | public abstract String category(); | 
|  |  | 
|  | public abstract String name(); | 
|  |  | 
|  | @Nullable | 
|  | public abstract String type(); | 
|  |  | 
|  | @Nullable | 
|  | public abstract Duration timestamp(); | 
|  |  | 
|  | @Nullable | 
|  | public abstract Duration duration(); | 
|  |  | 
|  | public abstract long processId(); | 
|  |  | 
|  | public abstract long threadId(); | 
|  |  | 
|  | @Nullable | 
|  | public abstract ImmutableMap<String, Object> args(); | 
|  |  | 
|  | // Only applicable to action-related TraceEvents. | 
|  | @Nullable | 
|  | public abstract String primaryOutputPath(); | 
|  |  | 
|  | @Nullable | 
|  | public abstract String targetLabel(); | 
|  |  | 
|  | @Nullable | 
|  | public abstract String mnemonic(); | 
|  |  | 
|  | @Nullable | 
|  | public abstract String configuration(); | 
|  |  | 
|  | private static TraceEvent createFromJsonReader(JsonReader reader) throws IOException { | 
|  | String category = null; | 
|  | String name = null; | 
|  | Duration timestamp = null; | 
|  | Duration duration = null; | 
|  | long processId = -1; | 
|  | long threadId = -1; | 
|  | String primaryOutputPath = null; | 
|  | String targetLabel = null; | 
|  | String mnemonic = null; | 
|  | String type = null; | 
|  | String configuration = null; | 
|  | ImmutableMap<String, Object> args = null; | 
|  |  | 
|  | reader.beginObject(); | 
|  | while (reader.hasNext()) { | 
|  | switch (reader.nextName()) { | 
|  | case "cat" -> category = reader.nextString(); | 
|  | case "name" -> name = reader.nextString(); | 
|  | case "ph" -> type = reader.nextString(); | 
|  | case "ts" -> | 
|  | // Duration has no microseconds :-/. | 
|  | timestamp = Duration.ofNanos(reader.nextLong() * 1000); | 
|  | case "dur" -> duration = Duration.ofNanos(reader.nextLong() * 1000); | 
|  | case "pid" -> processId = reader.nextLong(); | 
|  | case "tid" -> threadId = reader.nextLong(); | 
|  | case "out" -> primaryOutputPath = reader.nextString(); | 
|  | case "args" -> { | 
|  | args = parseMap(reader); | 
|  | Object target = args.get("target"); | 
|  | targetLabel = target instanceof String ? (String) target : null; | 
|  | Object mnemonicValue = args.get("mnemonic"); | 
|  | mnemonic = mnemonicValue instanceof String ? (String) mnemonicValue : null; | 
|  | Object configurationValue = args.get("configuration"); | 
|  | configuration = configurationValue instanceof String ? (String) configurationValue : null; | 
|  | } | 
|  | default -> reader.skipValue(); | 
|  | } | 
|  | } | 
|  | reader.endObject(); | 
|  | return TraceEvent.create( | 
|  | category, | 
|  | name, | 
|  | type, | 
|  | timestamp, | 
|  | duration, | 
|  | processId, | 
|  | threadId, | 
|  | args, | 
|  | primaryOutputPath, | 
|  | targetLabel, | 
|  | mnemonic, | 
|  | configuration); | 
|  | } | 
|  |  | 
|  | private static ImmutableMap<String, Object> parseMap(JsonReader reader) throws IOException { | 
|  | ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder(); | 
|  |  | 
|  | reader.beginObject(); | 
|  | while (reader.peek() != JsonToken.END_OBJECT) { | 
|  | String name = reader.nextName(); | 
|  | Object val = parseSingleValueRecursively(reader); | 
|  | builder.put(name, val); | 
|  | } | 
|  | reader.endObject(); | 
|  |  | 
|  | return builder.buildOrThrow(); | 
|  | } | 
|  |  | 
|  | private static ImmutableList<Object> parseArray(JsonReader reader) throws IOException { | 
|  | ImmutableList.Builder<Object> builder = ImmutableList.builder(); | 
|  |  | 
|  | reader.beginArray(); | 
|  | while (reader.peek() != JsonToken.END_ARRAY) { | 
|  | Object val = parseSingleValueRecursively(reader); | 
|  | builder.add(val); | 
|  | } | 
|  | reader.endArray(); | 
|  |  | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | private static Object parseSingleValueRecursively(JsonReader reader) throws IOException { | 
|  | JsonToken nextToken = reader.peek(); | 
|  | return switch (nextToken) { | 
|  | case BOOLEAN -> reader.nextBoolean(); | 
|  | case NULL -> { | 
|  | reader.nextNull(); | 
|  | yield null; | 
|  | } | 
|  | case NUMBER -> | 
|  | // Json's only numeric type is number, using Double to accommodate all types | 
|  | reader.nextDouble(); | 
|  | case STRING -> reader.nextString(); | 
|  | case BEGIN_OBJECT -> parseMap(reader); | 
|  | case BEGIN_ARRAY -> parseArray(reader); | 
|  | default -> throw new IOException("Unexpected token " + nextToken.name()); | 
|  | }; | 
|  | } | 
|  |  | 
|  | public static List<TraceEvent> parseTraceEvents(JsonReader reader) throws IOException { | 
|  | List<TraceEvent> traceEvents = new ArrayList<>(); | 
|  | reader.beginArray(); | 
|  | while (reader.hasNext()) { | 
|  | traceEvents.add(createFromJsonReader(reader)); | 
|  | } | 
|  | reader.endArray(); | 
|  | return traceEvents; | 
|  | } | 
|  | } |