| // 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 static java.util.Objects.requireNonNull; |
| |
| 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 |
| */ |
| public record TraceEvent( |
| @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) { |
| public TraceEvent { |
| requireNonNull(name, "name"); |
| } |
| |
| 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 TraceEvent( |
| category, |
| name, |
| type, |
| timestamp, |
| duration, |
| processId, |
| threadId, |
| args, |
| primaryOutputPath, |
| targetLabel, |
| mnemonic, |
| configuration); |
| } |
| |
| // Only applicable to action-related TraceEvents. |
| |
| 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; |
| } |
| } |