Merge "Perfetto metrics for Multiuser"
diff --git a/CHANGELOG b/CHANGELOG
index e8e0dd2..128093a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,8 @@
     * Removed DCHECK that would cause crashes when a debug build of the service
       is used with a producer built with -DNDEBUG.
   Trace Processor:
+    * Added reqiurement of separating queries by semi-colon (;) followed by
+      new-line when specifying a query file with -q to trace processor shell.
     * Added "ancestor_slice_by_stack" and "descendant_slice_by_stack" table
       functions to walk up and down the slice stacks.
   UI:
diff --git a/src/trace_processor/metrics/android/android_sysui_cuj.sql b/src/trace_processor/metrics/android/android_sysui_cuj.sql
index 4d5bcd8..6b5c58d 100644
--- a/src/trace_processor/metrics/android/android_sysui_cuj.sql
+++ b/src/trace_processor/metrics/android/android_sysui_cuj.sql
@@ -34,7 +34,7 @@
     AND slice.dur > 0
     AND (
       process.name LIKE 'com.google.android%'
-      OR process.name = 'com.android.systemui')
+      OR process.name LIKE 'com.android.%')
   ORDER BY ts desc
   LIMIT 1;
 
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 48d1d71..8caa693 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -542,10 +542,6 @@
   return it->Status();
 }
 
-bool IsBlankLine(const std::string& buffer) {
-  return buffer == "\n" || buffer == "\r\n";
-}
-
 bool IsCommentLine(const std::string& buffer) {
   return base::StartsWith(buffer, "--");
 }
@@ -561,8 +557,6 @@
     std::string sql_query;
     while (fgets(buffer, sizeof(buffer), input)) {
       std::string line = base::TrimLeading(buffer);
-      if (IsBlankLine(line))
-        break;
 
       if (IsCommentLine(line))
         continue;
diff --git a/test/trace_processor/parsing/android_log_msgs.sql b/test/trace_processor/parsing/android_log_msgs.sql
index dfbd6a3..02093ca 100644
--- a/test/trace_processor/parsing/android_log_msgs.sql
+++ b/test/trace_processor/parsing/android_log_msgs.sql
@@ -13,7 +13,7 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-create view v1 as select tag, count(*) from android_logs group by tag order by 2 desc limit 5
+create view v1 as select tag, count(*) from android_logs group by tag order by 2 desc limit 5;
 
 create view v2 as select tag, count(*) from android_logs group by tag order by 2 asc limit 5;
 
@@ -27,4 +27,4 @@
 select '-----', 0 union all
 select * from v3 union all
 select '-----', 0 union all
-select * from v4;
\ No newline at end of file
+select * from v4;
diff --git a/test/trace_processor/parsing/b120487929.sql b/test/trace_processor/parsing/b120487929.sql
index 179d324..a266d7a 100644
--- a/test/trace_processor/parsing/b120487929.sql
+++ b/test/trace_processor/parsing/b120487929.sql
@@ -38,19 +38,19 @@
   where name = 'cpuidle';
 
 create virtual table freq_idle
-  using span_join(freq_view PARTITIONED cpu, idle_view PARTITIONED cpu)
+  using span_join(freq_view PARTITIONED cpu, idle_view PARTITIONED cpu);
 
 create virtual table window_freq_idle using window;
 
 create virtual table span_freq_idle
-  using span_join(freq_idle PARTITIONED cpu, window_freq_idle)
+  using span_join(freq_idle PARTITIONED cpu, window_freq_idle);
 
 update window_freq_idle
   set
     window_start=(select min(ts) from sched),
     window_dur=(select max(ts) - min(ts) from sched),
     quantum=1000000
-  where rowid = 0
+  where rowid = 0;
 
 create view counter_view
   as select
@@ -66,6 +66,6 @@
       when 4294967295 then freq_value
       else idle_value
     end as value
-  from span_freq_idle
+  from span_freq_idle;
 
-select cpu, name, value, sum(dur) from counter_view group by cpu, name, value
+select cpu, name, value, sum(dur) from counter_view group by cpu, name, value;
diff --git a/test/trace_processor/tables/nulls.sql b/test/trace_processor/tables/nulls.sql
index 44e5132..cccbc47 100644
--- a/test/trace_processor/tables/nulls.sql
+++ b/test/trace_processor/tables/nulls.sql
@@ -37,6 +37,6 @@
 (2,     NULL,     NULL, NULL, "test", NULL),
 (1,     "other",  NULL, NULL, NULL,   NULL),
 (4,     NULL,     NULL, NULL, NULL,   1.0),
-(NULL,  "test",   1.0,  1,    NULL,   NULL)
+(NULL,  "test",   1.0,  1,    NULL,   NULL);
 
 SELECT * from null_test;
diff --git a/ui/release/channels.json b/ui/release/channels.json
index f267c6a..c13f7da 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -2,11 +2,11 @@
   "channels": [
     {
       "name": "stable",
-      "rev": "0f82b928d7a9f24bafabe50daa1f50b11fd3848d"
+      "rev": "448de39fa777ebfb143fb407b3f78ba6f422ae04"
     },
     {
       "name": "canary",
-      "rev": "3cedc5a20af20fceef7439e7f94fc33fe2da0d05"
+      "rev": "e8ca899d9d2bfba9b68f8e05908372c1379c410b"
     },
     {
       "name": "autopush",
diff --git a/ui/src/assets/record.scss b/ui/src/assets/record.scss
index 73198ba..e360857 100644
--- a/ui/src/assets/record.scss
+++ b/ui/src/assets/record.scss
@@ -787,18 +787,6 @@
     outline: none;
     -webkit-appearance: none;
 
-    &:not(.multicolumn) {
-      overflow: hidden;
-      height: 25px;
-      padding: 0 5px;
-      &:focus, &:hover {
-        height: 30vh;
-        position: absolute;
-        overflow: auto;
-        box-shadow: 0 0 15px 0 #eee;
-      }
-    }
-
     option, optgroup {
       @include transition();
       min-height: 25px;
@@ -820,6 +808,23 @@
       }
     }
 
+    &.singlecolumn {
+      margin: var(--record-section-padding) 0;
+      padding: 0;
+      max-width: 50%;
+      width: 50%;
+      overflow-y: auto;
+      height: 400px;
+      optgroup {
+        display: grid;
+        padding: 0;
+        grid-template-columns: 1fr;
+      }
+      option {
+        margin: 0;
+      }
+    }
+
     &.multicolumn {
       padding: 0;
       max-width: 100%;
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index 8602089..065df54 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -25,8 +25,9 @@
 import {NUM, NUM_NULL, STR} from './query_result';
 import {
   createQueryResult,
+  QueryError,
   QueryResult,
-  WritableQueryResult
+  WritableQueryResult,
 } from './query_result';
 import {TimeSpan} from './time';
 
@@ -44,7 +45,6 @@
   endLoading(): void {}
 }
 
-export class QueryError extends Error {}
 
 // This is used to skip the decoding of queryResult from protobufjs and deal
 // with it ourselves. See the comment below around `QueryResult.decode = ...`.
diff --git a/ui/src/common/query_result.ts b/ui/src/common/query_result.ts
index e882624..70186cf 100644
--- a/ui/src/common/query_result.ts
+++ b/ui/src/common/query_result.ts
@@ -60,6 +60,8 @@
 
 export type ColumnType = string|number|null;
 
+export class QueryError extends Error {}
+
 // One row extracted from an SQL result:
 export interface Row {
   [key: string]: ColumnType;
@@ -335,7 +337,7 @@
     if (this._error === undefined) {
       promise.resolve(arg);
     } else {
-      promise.reject(new Error(this._error));
+      promise.reject(new QueryError(this._error));
     }
   }
 }
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index b4a0f83..fec17e5 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -59,9 +59,10 @@
 export const MAX_TIME = 180;
 
 // 3: TrackKindPriority and related sorting changes.
-// 5: Move a large number of items off frontendLocalState and onto state
+// 5: Move a large number of items off frontendLocalState and onto state.
 // 6: Common PivotTableConfig and pivot table specific PivotTableState.
-export const STATE_VERSION = 6;
+// 7: Split Chrome categories in two and add 'symbolize ksyms' flag.
+export const STATE_VERSION = 7;
 
 export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
@@ -418,8 +419,7 @@
 
 export function isAdbTarget(target: RecordingTarget):
     target is AdbRecordingTarget {
-  if ((target as AdbRecordingTarget).serial) return true;
-  return false;
+  return !!(target as AdbRecordingTarget).serial;
 }
 
 export function hasActiveProbes(config: RecordConfig) {
@@ -496,6 +496,7 @@
   procStatsPeriodMs: number;
 
   chromeCategoriesSelected: string[];
+  chromeHighOverheadCategoriesSelected: string[];
 
   chromeLogs: boolean;
   taskScheduling: boolean;
@@ -573,6 +574,7 @@
     procStatsPeriodMs: 1000,
 
     chromeCategoriesSelected: [],
+    chromeHighOverheadCategoriesSelected: [],
 
     chromeLogs: false,
     taskScheduling: false,
diff --git a/ui/src/controller/metrics_controller.ts b/ui/src/controller/metrics_controller.ts
index 21ca500..92afb8a 100644
--- a/ui/src/controller/metrics_controller.ts
+++ b/ui/src/controller/metrics_controller.ts
@@ -13,8 +13,8 @@
 // limitations under the License.
 
 import {Actions} from '../common/actions';
-import {Engine, QueryError} from '../common/engine';
-import {STR} from '../common/query_result';
+import {Engine} from '../common/engine';
+import {QueryError, STR} from '../common/query_result';
 import {publishMetricResult} from '../frontend/publish';
 
 import {Controller} from './controller';
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index db03bb5..1b2c363 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -115,6 +115,8 @@
   const atraceApps = new Set<string>();
   const chromeCategories = new Set<string>();
   uiCfg.chromeCategoriesSelected.forEach(it => chromeCategories.add(it));
+  uiCfg.chromeHighOverheadCategoriesSelected.forEach(
+      it => chromeCategories.add(it));
 
   let procThreadAssociationPolling = false;
   let procThreadAssociationFtrace = false;
diff --git a/ui/src/controller/record_controller_jsdomtest.ts b/ui/src/controller/record_controller_jsdomtest.ts
index 516c04c..916054b 100644
--- a/ui/src/controller/record_controller_jsdomtest.ts
+++ b/ui/src/controller/record_controller_jsdomtest.ts
@@ -157,7 +157,8 @@
 
 test('ChromeMemoryConfig', () => {
   const config = createEmptyRecordConfig();
-  config.chromeCategoriesSelected = ['disabled-by-default-memory-infra'];
+  config.chromeHighOverheadCategoriesSelected =
+      ['disabled-by-default-memory-infra'];
   const result =
       TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'}));
   const sources = assertExists(result.dataSources);
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 5a07355..fefb2ae 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -19,10 +19,10 @@
 } from '../common/actions';
 import {cacheTrace} from '../common/cache_manager';
 import {TRACE_MARGIN_TIME_S} from '../common/constants';
-import {Engine, QueryError} from '../common/engine';
+import {Engine} from '../common/engine';
 import {featureFlags, Flag} from '../common/feature_flags';
 import {HttpRpcEngine} from '../common/http_rpc_engine';
-import {NUM, NUM_NULL, STR, STR_NULL} from '../common/query_result';
+import {NUM, NUM_NULL, QueryError, STR, STR_NULL} from '../common/query_result';
 import {EngineMode} from '../common/state';
 import {TimeSpan, toNs, toNsCeil, toNsFloor} from '../common/time';
 import {resetEngineWorker, WasmEngineProxy} from '../common/wasm_engine_proxy';
@@ -507,6 +507,7 @@
            inner join thread_track on slice.track_id = thread_track.id
            group by bucket, utid
          ) using(utid)
+         where upid is not null
          group by bucket, upid`);
 
     const slicesData: {[key: string]: QuantizedLoad[]} = {};
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index 548db2a..7b957d9 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -721,35 +721,40 @@
     categories = getBuiltinChromeCategoryList();
   }
 
-  // Show "disabled-by-default" categories last.
-  const categoriesMap = new Map<string, string>();
+  const defaultCategories = new Map<string, string>();
+  const disabledByDefaultCategories = new Map<string, string>();
   const disabledPrefix = 'disabled-by-default-';
-  const overheadSuffix = '(high overhead)';
   categories.forEach(cat => {
     if (cat.startsWith(disabledPrefix)) {
-      categoriesMap.set(
-          cat, `${cat.replace(disabledPrefix, '')} ${overheadSuffix}`);
+      disabledByDefaultCategories.set(cat, cat.replace(disabledPrefix, ''));
     } else {
-      categoriesMap.set(cat, cat);
+      defaultCategories.set(cat, cat);
     }
   });
 
-  return m(Dropdown, {
-    title: 'Additional Chrome categories',
-    cssClass: '.multicolumn.two-columns',
-    options: categoriesMap,
-    set: (cfg, val) => cfg.chromeCategoriesSelected = val,
-    get: (cfg) => cfg.chromeCategoriesSelected,
-    sort: (a, b) => {
-      const aIsDisabled = a.includes(overheadSuffix);
-      const bIsDisabled = b.includes(overheadSuffix);
-      if (aIsDisabled === bIsDisabled) {
-        return a.localeCompare(b);
-      } else {
-        return Number(aIsDisabled) - Number(bIsDisabled);
-      }
-    },
-  } as DropdownAttrs);
+  return m(
+      'div',
+      m(Dropdown, {
+        cssClass: '.singlecolumn',
+        title: 'Additional Chrome categories',
+        options: defaultCategories,
+        set: (cfg, val) => cfg.chromeCategoriesSelected = val,
+        get: (cfg) => cfg.chromeCategoriesSelected,
+        sort: (a, b) => {
+          return a.localeCompare(b);
+        },
+      } as DropdownAttrs),
+      m(Dropdown, {
+        cssClass: '.singlecolumn',
+        title: 'Additional high overhead Chrome categories',
+        options: disabledByDefaultCategories,
+        set: (cfg, val) => cfg.chromeHighOverheadCategoriesSelected = val,
+        get: (cfg) => cfg.chromeHighOverheadCategoriesSelected,
+        sort: (a, b) => {
+          return a.localeCompare(b);
+        },
+      } as DropdownAttrs),
+  );
 }
 
 function AdvancedSettings(cssClass: string) {
diff --git a/ui/src/tracks/cpu_profile/frontend.ts b/ui/src/tracks/cpu_profile/frontend.ts
index d156021..50b0361 100644
--- a/ui/src/tracks/cpu_profile/frontend.ts
+++ b/ui/src/tracks/cpu_profile/frontend.ts
@@ -25,6 +25,7 @@
 
 import {Config, CPU_PROFILE_TRACK_KIND, Data} from './common';
 
+const BAR_HEIGHT = 3;
 const MARGIN_TOP = 4.5;
 const RECT_HEIGHT = 30.5;
 
@@ -38,8 +39,8 @@
     return new CpuProfileTrack(args);
   }
 
-  private centerY = this.getHeight() / 2;
-  private markerWidth = (this.getHeight() - MARGIN_TOP) / 2;
+  private centerY = this.getHeight() / 2 + BAR_HEIGHT;
+  private markerWidth = (this.getHeight() - MARGIN_TOP - BAR_HEIGHT) / 2;
   private hoveredTs: number|undefined = undefined;
 
   constructor(args: NewTrackArgs) {
@@ -73,6 +74,26 @@
           strokeWidth,
           data.callsiteId[i]);
     }
+
+    let startX = data.tsStarts.length ? data.tsStarts[0] : -1;
+    let endX = data.tsStarts.length ? data.tsStarts[0] : -1;
+    let lastCallsiteId = data.callsiteId.length ? data.callsiteId[0] : -1;
+    for (let i = 0; i < data.tsStarts.length; i++) {
+      const centerX = data.tsStarts[i];
+      const callsiteId = data.callsiteId[i];
+      if (lastCallsiteId !== callsiteId) {
+        if (startX !== endX) {
+          const leftPx = timeScale.timeToPx(fromNs(startX)) - this.markerWidth;
+          const rightPx = timeScale.timeToPx(fromNs(endX)) + this.markerWidth;
+          const width = rightPx - leftPx;
+          ctx.fillStyle = colorForSample(lastCallsiteId, false);
+          ctx.fillRect(leftPx, MARGIN_TOP, width, BAR_HEIGHT);
+        }
+        startX = centerX;
+      }
+      endX = centerX;
+      lastCallsiteId = callsiteId;
+    }
   }
 
   drawMarker(