aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar twerth <twerth@google.com>2018-08-13 11:05:05 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-08-13 11:06:56 -0700
commit0da2b8003ed8e42b3bc407de101d1173be87da6f (patch)
treedc158c3de12315ca781fac801c2ba19540807fc3
parent37c70a125a0282d331a1ec92397b750d50367551 (diff)
Add experimental tool to turn Bazel Json profiles into graphs.
RELNOTES: None PiperOrigin-RevId: 208509424
-rw-r--r--src/main/java/com/google/devtools/build/lib/profiler/BUILD13
-rw-r--r--src/main/java/com/google/devtools/build/lib/profiler/grapher/ProfileGrapher.java145
2 files changed, 158 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/profiler/BUILD b/src/main/java/com/google/devtools/build/lib/profiler/BUILD
index 85c506dceb..bf703c4d4d 100644
--- a/src/main/java/com/google/devtools/build/lib/profiler/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/profiler/BUILD
@@ -43,3 +43,16 @@ java_library(
"//third_party:jsr305",
],
)
+
+java_binary(
+ name = "profiler-grapher",
+ srcs = glob([
+ "grapher/*.java",
+ ]),
+ main_class = "com.google.devtools.build.lib.profiler.grapher.ProfileGrapher",
+ deps = [
+ ":profiler",
+ "//third_party:gson",
+ "//third_party:guava",
+ ],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/profiler/grapher/ProfileGrapher.java b/src/main/java/com/google/devtools/build/lib/profiler/grapher/ProfileGrapher.java
new file mode 100644
index 0000000000..6f77404c21
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/profiler/grapher/ProfileGrapher.java
@@ -0,0 +1,145 @@
+// 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.
+ *
+ * <p>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
+ *
+ * <p>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<String> profilerTaskDescriptions =
+ ImmutableList.of(
+ "Remote execution process wall time",
+ "action processing",
+ "Remote execution file fetching");
+ Map<String, CpuUsageTimeSeries> 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<String, Object> 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<Map.Entry<String, CpuUsageTimeSeries>> 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<String, Object> 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;
+ }
+}