perfetto-ui: Add go to dest button for binder slices

Change-Id: Ibb8069feb6055caa552a44ee887003277d5b6662
diff --git a/src/trace_processor/importers/common/slice_tracker.cc b/src/trace_processor/importers/common/slice_tracker.cc
index 8c82982..3fca417 100644
--- a/src/trace_processor/importers/common/slice_tracker.cc
+++ b/src/trace_processor/importers/common/slice_tracker.cc
@@ -124,25 +124,26 @@
   return context_->storage->slice_table().id().IndexOf(*slice_id);
 }
 
-void SliceTracker::AddArgs(TrackId track_id,
-                           StringId category,
-                           StringId name,
-                           SetArgsCallback args_callback) {
+base::Optional<uint32_t> SliceTracker::AddArgs(TrackId track_id,
+                                               StringId category,
+                                               StringId name,
+                                               SetArgsCallback args_callback) {
   auto& stack = stacks_[track_id];
   if (stack.empty())
-    return;
+    return base::nullopt;
 
   auto* slices = context_->storage->mutable_slice_table();
   base::Optional<uint32_t> stack_idx =
       MatchingIncompleteSliceIndex(stack, name, category);
   if (!stack_idx.has_value())
-    return;
+    return base::nullopt;
   uint32_t slice_idx = stack[*stack_idx].first;
   PERFETTO_DCHECK(slices->dur()[slice_idx] == kPendingDuration);
   // Add args to current pending slice.
   ArgsTracker* tracker = &stack[*stack_idx].second;
   auto bound_inserter = tracker->AddArgsTo(slices->id()[slice_idx]);
   args_callback(&bound_inserter);
+  return slice_idx;
 }
 
 base::Optional<SliceId> SliceTracker::EndGpu(int64_t ts,
diff --git a/src/trace_processor/importers/common/slice_tracker.h b/src/trace_processor/importers/common/slice_tracker.h
index 804806c..eac656e 100644
--- a/src/trace_processor/importers/common/slice_tracker.h
+++ b/src/trace_processor/importers/common/slice_tracker.h
@@ -75,10 +75,10 @@
   // Usually args should be added in the Begin or End args_callback but this
   // method is for the situation where new args need to be added to an
   // in-progress slice.
-  void AddArgs(TrackId track_id,
-               StringId category,
-               StringId name,
-               SetArgsCallback args_callback);
+  base::Optional<uint32_t> AddArgs(TrackId track_id,
+                                   StringId category,
+                                   StringId name,
+                                   SetArgsCallback args_callback);
 
   // TODO(lalitm): eventually this method should become End and End should
   // be renamed EndChrome.
diff --git a/src/trace_processor/importers/ftrace/binder_tracker.cc b/src/trace_processor/importers/ftrace/binder_tracker.cc
index e9a081d..b6ef00e 100644
--- a/src/trace_processor/importers/ftrace/binder_tracker.cc
+++ b/src/trace_processor/importers/ftrace/binder_tracker.cc
@@ -75,6 +75,7 @@
       flags_(context->storage->InternString("flags")),
       code_(context->storage->InternString("code")),
       calling_tid_(context->storage->InternString("calling tid")),
+      dest_slice_id_(context->storage->InternString("destination slice id")),
       data_size_(context->storage->InternString("data size")),
       offsets_size_(context->storage->InternString("offsets size")) {}
 
@@ -161,17 +162,32 @@
   }
 
   if (transaction_await_rcv.count(transaction_id) > 0) {
+    // First begin the reply slice to get its slice id.
+    auto reply_slice_id = context_->slice_tracker->Begin(
+        ts, track_id, binder_category_id_, reply_id_);
     // Add accurate dest info to the binder transaction slice.
-    auto args_inserter = [this, pid,
-                          &thread_name](ArgsTracker::BoundInserter* inserter) {
+    auto args_inserter = [this, pid, &thread_name, &reply_slice_id](
+                             ArgsTracker::BoundInserter* inserter) {
       inserter->AddArg(dest_thread_, Variadic::UnsignedInteger(pid));
       inserter->AddArg(dest_name_, Variadic::String(thread_name));
+      if (reply_slice_id.has_value())
+        inserter->AddArg(dest_slice_id_,
+                         Variadic::UnsignedInteger(reply_slice_id.value()));
     };
-    context_->slice_tracker->AddArgs(transaction_await_rcv[transaction_id],
-                                     binder_category_id_, transaction_slice_id_,
-                                     args_inserter);
-    context_->slice_tracker->Begin(ts, track_id, binder_category_id_,
-                                   reply_id_);
+    // Add the dest args to the current transaction slice and get the slice id.
+    auto transaction_slice_id = context_->slice_tracker->AddArgs(
+        transaction_await_rcv[transaction_id], binder_category_id_,
+        transaction_slice_id_, args_inserter);
+
+    // Add the dest slice id to the reply slice that has just begun.
+    auto reply_dest_inserter =
+        [this, &transaction_slice_id](ArgsTracker::BoundInserter* inserter) {
+          if (transaction_slice_id.has_value())
+            inserter->AddArg(dest_slice_id_, Variadic::UnsignedInteger(
+                                                 transaction_slice_id.value()));
+        };
+    context_->slice_tracker->AddArgs(track_id, binder_category_id_, reply_id_,
+                                     reply_dest_inserter);
     transaction_await_rcv.erase(transaction_id);
     return;
   }
diff --git a/src/trace_processor/importers/ftrace/binder_tracker.h b/src/trace_processor/importers/ftrace/binder_tracker.h
index 3c2b982..a96816d 100644
--- a/src/trace_processor/importers/ftrace/binder_tracker.h
+++ b/src/trace_processor/importers/ftrace/binder_tracker.h
@@ -93,6 +93,7 @@
   const StringId flags_;
   const StringId code_;
   const StringId calling_tid_;
+  const StringId dest_slice_id_;
   const StringId data_size_;
   const StringId offsets_size_;
 };
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index 7dd5ff7..5efcce5 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -93,6 +93,24 @@
   font-family: 'Roboto Condensed', sans-serif;
   font-weight: 300;
   color: #3c4b5d;
+
+  .material-icons {
+    @include transition(0.3s);
+    font-size: 16px;
+    margin-left: 5px;
+    &:hover {
+      cursor: pointer;
+    }
+    &.grey {
+      border-radius: 3px;
+      border: 1px solid transparent;
+      background-color: #e8e8e8;
+      &:hover {
+        border: #475566 solid 1px;
+      }
+    }
+  }
+
   .details-panel-heading {
     padding: 10px 0 5px 0;
     position: sticky;
@@ -190,22 +208,6 @@
       width: 30%;
       font-weight: normal;
     }
-    .material-icons {
-      @include transition(0.3s);
-      font-size: 16px;
-      margin-left: 5px; 
-      &:hover {
-        cursor: pointer;
-      }
-      &.grey {
-        border-radius: 3px;
-        border: 1px solid transparent;
-        background-color: #e8e8e8;
-        &:hover {
-          border: #475566 solid 1px;
-        }
-      }
-    }
   }
 
   button {
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 34dbfd1..82ab2ce 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -14,7 +14,9 @@
 
 import {Engine} from '../common/engine';
 import {fromNs, toNs} from '../common/time';
-import {CounterDetails, SliceDetails} from '../frontend/globals';
+import {Arg, Args, CounterDetails, SliceDetails} from '../frontend/globals';
+import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
+
 import {Controller} from './controller';
 import {globals} from './globals';
 
@@ -140,8 +142,8 @@
     return map;
   }
 
-  async getArgs(argId: number): Promise<Map<string, string>> {
-    const args = new Map<string, string>();
+  async getArgs(argId: number): Promise<Args> {
+    const args = new Map<string, Arg>();
     const query = `
       select
         flat_key AS name,
@@ -153,11 +155,36 @@
     for (let i = 0; i < result.numRecords; i++) {
       const name = result.columns[0].stringValues![i];
       const value = result.columns[1].stringValues![i];
-      args.set(name, value);
+      if (name === 'destination slice id' && !isNaN(Number(value))) {
+        const destTrackId = await this.getDestTrackId(value);
+        args.set(
+            'Destination Slice',
+            {kind: 'SLICE', trackId: destTrackId, sliceId: Number(value)});
+      } else {
+        args.set(name, value);
+      }
     }
     return args;
   }
 
+  async getDestTrackId(sliceId: string): Promise<string> {
+    const trackIdQuery = `select track_id from slice
+    where slice_id = ${sliceId}`;
+    const destResult = await this.args.engine.query(trackIdQuery);
+    const trackIdTp = destResult.columns[0].longValues![0];
+    // TODO(taylori): If we had a consistent mapping from TP track_id
+    // UI track id for slice tracks this would be unnecessary.
+    let trackId = '';
+    for (const track of Object.values(globals.state.tracks)) {
+      if (track.kind === SLICE_TRACK_KIND &&
+          (track.config as {trackId: number}).trackId === Number(trackIdTp)) {
+        trackId = track.id;
+        break;
+      }
+    }
+    return trackId;
+  }
+
   async sliceDetails(id: number) {
     const sqlQuery = `SELECT ts, dur, priority, end_state, utid, cpu FROM sched
     WHERE id = ${id}`;
diff --git a/ui/src/frontend/chrome_slice_panel.ts b/ui/src/frontend/chrome_slice_panel.ts
index a0a7f35..f211cb7 100644
--- a/ui/src/frontend/chrome_slice_panel.ts
+++ b/ui/src/frontend/chrome_slice_panel.ts
@@ -14,10 +14,12 @@
 
 import * as m from 'mithril';
 
+import {Actions} from '../common/actions';
 import {timeToCode, toNs} from '../common/time';
 
-import {globals} from './globals';
+import {Args, globals} from './globals';
 import {Panel, PanelSize} from './panel';
+import {verticalScrollToTrack} from './scroll_helper';
 
 export class ChromeSliceDetailsPanel extends Panel {
   view() {
@@ -64,11 +66,35 @@
 
   renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {}
 
-  getArgs(args?: Map<string, string>): m.Vnode[] {
+  getArgs(args?: Args): m.Vnode[] {
     if (!args || args.size === 0) return [];
     const result = [];
     for (const [key, value] of args) {
-      result.push(m('tr', m('th', key), m('td', value)));
+      if (typeof value === 'string') {
+        result.push(m('tr', m('th', key), m('td', value)));
+      } else {
+        result.unshift(
+            m('tr',
+              m('th', key),
+              m('td',
+                m('i.material-icons.grey',
+                  {
+                    onclick: () => {
+                      globals.makeSelection(Actions.selectChromeSlice({
+                        id: value.sliceId,
+                        trackId: value.trackId,
+                        table: 'slice'
+                      }));
+                      // Ideally we want to have a callback to
+                      // findCurrentSelection after this selection has been
+                      // made. Here we do not have the info for horizontally
+                      // scrolling to ts.
+                      verticalScrollToTrack(value.trackId, true);
+                    },
+                    title: 'Go to destination slice'
+                  },
+                  'call_made'))));
+      }
     }
     return result;
   }
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 88650d5..5b859ad 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -27,7 +27,8 @@
 type QueryResultsStore = Map<string, {}>;
 type AggregateDataStore = Map<string, AggregateData>;
 type Description = Map<string, string>;
-type Args = Map<string, string>;
+export type Arg = string|{kind: 'SLICE', trackId: string, sliceId: number};
+export type Args = Map<string, Arg>;
 export interface SliceDetails {
   ts?: number;
   dur?: number;