From b75db4a02dbfd166236d984e8d03e9527572acd3 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Mon, 10 Jun 2019 16:42:34 -0700 Subject: [PATCH 1/6] Query Perfetto counters when loading a Perfetto trace. Gathers data about all available counters in a Perfetto trace during loading. --- .../com/google/gapid/models/Perfetto.java | 36 ++++++++++-- .../gapid/perfetto/models/CounterInfo.java | 58 +++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 gapic/src/main/com/google/gapid/perfetto/models/CounterInfo.java diff --git a/gapic/src/main/com/google/gapid/models/Perfetto.java b/gapic/src/main/com/google/gapid/models/Perfetto.java index 9b027a8f92..3f0f706e64 100644 --- a/gapic/src/main/com/google/gapid/models/Perfetto.java +++ b/gapic/src/main/com/google/gapid/models/Perfetto.java @@ -15,18 +15,22 @@ */ package com.google.gapid.models; +import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap; import static com.google.gapid.rpc.UiErrorCallback.error; import static com.google.gapid.rpc.UiErrorCallback.success; import static com.google.gapid.util.Logging.throttleLogRpcError; import static com.google.gapid.util.MoreFutures.transform; import static com.google.gapid.util.MoreFutures.transformAsync; import static com.google.gapid.widgets.Widgets.scheduleIfNotDisposed; +import static java.util.function.Function.identity; import static java.util.logging.Level.WARNING; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.models.CounterInfo; import com.google.gapid.perfetto.models.ProcessInfo; import com.google.gapid.perfetto.models.QueryEngine; import com.google.gapid.perfetto.models.ThreadInfo; @@ -82,8 +86,9 @@ protected ListenableFuture doLoad(Path.Capture source) { return transformAsync(withStatus("Examining the trace...", examineTrace(data)), $1 -> transformAsync(withStatus("Querying threads...", queryThreads(data)), $2 -> - transform(withStatus("Enumerating tracks...", enumerateTracks(data)), $3 -> - data.build()))); + transformAsync(withStatus("Querying counters...", queryCounters(data)), $3 -> + transform(withStatus("Enumerating tracks...", enumerateTracks(data)), $4 -> + data.build())))); } private static ListenableFuture examineTrace(Data.Builder data) { @@ -97,6 +102,10 @@ private static ListenableFuture queryThreads(Data.Builder data) { return ThreadInfo.listThreads(data); } + private static ListenableFuture queryCounters(Data.Builder data) { + return CounterInfo.listCounters(data); + } + private static ListenableFuture enumerateTracks(Data.Builder data) { return Tracks.enumerate(data); } @@ -156,16 +165,18 @@ public static class Data { public final int numCpus; public final ImmutableMap processes; public final ImmutableMap threads; + public final ImmutableMap counters; public final TrackConfig tracks; public Data(QueryEngine queries, TimeSpan traceTime, int numCpus, ImmutableMap processes, ImmutableMap threads, - TrackConfig tracks) { + ImmutableMap counters, TrackConfig tracks) { this.qe = queries; this.traceTime = traceTime; this.numCpus = numCpus; this.processes = processes; this.threads = threads; + this.counters = counters; this.tracks = tracks; } @@ -175,6 +186,8 @@ public static class Builder { private int numCpus; private ImmutableMap processes; private ImmutableMap threads; + private ImmutableMap counters; + private ImmutableListMultimap countersByName; public final TrackConfig.Builder tracks = new TrackConfig.Builder(); public Builder(QueryEngine qe) { @@ -217,8 +230,23 @@ public Builder setThreads(ImmutableMap threads) { return this; } + public ImmutableMap getCounters() { + return counters; + } + + public ImmutableListMultimap getCountersByName() { + return countersByName; + } + + public Builder setCounters(ImmutableMap counters) { + this.counters = counters; + this.countersByName = counters.values().stream() + .collect(toImmutableListMultimap(c -> c.name, identity())); + return this; + } + public Data build() { - return new Data(qe, traceTime, numCpus, processes, threads, tracks.build()); + return new Data(qe, traceTime, numCpus, processes, threads, counters, tracks.build()); } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/CounterInfo.java b/gapic/src/main/com/google/gapid/perfetto/models/CounterInfo.java new file mode 100644 index 0000000000..3114cf0ba5 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/models/CounterInfo.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * 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.gapid.perfetto.models; + +import static com.google.gapid.util.MoreFutures.transform; + +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.models.Perfetto; + +public class CounterInfo { + private static final String LIST_SQL = + "select counter_id, name, ref, ref_type, min(value), max(value) " + + "from counter_definitions left join counter_values using (counter_id) " + + "group by counter_id"; + + public final long id; + public final String name; + public final long ref; + public final String refType; + public final double min; + public final double max; + + public CounterInfo(long id, String name, long ref, String refType, double min, double max) { + this.id = id; + this.name = name; + this.ref = ref; + this.refType = refType; + this.min = min; + this.max = max; + } + + private CounterInfo(QueryEngine.Row row) { + this(row.getLong(0), row.getString(1), row.getLong(2), row.getString(3), row.getDouble(4), + row.getDouble(5)); + } + + public static ListenableFuture listCounters(Perfetto.Data.Builder data) { + return transform(data.qe.query(LIST_SQL), res -> { + ImmutableMap.Builder counters = ImmutableMap.builder(); + res.forEachRow((i, r) -> counters.put(r.getLong(0), new CounterInfo(r))); + return data.setCounters(counters.build()); + }); + } +} From 2dd8118e5c9d2987e943fc657ac6e454fcbe5394 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Mon, 10 Jun 2019 16:27:43 -0700 Subject: [PATCH 2/6] Use the counter info in the memory summary track. --- .../perfetto/models/MemorySummaryTrack.java | 63 +++++++++---------- .../google/gapid/perfetto/models/Tracks.java | 9 +-- 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/gapic/src/main/com/google/gapid/perfetto/models/MemorySummaryTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/MemorySummaryTrack.java index d8f30af051..861206f74e 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/MemorySummaryTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/MemorySummaryTrack.java @@ -20,27 +20,21 @@ import static com.google.gapid.perfetto.models.QueryEngine.createWindow; import static com.google.gapid.perfetto.models.QueryEngine.dropTable; import static com.google.gapid.perfetto.models.QueryEngine.dropView; -import static com.google.gapid.perfetto.models.QueryEngine.expectOneRow; +import static com.google.gapid.perfetto.views.TrackContainer.single; import static com.google.gapid.util.MoreFutures.transform; import static com.google.gapid.util.MoreFutures.transformAsync; import static java.lang.String.format; +import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.models.Perfetto; +import com.google.gapid.perfetto.views.MemorySummaryPanel; /** * {@link Track} containing the total system memory usage data. */ public class MemorySummaryTrack extends Track { - private static final String DEF_TABLE = "counter_definitions"; - private static final String DEF_SQL = "select " + - "(select counter_id from " + DEF_TABLE + " where name = 'MemTotal' and ref_type is null), " + - "(select counter_id from " + DEF_TABLE + " where name = 'MemFree' and ref_type is null), " + - "(select counter_id from " + DEF_TABLE + " where name = 'Buffers' and ref_type is null), " + - "(select counter_id from " + DEF_TABLE + " where name = 'Cached' and ref_type is null), " + - "(select counter_id from " + DEF_TABLE + " where name = 'SwapCached' and ref_type is null)"; - private static final String MAX_SQL = - "select cast(max(value) as int) from counter_values where counter_id = %d"; private static final String VIEW_SQL = "select ts, lead(ts) over (order by ts) - ts dur, max(a) total, max(b) unused," + " max(c) + max(d) + max(e) buffCache " + @@ -60,14 +54,14 @@ public class MemorySummaryTrack extends Track { "select ts, ts + dur, total, unused, buffCache from %s"; private final long maxTotal; - private final int totalId; - private final int unusedId; - private final int buffersId; - private final int cachedId; - private final int swapCachedId; - - public MemorySummaryTrack( - long maxTotal, int totalId, int unusedId, int buffersId, int cachedId, int swapCachedId) { + private final long totalId; + private final long unusedId; + private final long buffersId; + private final long cachedId; + private final long swapCachedId; + + public MemorySummaryTrack(long maxTotal, long totalId, long unusedId, long buffersId, + long cachedId, long swapCachedId) { super("mem_sum"); this.maxTotal = maxTotal; this.totalId = totalId; @@ -133,23 +127,26 @@ private String counterSQL() { return format(COUNTER_SQL, tableName("span")); } - public static ListenableFuture enumerate(QueryEngine qe) { - return transformAsync(expectOneRow(qe.query(DEF_SQL)), res -> { - int total = res.getInt(0, -1); - int unusued = res.getInt(1, -1); - int buffers = res.getInt(2, -1); - int cached = res.getInt(3, -1); - int swapCached = res.getInt(4, -1); - if ((total < 0) || (unusued < 0) || (buffers < 0) || (cached < 0) || (swapCached < 0)) { - return Futures.immediateFuture(null); - } - return transform(expectOneRow(qe.query(maxTotalSql(total))), max -> - new MemorySummaryTrack(max.getLong(0), total, unusued, buffers, cached, swapCached)); - }); + public static ListenableFuture enumerate(Perfetto.Data.Builder data) { + CounterInfo total = onlyOne(data.getCountersByName().get("MemTotal")); + CounterInfo free = onlyOne(data.getCountersByName().get("MemFree")); + CounterInfo buffers = onlyOne(data.getCountersByName().get("Buffers")); + CounterInfo cached = onlyOne(data.getCountersByName().get("Cached")); + CounterInfo swapCached = onlyOne(data.getCountersByName().get("SwapCached")); + if ((total == null) || (free == null) || (buffers == null) || (cached == null) || + (swapCached == null)) { + return Futures.immediateFuture(data); + } + + MemorySummaryTrack track = new MemorySummaryTrack( + (long)total.max, total.id, free.id, buffers.id, cached.id, swapCached.id); + data.tracks.addTrack(null, track.getId(), "Memory Usage", + single(state -> new MemorySummaryPanel(state, track), true)); + return Futures.immediateFuture(data); } - private static String maxTotalSql(long id) { - return format(MAX_SQL, id); + private static CounterInfo onlyOne(ImmutableList counters) { + return (counters.size() != 1) ? null : counters.get(0); } public static class Data extends Track.Data { diff --git a/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java b/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java index f473997b7e..39515b0377 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java @@ -25,7 +25,6 @@ import com.google.gapid.models.Perfetto; import com.google.gapid.perfetto.canvas.Panel; import com.google.gapid.perfetto.views.CpuSummaryPanel; -import com.google.gapid.perfetto.views.MemorySummaryPanel; import com.google.gapid.perfetto.views.ProcessSummaryPanel; import com.google.gapid.perfetto.views.ThreadPanel; import com.google.gapid.perfetto.views.TitlePanel; @@ -64,13 +63,7 @@ private static ListenableFuture enumerateCpu(Perfetto.Dat public static ListenableFuture enumerateCounters( Perfetto.Data.Builder data) { - return transform(MemorySummaryTrack.enumerate(data.qe), track -> { - if (track != null) { - data.tracks.addTrack(null, track.getId(), "Memory Usage", - single(state -> new MemorySummaryPanel(state, track))); - } - return data; - }); + return MemorySummaryTrack.enumerate(data); } public static Perfetto.Data.Builder enumerateProcesses(Perfetto.Data.Builder data) { From 1348f091710f76c7e1eb52fcb94ec44197e6a1c8 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Mon, 10 Jun 2019 16:31:08 -0700 Subject: [PATCH 3/6] Be explicit about track separator lines. --- .../google/gapid/perfetto/models/CpuTrack.java | 6 +++--- .../com/google/gapid/perfetto/models/Tracks.java | 4 ++-- .../gapid/perfetto/views/TrackContainer.java | 15 +++++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/gapic/src/main/com/google/gapid/perfetto/models/CpuTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/CpuTrack.java index b5bee44830..3045f78d0b 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/CpuTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/CpuTrack.java @@ -152,14 +152,14 @@ public static ListenableFuture enumerate( for (int i = 0; i < data.getNumCpus(); i++) { QueryEngine.Row freq = freqMap.get(Long.valueOf(i)); CpuTrack track = new CpuTrack(i); - data.tracks.addTrack( - parent, track.getId(), "CPU " + (i + 1), single(state -> new CpuPanel(state, track))); + data.tracks.addTrack(parent, track.getId(), "CPU " + (i + 1), + single(state -> new CpuPanel(state, track), false)); if (freq != null) { CpuFrequencyTrack freqTrack = new CpuFrequencyTrack(i, freq.getLong(1), freq.getDouble(2), freq.getLong(3)); data.tracks.addTrack( parent, freqTrack.getId(), "CPU " + (i + 1) + " Frequency", - single(state -> new CpuFrequencyPanel(state, freqTrack))); + single(state -> new CpuFrequencyPanel(state, freqTrack), false)); } } return data; diff --git a/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java b/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java index 39515b0377..9d167d9894 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java @@ -100,10 +100,10 @@ public static Perfetto.Data.Builder enumerateProcesses(Perfetto.Data.Builder dat threads.forEach(track -> { TrackConfig.Track.UiFactory ui; if (track.getThread().maxDepth == 0) { - ui = single(state -> new ThreadPanel(state, track)); + ui = single(state -> new ThreadPanel(state, track), false); } else { ui = single( - state -> new ThreadPanel(state, track), ThreadPanel::setCollapsed, true); + state -> new ThreadPanel(state, track), false, ThreadPanel::setCollapsed, true); } String threadParent = (track.getThread().totalDur >= idleCutoffThread || !hasIdleThreads) ? summary.getId() : summary.getId() + "_idle"; diff --git a/gapic/src/main/com/google/gapid/perfetto/views/TrackContainer.java b/gapic/src/main/com/google/gapid/perfetto/views/TrackContainer.java index 20e8b2ddac..3759ec3136 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/TrackContainer.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/TrackContainer.java @@ -47,18 +47,19 @@ private TrackContainer() { } public static TrackConfig.Track.UiFactory single( - TrackConfig.Track.UiFactory track) { - return state -> new Single(track.createPanel(state), null, true); + TrackConfig.Track.UiFactory track, boolean sep) { + return state -> new Single(track.createPanel(state), sep, null, true); } public static TrackConfig.Track.UiFactory single( - TrackConfig.Track.UiFactory track, BiConsumer filter, boolean initial) { + TrackConfig.Track.UiFactory track, boolean sep, BiConsumer filter, + boolean initial) { return state -> { T panel = track.createPanel(state); if (initial) { filter.accept(panel, initial); } - return new Single(panel, filtered -> filter.accept(panel, filtered), initial); + return new Single(panel, sep, filtered -> filter.accept(panel, filtered), initial); }; } @@ -90,12 +91,14 @@ public static TrackConfig.Group.UiFactory group(TrackConfig.Track.UiFactory filter; protected boolean filtered; - public Single(TrackPanel track, Consumer filter, boolean filtered) { + public Single(TrackPanel track, boolean sep, Consumer filter, boolean filtered) { this.track = track; + this.sep = sep; this.filter = filter; this.filtered = filtered; } @@ -125,7 +128,7 @@ public void render(RenderContext ctx, Repainter repainter) { ctx.setForegroundColor(colors().panelBorder); ctx.drawLine(LABEL_WIDTH - 1, 0, LABEL_WIDTH - 1, height); - ctx.drawLine(LABEL_WIDTH, height - 1, width, height - 1); + ctx.drawLine(sep ? 0 : LABEL_WIDTH, height - 1, width, height - 1); track.render(ctx, repainter); } From b2178b8b7fcf791950f7b41b9a4873c4a3dc455e Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Mon, 10 Jun 2019 16:32:36 -0700 Subject: [PATCH 4/6] Adds a Perffeto counter track UI. --- .../gapid/perfetto/models/CounterTrack.java | 112 ++++++++++++++ .../gapid/perfetto/views/CounterPanel.java | 137 ++++++++++++++++++ .../gapid/perfetto/views/StyleConstants.java | 7 + 3 files changed, 256 insertions(+) create mode 100644 gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java create mode 100644 gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java diff --git a/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java new file mode 100644 index 0000000000..02f413caef --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * 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.gapid.perfetto.models; + +import static com.google.gapid.perfetto.models.QueryEngine.createSpan; +import static com.google.gapid.perfetto.models.QueryEngine.createView; +import static com.google.gapid.perfetto.models.QueryEngine.createWindow; +import static com.google.gapid.perfetto.models.QueryEngine.dropTable; +import static com.google.gapid.perfetto.models.QueryEngine.dropView; +import static com.google.gapid.util.MoreFutures.transform; +import static com.google.gapid.util.MoreFutures.transformAsync; +import static java.lang.String.format; + +import com.google.common.util.concurrent.ListenableFuture; + +public class CounterTrack extends Track { + private static final String VIEW_SQL = + "select ts, lead(ts) over (order by ts) - ts dur, value " + + "from counter_values where counter_id = %d"; + private static final String SUMMARY_SQL = + "select min(ts), max(ts + dur), avg(value) from %s group by quantum_ts"; + private static final String COUNTER_SQL = "select ts, ts + dur, value from %s"; + + private final long id; + private final double min; + private final double max; + + public CounterTrack(long id, double min, double max) { + super("counter_" + id); + this.id = id; + this.min = min; + this.max = max; + } + + public double getMin() { + return min; + } + + public double getMax() { + return max; + } + + @Override + protected ListenableFuture initialize(QueryEngine qe) { + String vals = tableName("vals"); + String span = tableName("span"); + String window = tableName("window"); + return qe.queries( + dropTable(span), + dropTable(window), + dropView(vals), + createView(vals, viewSql()), + createWindow(window), + createSpan(span, vals + ", " + window)); + } + + private String viewSql() { + return format(VIEW_SQL, id); + } + + @Override + protected ListenableFuture computeData(QueryEngine qe, DataRequest req) { + Window win = Window.compute(req, 5); + return transformAsync(win.update(qe, tableName("window")), $ -> computeData(qe, req, win)); + } + + private ListenableFuture computeData(QueryEngine qe, DataRequest req, Window win) { + return transform(qe.query(win.quantized ? summarySql() : counterSQL()), res -> { + int rows = res.getNumRows(); + Data data = new Data(req, new long[rows + 1], new double[rows + 1]); + res.forEachRow((i, r) -> { + data.ts[i] = r.getLong(0); + data.values[i] = r.getDouble(2); + }); + data.ts[rows] = res.getLong(rows - 1, 1, 0); + data.values[rows] = data.values[rows - 1]; + return data; + }); + } + + private String summarySql() { + return format(SUMMARY_SQL, tableName("span")); + } + + private String counterSQL() { + return format(COUNTER_SQL, tableName("span")); + } + + public static class Data extends Track.Data { + public final long[] ts; + public final double[] values; + + public Data(DataRequest request, long[] ts, double[] values) { + super(request); + this.ts = ts; + this.values = values; + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java new file mode 100644 index 0000000000..0f6bd4bb1a --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * 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.gapid.perfetto.views; + +import static com.google.gapid.perfetto.views.Loading.drawLoading; +import static com.google.gapid.perfetto.views.StyleConstants.colors; + +import com.google.gapid.perfetto.canvas.Area; +import com.google.gapid.perfetto.canvas.RenderContext; +import com.google.gapid.perfetto.canvas.Size; +import com.google.gapid.perfetto.models.CounterTrack; + +public class CounterPanel extends TrackPanel { + private static final double HEIGHT = 30; + private static final double HOVER_MARGIN = 10; + private static final double HOVER_PADDING = 4; + private static final double CURSOR_SIZE = 5; + + private final CounterTrack track; + private final String name; + protected HoverCard hovered = null; + protected double mouseXpos = 0; + + public CounterPanel(State state, CounterTrack track, String name) { + super(state); + this.track = track; + this.name = name; + } + + @Override + public String getTitle() { + return name; + } + + @Override + public double getHeight() { + return HEIGHT; + } + + @Override + protected void renderTrack(RenderContext ctx, Repainter repainter, double w, double h) { + ctx.trace("Counter", () -> { + CounterTrack.Data data = track.getData(state, () -> { + repainter.repaint(new Area(0, 0, width, height)); + }); + drawLoading(ctx, data, state, h); + + if (data == null) { + return; + } + + double min = Math.min(0, track.getMin()), range = track.getMax() - min; + ctx.setForegroundColor(colors().counter); + ctx.path(path -> { + path.moveTo(0, h); + double lastX = 0, lastY = h; + for (int i = 0; i < data.ts.length; i++) { + double nextX = state.timeToPx(data.ts[i]); + double nextY = (HEIGHT - 1) * (1 - (data.values[i] - min) / range); + path.lineTo(nextX, lastY); + path.lineTo(nextX, nextY); + lastX = nextX; + lastY = nextY; + } + path.lineTo(lastX, h); + ctx.drawPath(path); + }); + + if (hovered != null) { + ctx.setBackgroundColor(colors().hoverBackground); + ctx.fillRect(mouseXpos + HOVER_MARGIN, 0, 2 * HOVER_PADDING + hovered.size.w, HEIGHT); + ctx.setForegroundColor(colors().textMain); + ctx.drawText( + hovered.label, mouseXpos + HOVER_MARGIN + HOVER_PADDING, (HEIGHT - hovered.size.h) / 2); + + ctx.drawCircle( + mouseXpos, (HEIGHT - 1) * (1 - (hovered.value - min) / range), CURSOR_SIZE / 2); + } + }); + } + + @Override + protected Hover onTrackMouseMove(TextMeasurer m, double x, double y) { + CounterTrack.Data data = track.getData(state, () -> { /* nothing */ }); + if (data == null || data.ts.length == 0) { + return Hover.NONE; + } + + long time = state.pxToTime(x); + int idx = 0; + for (; idx < data.ts.length - 1; idx++) { + if (data.ts[idx + 1] > time) { + break; + } + } + + hovered = new HoverCard(m, data.values[idx]); + mouseXpos = state.timeToPx(data.ts[idx]); + return new Hover() { + @Override + public Area getRedraw() { + return new Area(mouseXpos - CURSOR_SIZE, 0, + CURSOR_SIZE + HOVER_MARGIN + HOVER_PADDING + hovered.size.w + HOVER_PADDING, HEIGHT); + } + + @Override + public void stop() { + hovered = null; + } + }; + } + + private static class HoverCard { + public final double value; + public final String label; + public final Size size; + + public HoverCard(TextMeasurer tm, double value) { + this.value = value; + this.label = String.format("Value: %,d", Math.round(value)); + this.size = tm.measure(label); + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/StyleConstants.java b/gapic/src/main/com/google/gapid/perfetto/views/StyleConstants.java index 1471af23c6..f5ba81379a 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/StyleConstants.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/StyleConstants.java @@ -51,6 +51,7 @@ public static class Colors { public final RGBA cpuUsageStroke; public final RGBA cpuFreqIdle; public final RGBA timelineRuler; + public final RGBA counter; public final RGBA textMain; public final RGBA textAlt; @@ -79,6 +80,7 @@ public Colors(int background, RGBA cpuUsageStroke, RGBA cpuFreqIdle, RGBA timelineRuler, + RGBA counter, RGBA textMain, RGBA textAlt, RGBA textInvertedMain, @@ -103,6 +105,7 @@ public Colors(int background, this.cpuUsageStroke = cpuUsageStroke; this.cpuFreqIdle = cpuFreqIdle; this.timelineRuler = timelineRuler; + this.counter = counter; this.textMain = textMain; this.textAlt = textAlt; this.textInvertedMain = textInvertedMain; @@ -129,6 +132,7 @@ public Colors(int background, private static final RGBA LIGHT_CPU_USAGE_STROKE = rgb(0x0D, 0x9A, 0xA8); private static final RGBA LIGHT_CPU_FREQ_IDLE = rgb(240, 240, 240); private static final RGBA LIGHT_TIMELINE_RULER = rgb(0x99, 0x99, 0x99); + private static final RGBA LIGHT_COUNTER = rgb(0x00, 0xB8, 0xD4); private static final RGBA LIGHT_TEXT_MAIN = rgb(0x32, 0x34, 0x35); private static final RGBA LIGHT_TEXT_ALT = rgb(101, 102, 104); @@ -159,6 +163,7 @@ public static Colors light() { LIGHT_CPU_USAGE_STROKE, LIGHT_CPU_FREQ_IDLE, LIGHT_TIMELINE_RULER, + LIGHT_COUNTER, LIGHT_TEXT_MAIN, LIGHT_TEXT_ALT, LIGHT_TEXT_INVERTED_MAIN, @@ -185,6 +190,7 @@ public static Colors light() { private static final RGBA DARK_CPU_USAGE_STROKE = rgb(0x0D, 0x9A, 0xA8); private static final RGBA DARK_CPU_FREQ_IDLE = rgb(240, 240, 240); private static final RGBA DARK_TIMELINE_RULER = rgb(0x99, 0x99, 0x99); + private static final RGBA DARK_COUNTER = rgb(0x00, 0xB8, 0xD4); private static final RGBA DARK_TEXT_MAIN = rgb(0xff, 0xff, 0xff); private static final RGBA DARK_TEXT_ALT = rgb(0xdd, 0xdd, 0xdd); @@ -215,6 +221,7 @@ public static Colors dark() { DARK_CPU_USAGE_STROKE, DARK_CPU_FREQ_IDLE, DARK_TIMELINE_RULER, + DARK_COUNTER, DARK_TEXT_MAIN, DARK_TEXT_ALT, DARK_TEXT_INVERTED_MAIN, From 16cd59e4d31d1256ab3c2b6a04e847ca852cc018 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Mon, 10 Jun 2019 16:35:31 -0700 Subject: [PATCH 5/6] Show GPU counter tracks in a new GPU group. --- .../google/gapid/perfetto/models/Tracks.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java b/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java index 9d167d9894..dcedc54311 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java @@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.models.Perfetto; import com.google.gapid.perfetto.canvas.Panel; +import com.google.gapid.perfetto.views.CounterPanel; import com.google.gapid.perfetto.views.CpuSummaryPanel; import com.google.gapid.perfetto.views.ProcessSummaryPanel; import com.google.gapid.perfetto.views.ThreadPanel; @@ -46,8 +47,11 @@ private Tracks() { public static ListenableFuture enumerate(Perfetto.Data.Builder data) { return transformAsync(enumerateCpu(data), $1 -> - transform(enumerateCounters(data), $2 -> - enumerateProcesses(data))); + transform(enumerateCounters(data), $2 -> { + enumerateGpu(data); + enumerateProcesses(data); + return data; + })); } private static ListenableFuture enumerateCpu(Perfetto.Data.Builder data) { @@ -66,6 +70,26 @@ public static ListenableFuture enumerateCounters( return MemorySummaryTrack.enumerate(data); } + public static Perfetto.Data.Builder enumerateGpu(Perfetto.Data.Builder data) { + boolean found = false; + for (CounterInfo counter : data.getCounters().values()) { + if ("gpu".equals(counter.refType)) { + if (!found) { + addGpuGroup(data); + found = true; + } + CounterTrack track = new CounterTrack(counter.id, counter.min, counter.max); + data.tracks.addTrack("gpu", track.getId(), counter.name, + single(state -> new CounterPanel(state, track, counter.name), true)); + } + } + return data; + } + + private static void addGpuGroup(Perfetto.Data.Builder data) { + data.tracks.addLabelGroup(null, "gpu", "GPU", group(state -> new TitlePanel("GPU"), true)); + } + public static Perfetto.Data.Builder enumerateProcesses(Perfetto.Data.Builder data) { List processes = Lists.newArrayList(data.getProcesses().values()); Collections.sort(processes, (p1, p2) -> Long.compare(p2.totalDur, p1.totalDur)); From 0870f84b9d8f48579d09d12e03bea072e5b1da7b Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Fri, 17 May 2019 14:39:28 -0700 Subject: [PATCH 6/6] Counter selection UI. --- .../gapid/perfetto/models/CounterTrack.java | 117 ++++++++++++++++++ .../gapid/perfetto/views/CounterPanel.java | 11 +- .../perfetto/views/CountersSelectionView.java | 55 ++++++++ .../gapid/perfetto/views/Selectable.java | 2 +- 4 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 gapic/src/main/com/google/gapid/perfetto/views/CountersSelectionView.java diff --git a/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java index 02f413caef..b69fa0e432 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java @@ -25,6 +25,13 @@ import static java.lang.String.format; import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.views.CountersSelectionView; +import com.google.gapid.perfetto.views.State; + +import org.eclipse.swt.widgets.Composite; + +import java.util.Arrays; public class CounterTrack extends Track { private static final String VIEW_SQL = @@ -33,6 +40,9 @@ public class CounterTrack extends Track { private static final String SUMMARY_SQL = "select min(ts), max(ts + dur), avg(value) from %s group by quantum_ts"; private static final String COUNTER_SQL = "select ts, ts + dur, value from %s"; + private static final String RANGE_SQL = + "select ts, ts + dur, value from %s " + + "where ts + dur >= %d and ts <= %d order by ts"; private final long id; private final double min; @@ -99,6 +109,22 @@ private String counterSQL() { return format(COUNTER_SQL, tableName("span")); } + public ListenableFuture getValues(QueryEngine qe, TimeSpan ts) { + return transform(qe.query(rangeSql(ts)), res -> { + int rows = res.getNumRows(); + Data data = new Data(null, new long[rows], new double[rows]); + res.forEachRow((i, r) -> { + data.ts[i] = r.getLong(0); + data.values[i] = r.getDouble(2); + }); + return data; + }); + } + + private String rangeSql(TimeSpan ts) { + return format(RANGE_SQL, tableName("vals"), ts.start, ts.end); + } + public static class Data extends Track.Data { public final long[] ts; public final double[] values; @@ -109,4 +135,95 @@ public Data(DataRequest request, long[] ts, double[] values) { this.values = values; } } + + public static class Values implements Selection, Selection.CombiningBuilder.Combinable { + public final long[] ts; + public final String[] names; + public final double[][] values; + + public Values(String name, Data data) { + this.ts = data.ts; + this.names = new String[] { name }; + this.values = new double[][] { data.values }; + } + + private Values(long[] ts, String[] names, double[][] values) { + this.ts = ts; + this.names = names; + this.values = values; + } + + @Override + public String getTitle() { + return "Counters"; + } + + @Override + public Composite buildUi(Composite parent, State state) { + return new CountersSelectionView(parent, state, this); + } + + @Override + public Values combine(Values other) { + long[] newTs = combineTs(ts, other.ts); + + double[][] newValues = new double[names.length + other.names.length][newTs.length]; + for (int i = 0, me = 0, them = 0; i < newTs.length; i++) { + long rTs = newTs[i], meTs = ts[me], themTs = other.ts[them]; + if (rTs == meTs) { + for (int n = 0; n < names.length; n++) { + newValues[n][i] = values[n][me]; + } + me = Math.min(me + 1, ts.length - 1); + } else if (i > 0) { + for (int n = 0; n < names.length; n++) { + newValues[n][i] = newValues[n][i - 1]; + } + } + + if (rTs == themTs) { + for (int n = 0; n < other.names.length; n++) { + newValues[n + names.length][i] = other.values[n][them]; + } + them = Math.min(them + 1, other.ts.length - 1); + } else if (i > 0) { + for (int n = 0; n < other.names.length; n++) { + newValues[names.length + n][i] = newValues[names.length + n][i - 1]; + } + } + } + + String[] newNames = Arrays.copyOf(names, names.length + other.names.length); + System.arraycopy(other.names, 0, newNames, names.length, other.names.length); + return new Values(newTs, newNames, newValues); + } + + private static long[] combineTs(long[] a, long[] b) { + long[] r = new long[a.length + b.length]; + int ai = 0, bi = 0, ri = 0; + for (; ai < a.length && bi < b.length; ri++) { + long av = a[ai], bv = b[bi]; + if (av == bv) { + r[ri] = av; + ai++; + bi++; + } else if (av < bv) { + r[ri] = av; + ai++; + } else { + r[ri] = bv; + bi++; + } + } + // One of these copies does nothing. + System.arraycopy(a, ai, r, ri, a.length - ai); + System.arraycopy(b, bi, r, ri, b.length - bi); + return Arrays.copyOf(r, ri + a.length - ai + b.length - bi); // Truncate array. + } + + @Override + public Selection build() { + return this; + } + } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java index 0f6bd4bb1a..d3c93f57cc 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java @@ -17,13 +17,16 @@ import static com.google.gapid.perfetto.views.Loading.drawLoading; import static com.google.gapid.perfetto.views.StyleConstants.colors; +import static com.google.gapid.util.MoreFutures.transform; +import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.RenderContext; import com.google.gapid.perfetto.canvas.Size; import com.google.gapid.perfetto.models.CounterTrack; +import com.google.gapid.perfetto.models.Selection.CombiningBuilder; -public class CounterPanel extends TrackPanel { +public class CounterPanel extends TrackPanel implements Selectable { private static final double HEIGHT = 30; private static final double HOVER_MARGIN = 10; private static final double HOVER_PADDING = 4; @@ -123,6 +126,12 @@ public void stop() { }; } + @Override + public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { + builder.add(Kind.Counter, transform( + track.getValues(state.getQueryEngine(), ts), data -> new CounterTrack.Values(name, data))); + } + private static class HoverCard { public final double value; public final String label; diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CountersSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/CountersSelectionView.java new file mode 100644 index 0000000000..a7e65cad58 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/CountersSelectionView.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * 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.gapid.perfetto.views; + +import static com.google.gapid.perfetto.TimeSpan.timeToString; +import static com.google.gapid.widgets.Widgets.createTableColumn; +import static com.google.gapid.widgets.Widgets.createTableViewer; +import static com.google.gapid.widgets.Widgets.packColumns; + +import com.google.gapid.perfetto.models.CounterTrack; + +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; + +public class CountersSelectionView extends Composite { + public CountersSelectionView(Composite parent, State state, CounterTrack.Values sel) { + super(parent, SWT.None); + setLayout(new FillLayout()); + + TableViewer viewer = createTableViewer(this, SWT.NONE); + viewer.setContentProvider(new ArrayContentProvider()); + viewer.setLabelProvider(new LabelProvider()); + + createTableColumn( + viewer, "Time", r -> timeToString(sel.ts[(Integer)r] - state.getTraceTime().start)); + for (int i = 0; i < sel.names.length; i++) { + final int idx = i; + createTableColumn(viewer, sel.names[i], r -> String.valueOf(sel.values[idx][(Integer)r])); + } + + Integer[] rows = new Integer[sel.ts.length]; + for (int i = 0; i < rows.length; i++) { + rows[i] = i; + } + viewer.setInput(rows); + packColumns(viewer.getTable()); + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/Selectable.java b/gapic/src/main/com/google/gapid/perfetto/views/Selectable.java index 550fbcc9d5..e3e8e629d4 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/Selectable.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/Selectable.java @@ -27,6 +27,6 @@ public interface Selectable { public static enum Kind { // Order as shown in the UI. - Thread, ThreadState, Cpu; + Thread, ThreadState, Cpu, Counter; } }