| // 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 Duration timestamp, | |
| @Nullable Duration duration, | |
| long threadId, | |
| @Nullable String primaryOutputPath, | |
| @Nullable String targetLabel) { | |
| return new AutoValue_TraceEvent( | |
| category, name, timestamp, duration, threadId, primaryOutputPath, targetLabel); | |
| } | |
| @Nullable | |
| public abstract String category(); | |
| public abstract String name(); | |
| @Nullable | |
| public abstract Duration timestamp(); | |
| @Nullable | |
| public abstract Duration duration(); | |
| public abstract long threadId(); | |
| // Only applicable to action-related TraceEvents. | |
| @Nullable | |
| public abstract String primaryOutputPath(); | |
| @Nullable | |
| public abstract String targetLabel(); | |
| private static TraceEvent createFromJsonReader(JsonReader reader) throws IOException { | |
| String category = null; | |
| String name = null; | |
| Duration timestamp = null; | |
| Duration duration = null; | |
| long threadId = -1; | |
| String primaryOutputPath = null; | |
| String targetLabel = null; | |
| reader.beginObject(); | |
| while (reader.hasNext()) { | |
| switch (reader.nextName()) { | |
| case "cat": | |
| category = reader.nextString(); | |
| break; | |
| case "name": | |
| name = reader.nextString(); | |
| break; | |
| case "ts": | |
| // Duration has no microseconds :-/. | |
| timestamp = Duration.ofNanos(reader.nextLong() * 1000); | |
| break; | |
| case "dur": | |
| duration = Duration.ofNanos(reader.nextLong() * 1000); | |
| break; | |
| case "tid": | |
| threadId = reader.nextLong(); | |
| break; | |
| case "out": | |
| primaryOutputPath = reader.nextString(); | |
| break; | |
| case "args": | |
| ImmutableMap<String, Object> args = parseMap(reader); | |
| Object target = args.get("target"); | |
| targetLabel = target instanceof String ? (String) target : null; | |
| break; | |
| default: | |
| reader.skipValue(); | |
| } | |
| } | |
| reader.endObject(); | |
| return TraceEvent.create( | |
| category, name, timestamp, duration, threadId, primaryOutputPath, targetLabel); | |
| } | |
| 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.build(); | |
| } | |
| 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(); | |
| } | |
| private static Object parseSingleValueRecursively(JsonReader reader) throws IOException { | |
| JsonToken nextToken = reader.peek(); | |
| switch (nextToken) { | |
| case BOOLEAN: | |
| return reader.nextBoolean(); | |
| case NULL: | |
| reader.nextNull(); | |
| return null; | |
| case NUMBER: | |
| // Json's only numeric type is number, using Double to accommodate all types | |
| return reader.nextDouble(); | |
| case STRING: | |
| return reader.nextString(); | |
| case BEGIN_OBJECT: | |
| return parseMap(reader); | |
| case BEGIN_ARRAY: | |
| return 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; | |
| } | |
| } |