// Copyright 2018 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.grapher; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.profiler.CpuUsageTimeSeries; import com.google.gson.stream.JsonReader; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.StringJoiner; /** * An experimental tool to turn Json profiles into graphs. Do not depend on the continuing existence * of this tool. * *

Run this tool like so: bazel run \ * //src/main/java/com/google/devtools/build/lib/profiler:profiler-grapher \ * -- /path/to/command.profile > /tmp/tmp.csv * *

Plot the resulting CSV with gnuplot like so: gnuplot -p -e \ * set datafile sep ','; plot for [col=1:3] '/tmp/tmp.csv' using col lw 2 with lines \ * title columnheader" */ public class ProfileGrapher { public static final long DEFAULT_BUCKET_SIZE_MILLIS = 1000; public static void main(String[] args) throws IOException { if (args.length != 1) { System.err.println("Requires filename of a Bazel profile in Chrome event trace format."); System.exit(1); } String filename = args[0]; // TODO(twerth): Make it possible to select the set of profiler task descriptions on the command // line. ImmutableList profilerTaskDescriptions = ImmutableList.of( "Remote execution process wall time", "action processing", "Remote execution file fetching"); Map seriesMap = new LinkedHashMap<>(); for (String profilerTaskDescription : profilerTaskDescriptions) { seriesMap.put(profilerTaskDescription, new CpuUsageTimeSeries(0, DEFAULT_BUCKET_SIZE_MILLIS)); } long maxEndTime = 0; // TODO(twerth): Support gzip compressed profiles. try (JsonReader reader = new JsonReader( new BufferedReader( new InputStreamReader(new FileInputStream(filename), StandardCharsets.UTF_8)))) { reader.beginArray(); while (reader.hasNext()) { reader.beginObject(); Map data = new HashMap<>(); while (reader.hasNext()) { String name = reader.nextName(); Object value; switch (reader.peek()) { case BOOLEAN: value = reader.nextBoolean(); break; case NUMBER: value = reader.nextDouble(); break; case STRING: value = reader.nextString(); break; default: reader.skipValue(); continue; } data.put(name, value); } Object cat = data.get("cat"); CpuUsageTimeSeries series = seriesMap.get(cat); if (series != null) { Long endTimeMillis = decodeAndAdd(series, data); if (endTimeMillis != null) { maxEndTime = Math.max(maxEndTime, endTimeMillis); } } reader.endObject(); } reader.endArray(); } // Instead of generating a CSV here, we could generate Json and use the Google Charts API to // generate interactive web graphs (https://developers.google.com/chart/). int len = (int) (maxEndTime / DEFAULT_BUCKET_SIZE_MILLIS) + 1; double[][] numbers = new double[seriesMap.size()][]; List> allSeries = new ArrayList<>(seriesMap.entrySet()); // Write the titles in the first line of the CSV StringJoiner stringJoiner = new StringJoiner(","); for (int i = 0; i < numbers.length; i++) { stringJoiner.add(allSeries.get(i).getKey()); numbers[i] = allSeries.get(i).getValue().toDoubleArray(len); } System.out.println(stringJoiner.toString()); for (int i = 0; i < numbers[0].length; i++) { stringJoiner = new StringJoiner(","); for (int j = 0; j < numbers.length; j++) { stringJoiner.add(String.valueOf(numbers[j][i])); } System.out.println(stringJoiner.toString()); } } /** * Decodes the start time and duration from the data, adds it to the given series and returns the * end time in milliseconds if it was possible to decode the data, otherwise null. */ private static Long decodeAndAdd(CpuUsageTimeSeries series, Map data) { Double ts = (Double) data.get("ts"); Double dur = (Double) data.get("dur"); if (ts == null || dur == null) { return null; } long durationMillis = Math.round(dur.doubleValue() / 1000); long startTimeMillis = Math.round(ts.doubleValue() / 1000); long endTimeMillis = startTimeMillis + durationMillis; series.addRange(startTimeMillis, endTimeMillis); return endTimeMillis; } }