Merge "Reland: Add proto for recording VkDebugMarkerSetObjectNameEXT"
diff --git a/Android.bp b/Android.bp
index f3c60ad..ef8d66b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4725,7 +4725,6 @@
     "src/trace_processor/read_trace.cc",
     "src/trace_processor/row_iterators.cc",
     "src/trace_processor/sched_slice_table.cc",
-    "src/trace_processor/slice_table.cc",
     "src/trace_processor/span_join_operator_table.cc",
     "src/trace_processor/sql_stats_table.cc",
     "src/trace_processor/stack_profile_frame_table.cc",
@@ -4791,6 +4790,8 @@
     "src/trace_processor/gzip_trace_parser.cc",
     "src/trace_processor/heap_profile_tracker.cc",
     "src/trace_processor/importers/ftrace/ftrace_descriptors.cc",
+    "src/trace_processor/importers/ftrace/ftrace_module.cc",
+    "src/trace_processor/importers/ftrace/ftrace_module_impl.cc",
     "src/trace_processor/importers/ftrace/ftrace_parser.cc",
     "src/trace_processor/importers/ftrace/ftrace_tokenizer.cc",
     "src/trace_processor/importers/ftrace/sched_event_tracker.cc",
@@ -4798,6 +4799,7 @@
     "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc",
     "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc",
     "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.cc",
+    "src/trace_processor/importers/proto/android_probes_module.cc",
     "src/trace_processor/importers/proto/android_probes_parser.cc",
     "src/trace_processor/importers/proto/args_table_utils.cc",
     "src/trace_processor/importers/proto/graphics_event_module.cc",
@@ -4808,7 +4810,9 @@
     "src/trace_processor/importers/proto/proto_importer_module.cc",
     "src/trace_processor/importers/proto/proto_trace_parser.cc",
     "src/trace_processor/importers/proto/proto_trace_tokenizer.cc",
+    "src/trace_processor/importers/proto/system_probes_module.cc",
     "src/trace_processor/importers/proto/system_probes_parser.cc",
+    "src/trace_processor/importers/proto/track_event_module.cc",
     "src/trace_processor/importers/proto/track_event_parser.cc",
     "src/trace_processor/importers/proto/track_event_tokenizer.cc",
     "src/trace_processor/importers/systrace/systrace_parser.cc",
diff --git a/BUILD b/BUILD
index 7f7e153..882900a 100644
--- a/BUILD
+++ b/BUILD
@@ -101,6 +101,7 @@
 perfetto_cc_library(
     name = "libprotozero",
     srcs = [
+        ":src_base_base",
         ":src_protozero_protozero",
     ],
     hdrs = [
@@ -743,8 +744,6 @@
         "src/trace_processor/row_iterators.h",
         "src/trace_processor/sched_slice_table.cc",
         "src/trace_processor/sched_slice_table.h",
-        "src/trace_processor/slice_table.cc",
-        "src/trace_processor/slice_table.h",
         "src/trace_processor/span_join_operator_table.cc",
         "src/trace_processor/span_join_operator_table.h",
         "src/trace_processor/sql_stats_table.cc",
@@ -794,7 +793,9 @@
         "src/trace_processor/heap_profile_tracker.h",
         "src/trace_processor/importers/ftrace/ftrace_descriptors.cc",
         "src/trace_processor/importers/ftrace/ftrace_descriptors.h",
+        "src/trace_processor/importers/ftrace/ftrace_module.cc",
         "src/trace_processor/importers/ftrace/ftrace_module.h",
+        "src/trace_processor/importers/ftrace/ftrace_module_impl.cc",
         "src/trace_processor/importers/ftrace/ftrace_parser.cc",
         "src/trace_processor/importers/ftrace/ftrace_parser.h",
         "src/trace_processor/importers/ftrace/ftrace_tokenizer.cc",
@@ -815,6 +816,7 @@
         "src/trace_processor/importers/json/json_trace_tokenizer.h",
         "src/trace_processor/importers/json/json_trace_utils.cc",
         "src/trace_processor/importers/json/json_trace_utils.h",
+        "src/trace_processor/importers/proto/android_probes_module.cc",
         "src/trace_processor/importers/proto/android_probes_module.h",
         "src/trace_processor/importers/proto/android_probes_parser.cc",
         "src/trace_processor/importers/proto/android_probes_parser.h",
@@ -839,9 +841,11 @@
         "src/trace_processor/importers/proto/proto_trace_parser.h",
         "src/trace_processor/importers/proto/proto_trace_tokenizer.cc",
         "src/trace_processor/importers/proto/proto_trace_tokenizer.h",
+        "src/trace_processor/importers/proto/system_probes_module.cc",
         "src/trace_processor/importers/proto/system_probes_module.h",
         "src/trace_processor/importers/proto/system_probes_parser.cc",
         "src/trace_processor/importers/proto/system_probes_parser.h",
+        "src/trace_processor/importers/proto/track_event_module.cc",
         "src/trace_processor/importers/proto/track_event_module.h",
         "src/trace_processor/importers/proto/track_event_parser.cc",
         "src/trace_processor/importers/proto/track_event_parser.h",
diff --git a/README.md b/README.md
index 27f653a..be4c683 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,13 @@
 
 See [www.perfetto.dev](https://www.perfetto.dev) for docs.
 
+Contributing
+------------
+See [/docs/contributing.md](docs/contributing.md) for instructions.
+
+The source-of-truth repo is [Android's Gerrit][aosp].
+The [GitHub repo](https://github.com/google/perfetto) is a read-only mirror.
+
 Bugs
 ----
 * For bugs affecting Android or the tracing internals use the internal
@@ -16,4 +23,6 @@
 ---------
 You can reach us on our [Discord channel](https://discord.gg/35ShE3A).
 If you prefer using IRC we have an experimental Discord <> IRC bridge
-synced with `#perfetto-dev` on [Freenode](https://webchat.freenode.net/).
\ No newline at end of file
+synced with `#perfetto-dev` on [Freenode](https://webchat.freenode.net/).
+
+[aosp]: https://android.googlesource.com/platform/external/perfetto/
diff --git a/docs/heapprofd.md b/docs/heapprofd.md
index fa2d2ff..1e46ce4 100644
--- a/docs/heapprofd.md
+++ b/docs/heapprofd.md
@@ -19,7 +19,7 @@
 On Linux / MacOS, use the `tools/heap_profile` script to heap profile a
 process. If you are having trouble make sure you are using the
 [latest version](
-https://raw.githubusercontent.com/catapult-project/perfetto/master/tools/heap_profile).
+https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile).
 
 See all the arguments using `tools/heap_profile -h`, or use the defaults
 and just profile a process (e.g. `system_server`):
diff --git a/include/perfetto/trace_processor/trace_processor.h b/include/perfetto/trace_processor/trace_processor.h
index d469937..adf770e 100644
--- a/include/perfetto/trace_processor/trace_processor.h
+++ b/include/perfetto/trace_processor/trace_processor.h
@@ -60,7 +60,7 @@
 
     // Returns the name of the column at index |col|. Can be called even before
     // calling |Next()|.
-    std::string GetColumName(uint32_t col);
+    std::string GetColumnName(uint32_t col);
 
     // Returns the number of columns in this iterator's query. Can be called
     // even before calling |Next()|.
diff --git a/infra/ci/frontend/static/script.js b/infra/ci/frontend/static/script.js
index d6fe73e..4a537c6 100644
--- a/infra/ci/frontend/static/script.js
+++ b/infra/ci/frontend/static/script.js
@@ -130,7 +130,7 @@
   const logUrl = 'https://goto.google.com/perfetto-ci-logs-';
   const docsUrl = 'https://docs.perfetto.dev/#/continuous-integration';
   return m('header',
-    m('h1', 'Perfetto ', m('span', 'CI')),
+    m('a[href=/#!/cls]', m('h1', 'Perfetto ', m('span', 'CI'))),
     m('nav',
       m(`div${active('cls')}`, m('a[href=/#!/cls]', 'CLs')),
       m(`div${active('jobs')}`, m('a[href=/#!/jobs]', 'Jobs')),
diff --git a/infra/git_mirror_bot/Makefile b/infra/git_mirror_bot/Makefile
index dedaad9..be1af35 100644
--- a/infra/git_mirror_bot/Makefile
+++ b/infra/git_mirror_bot/Makefile
@@ -12,8 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-PROJECT="perfetto-ci"
-VM_NAME="perfetto-ci-git-mirror-bot"
+PROJECT="perfetto-aosp-to-github-bot"
+VM_NAME="perfetto-aosp-to-github-bot"
 ZONE="us-central1-c"
 
 ssh:
@@ -26,23 +26,19 @@
 		instances delete $(VM_NAME) \
 		--zone $(ZONE)
 
-deploy_key:
-	@echo "Download the deploy_key from the teams drive (go/perfetto_deploy_key)"
-	@exit 1
-
-start: deploy_key
+start:
 	gcloud compute \
 		--project $(PROJECT) \
 		instances create $(VM_NAME) \
 		--zone $(ZONE) \
-		--machine-type "n1-highcpu-16" \
+		--machine-type "n1-standard-2" \
 		--subnet "default" \
 		--maintenance-policy "MIGRATE" \
-		--image "debian-9-stretch-v20170918" \
-		--image-project "debian-cloud" \
+		--image "debian-9-drawfork-v20191004" \
+		--image-project "eip-images" \
 		--boot-disk-size "10" \
 		--boot-disk-type "pd-standard" \
 		--boot-disk-device-name "perfetto-ci-git-mirror-bot" \
-		--metadata-from-file=startup-script=startup-script.sh,deploy_key=deploy_key,main=mirror_aosp_to_ghub_repo.py
+		--metadata-from-file=startup-script=startup-script.sh,main=mirror_aosp_to_ghub_repo.py
 
 .PHONY: ssh stop start
diff --git a/infra/git_mirror_bot/mirror_aosp_to_ghub_repo.py b/infra/git_mirror_bot/mirror_aosp_to_ghub_repo.py
index fa17382..e254014 100644
--- a/infra/git_mirror_bot/mirror_aosp_to_ghub_repo.py
+++ b/infra/git_mirror_bot/mirror_aosp_to_ghub_repo.py
@@ -34,14 +34,14 @@
 
 CUR_DIR = os.path.dirname(os.path.abspath(__file__))
 GIT_UPSTREAM = 'https://android.googlesource.com/platform/external/perfetto/'
-GIT_MIRROR = 'git@github.com:catapult-project/perfetto.git'
+GIT_MIRROR = 'git@github.com:google/perfetto.git'
 WORKDIR = os.path.join(CUR_DIR, 'repo')
 
 # Min delay (in seconds) between two consecutive git poll cycles. This is to
 # avoid hitting gerrit API quota limits.
 POLL_PERIOD_SEC = 60
 
-# The actual deploy_key is stored into the internal team drive, undef /infra/.
+# The actual key is stored into the Google Cloud project metadata.
 ENV = {'GIT_SSH_COMMAND': 'ssh -i ' + os.path.join(CUR_DIR, 'deploy_key')}
 
 
@@ -68,7 +68,11 @@
   os.makedirs(WORKDIR)
   GitCmd('init', '--bare', '--quiet')
   GitCmd('remote', 'add', 'upstream', GIT_UPSTREAM)
-  GitCmd('config', 'remote.upstream.fetch', '+refs/*:refs/remotes/upstream/*')
+  GitCmd('config', 'remote.upstream.tagOpt', '--no-tags')
+  GitCmd('config', '--add', 'remote.upstream.fetch',
+         '+refs/heads/*:refs/remotes/upstream/heads/*')
+  GitCmd('config', '--add', 'remote.upstream.fetch',
+         '+refs/tags/*:refs/remotes/upstream/tags/*')
   GitCmd('remote', 'add', 'mirror', GIT_MIRROR, '--mirror=fetch')
 
 
@@ -90,16 +94,17 @@
   for line in all_refs.splitlines():
     ref_sha1, ref = line.split()
 
-    PREFIX = 'refs/heads/'
-    if ref.startswith(PREFIX):
-      branch = ref[len(PREFIX):]
-      current_heads['refs/heads/' + branch] = ref_sha1
+    FILTER_REGEX = r'(heads/master|heads/releases/.*|tags/v\d+\.\d+)$'
+    m = re.match('refs/' + FILTER_REGEX, ref)
+    if m is not None:
+      branch = m.group(1)
+      current_heads['refs/' + branch] = ref_sha1
       continue
 
-    PREFIX = 'refs/remotes/upstream/heads/'
-    if ref.startswith(PREFIX):
-      branch = ref[len(PREFIX):]
-      future_heads['refs/heads/' + branch] = ref_sha1
+    m = re.match('refs/remotes/upstream/' + FILTER_REGEX, ref)
+    if m is not None:
+      branch = m.group(1)
+      future_heads['refs/' + branch] = ref_sha1
       continue
 
   deleted_heads = set(current_heads) - set(future_heads)
@@ -121,7 +126,7 @@
 
   if args.push:
     logging.info('Pushing updates')
-    GitCmd('push', 'mirror', '--all', '--prune', '--force')
+    GitCmd('push', 'mirror', '--all', '--prune', '--force', '--follow-tags')
     GitCmd('gc', '--prune=all', '--aggressive', '--quiet')
   else:
     logging.info('Dry-run mode, skipping git push. Pass --push for prod mode.')
diff --git a/infra/git_mirror_bot/startup-script.sh b/infra/git_mirror_bot/startup-script.sh
index cba2d93..2ae7143 100644
--- a/infra/git_mirror_bot/startup-script.sh
+++ b/infra/git_mirror_bot/startup-script.sh
@@ -39,7 +39,8 @@
 redirect_stderr=true
 EOF
 
-curl -H Metadata-Flavor:Google "http://metadata.google.internal/computeMetadata/v1/instance/attributes/deploy_key" > /home/gitbot/deploy_key
+curl -H Metadata-Flavor:Google "http://metadata.google.internal/computeMetadata/v1/project/attributes/deploy_key" > /home/gitbot/deploy_key
+echo >> /home/gitbot/deploy_key  # metadata server strips trailing \n.
 chown gitbot /home/gitbot/deploy_key
 chmod 400 /home/gitbot/deploy_key
 
@@ -50,8 +51,3 @@
 cd /home/gitbot
 sudo -u gitbot bash -c "mkdir -p .ssh; ssh-keyscan github.com >> .ssh/known_hosts;"
 /usr/bin/supervisord -c /etc/supervisord.conf
-
-dd if=/dev/zero of=/swap bs=1M count=4k
-chmod 600 /swap
-mkswap /swap
-swapon /swap
diff --git a/infra/perfetto-site.appspot.com/index.yaml b/infra/perfetto-site.appspot.com/index.yaml
new file mode 100644
index 0000000..eb9fd6f
--- /dev/null
+++ b/infra/perfetto-site.appspot.com/index.yaml
@@ -0,0 +1,16 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# 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.
+
+indexes:
+# AUTOGENERATED
diff --git a/infra/perfetto-site.appspot.com/main.py b/infra/perfetto-site.appspot.com/main.py
index b42dd96..4a3618a 100644
--- a/infra/perfetto-site.appspot.com/main.py
+++ b/infra/perfetto-site.appspot.com/main.py
@@ -19,7 +19,7 @@
 import webapp2
 
 MEMCACHE_TTL_SEC = 60 * 60 * 24
-BASE = 'https://catapult-project.github.io/perfetto/%s'
+BASE = 'https://google.github.io/perfetto/%s'
 HEADERS = {
     'last-modified', 'content-type', 'content-length', 'content-encoding',
     'etag'
diff --git a/src/protozero/BUILD.gn b/src/protozero/BUILD.gn
index 3161670..e4cbd82 100644
--- a/src/protozero/BUILD.gn
+++ b/src/protozero/BUILD.gn
@@ -20,12 +20,12 @@
 source_set("protozero") {
   public_configs = [ "../../gn:default_config" ]
   public_deps = [
+    "../../include/perfetto/base",
     "../../include/perfetto/protozero",
   ]
   deps = [
     "../../gn:default_deps",
-    "../../include/perfetto/base",
-    "../../include/perfetto/ext/base",  # TODO(primiano): remove this
+    "../base",
   ]
   sources = [
     "field.cc",
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index fe0a7ea..1051a81 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -77,6 +77,7 @@
     "heap_profile_tracker.cc",
     "heap_profile_tracker.h",
     "importers/ftrace/ftrace_descriptors.h",
+    "importers/ftrace/ftrace_module.cc",
     "importers/ftrace/ftrace_module.h",
     "importers/ftrace/ftrace_parser.h",
     "importers/ftrace/ftrace_tokenizer.h",
@@ -102,6 +103,7 @@
     "importers/proto/proto_trace_tokenizer.h",
     "importers/proto/system_probes_module.h",
     "importers/proto/system_probes_parser.h",
+    "importers/proto/track_event_module.cc",
     "importers/proto/track_event_module.h",
     "importers/proto/track_event_parser.cc",
     "importers/proto/track_event_parser.h",
@@ -177,6 +179,7 @@
   if (enable_perfetto_trace_processor_ftrace) {
     sources += [
       "importers/ftrace/ftrace_descriptors.cc",
+      "importers/ftrace/ftrace_module_impl.cc",
       "importers/ftrace/ftrace_parser.cc",
       "importers/ftrace/ftrace_tokenizer.cc",
       "importers/ftrace/sched_event_tracker.cc",
@@ -189,11 +192,17 @@
     sources += [ "ftrace_utils.cc" ]
   }
   if (enable_perfetto_trace_processor_system_probes) {
-    sources += [ "importers/proto/system_probes_parser.cc" ]
+    sources += [
+      "importers/proto/system_probes_module.cc",
+      "importers/proto/system_probes_parser.cc",
+    ]
     deps += [ "../../include/perfetto/ext/traced:sys_stats_counters" ]
   }
   if (enable_perfetto_trace_processor_android_probes) {
-    sources += [ "importers/proto/android_probes_parser.cc" ]
+    sources += [
+      "importers/proto/android_probes_module.cc",
+      "importers/proto/android_probes_parser.cc",
+    ]
   }
   if (enable_perfetto_trace_processor_heap_graphs) {
     sources += [
@@ -276,8 +285,6 @@
       "row_iterators.cc",
       "row_iterators.h",
       "sched_slice_table.h",
-      "slice_table.cc",
-      "slice_table.h",
       "span_join_operator_table.cc",
       "span_join_operator_table.h",
       "sql_stats_table.cc",
diff --git a/src/trace_processor/args_tracker.cc b/src/trace_processor/args_tracker.cc
index c3e1bca..a28336c 100644
--- a/src/trace_processor/args_tracker.cc
+++ b/src/trace_processor/args_tracker.cc
@@ -79,7 +79,7 @@
         storage->mutable_instants()->set_arg_set_id(row, set_id);
         break;
       case TableId::kNestableSlices:
-        storage->mutable_nestable_slices()->set_arg_set_id(row, set_id);
+        storage->mutable_slice_table()->mutable_arg_set_id()->Set(row, set_id);
         break;
       // Special case: overwrites the metadata table row.
       case TableId::kMetadataTable:
diff --git a/src/trace_processor/db/column.cc b/src/trace_processor/db/column.cc
index b957d3d..e06258c 100644
--- a/src/trace_processor/db/column.cc
+++ b/src/trace_processor/db/column.cc
@@ -21,6 +21,35 @@
 namespace perfetto {
 namespace trace_processor {
 
+namespace {
+
+// This code mathces the behaviour of sqlite3IntFloatCompare to ensure that
+// we are consistent with SQLite.
+int CompareIntToDouble(int64_t i, double d) {
+  // First check if we are out of range for a int64_t. We use the constants
+  // directly instead of using numeric_limits as the casts introduces rounding
+  // in the doubles as a double cannot exactly represent int64::max().
+  if (d >= 9223372036854775808.0)
+    return -1;
+  if (d < -9223372036854775808.0)
+    return 1;
+
+  // Then, try to compare in int64 space to try and keep as much precision as
+  // possible.
+  int64_t d_i = static_cast<int64_t>(d);
+  if (i < d_i)
+    return -1;
+  if (i > d_i)
+    return 1;
+
+  // Finally, try and compare in double space, sacrificing precision if
+  // necessary.
+  double i_d = static_cast<double>(i);
+  return (i_d < d) ? -1 : (i_d > d ? 1 : 0);
+}
+
+}  // namespace
+
 Column::Column(const Column& column,
                Table* table,
                uint32_t col_idx,
@@ -54,6 +83,386 @@
                 col_idx, row_map_idx, nullptr);
 }
 
+void Column::StableSort(bool desc, std::vector<uint32_t>* idx) const {
+  if (desc) {
+    StableSort<true /* desc */>(idx);
+  } else {
+    StableSort<false /* desc */>(idx);
+  }
+}
+
+void Column::FilterIntoSlow(FilterOp op, SqlValue value, RowMap* rm) const {
+  switch (type_) {
+    case ColumnType::kInt32: {
+      if (IsNullable()) {
+        FilterIntoNumericSlow<int32_t, true /* is_nullable */>(op, value, rm);
+      } else {
+        FilterIntoNumericSlow<int32_t, false /* is_nullable */>(op, value, rm);
+      }
+      break;
+    }
+    case ColumnType::kUint32: {
+      if (IsNullable()) {
+        FilterIntoNumericSlow<uint32_t, true /* is_nullable */>(op, value, rm);
+      } else {
+        FilterIntoNumericSlow<uint32_t, false /* is_nullable */>(op, value, rm);
+      }
+      break;
+    }
+    case ColumnType::kInt64: {
+      if (IsNullable()) {
+        FilterIntoNumericSlow<int64_t, true /* is_nullable */>(op, value, rm);
+      } else {
+        FilterIntoNumericSlow<int64_t, false /* is_nullable */>(op, value, rm);
+      }
+      break;
+    }
+    case ColumnType::kDouble: {
+      if (IsNullable()) {
+        FilterIntoNumericSlow<double, true /* is_nullable */>(op, value, rm);
+      } else {
+        FilterIntoNumericSlow<double, false /* is_nullable */>(op, value, rm);
+      }
+      break;
+    }
+    case ColumnType::kString: {
+      FilterIntoStringSlow(op, value, rm);
+      break;
+    }
+    case ColumnType::kId: {
+      FilterIntoIdSlow(op, value, rm);
+      break;
+    }
+  }
+}
+
+template <typename T, bool is_nullable>
+void Column::FilterIntoNumericSlow(FilterOp op,
+                                   SqlValue value,
+                                   RowMap* rm) const {
+  PERFETTO_DCHECK(IsNullable() == is_nullable);
+  PERFETTO_DCHECK(type_ == ToColumnType<T>());
+  PERFETTO_DCHECK(std::is_arithmetic<T>::value);
+
+  if (op == FilterOp::kIsNull) {
+    PERFETTO_DCHECK(value.is_null());
+    if (is_nullable) {
+      row_map().FilterInto(rm, [this](uint32_t row) {
+        return !sparse_vector<T>().Get(row).has_value();
+      });
+    } else {
+      rm->Intersect(RowMap());
+    }
+    return;
+  } else if (op == FilterOp::kIsNotNull) {
+    PERFETTO_DCHECK(value.is_null());
+    if (is_nullable) {
+      row_map().FilterInto(rm, [this](uint32_t row) {
+        return sparse_vector<T>().Get(row).has_value();
+      });
+    }
+    return;
+  }
+
+  if (value.type == SqlValue::Type::kDouble) {
+    double double_value = value.double_value;
+    if (std::is_same<T, double>::value) {
+      auto fn = [double_value](T v) {
+        return v < double_value ? -1 : (v > double_value ? 1 : 0);
+      };
+      FilterIntoNumericWithComparatorSlow<T, is_nullable>(op, rm, fn);
+    } else {
+      auto fn = [double_value](T v) {
+        // We static cast here as this code will be compiled even when T ==
+        // double as we don't have if constexpr in C++11. In reality the cast is
+        // a noop but we cannot statically verify that for the compiler.
+        return CompareIntToDouble(static_cast<int64_t>(v), double_value);
+      };
+      FilterIntoNumericWithComparatorSlow<T, is_nullable>(op, rm, fn);
+    }
+  } else if (value.type == SqlValue::Type::kLong) {
+    int64_t long_value = value.long_value;
+    if (std::is_same<T, double>::value) {
+      auto fn = [long_value](T v) {
+        // We negate the return value as the long is always the first parameter
+        // for this function even though the LHS of the comparator should
+        // actually be |v|. This saves us having a duplicate implementation of
+        // the comparision function.
+        return -CompareIntToDouble(long_value, v);
+      };
+      FilterIntoNumericWithComparatorSlow<T, is_nullable>(op, rm, fn);
+    } else {
+      auto fn = [long_value](T v) {
+        return v < long_value ? -1 : (v > long_value ? 1 : 0);
+      };
+      FilterIntoNumericWithComparatorSlow<T, is_nullable>(op, rm, fn);
+    }
+  } else {
+    rm->Intersect(RowMap());
+  }
+}
+
+template <typename T, bool is_nullable, typename Comparator>
+void Column::FilterIntoNumericWithComparatorSlow(FilterOp op,
+                                                 RowMap* rm,
+                                                 Comparator cmp) const {
+  switch (op) {
+    case FilterOp::kLt:
+      row_map().FilterInto(rm, [this, &cmp](uint32_t idx) {
+        if (is_nullable) {
+          auto opt_value = sparse_vector<T>().Get(idx);
+          return opt_value && cmp(*opt_value) < 0;
+        }
+        return cmp(sparse_vector<T>().GetNonNull(idx)) < 0;
+      });
+      break;
+    case FilterOp::kEq:
+      row_map().FilterInto(rm, [this, &cmp](uint32_t idx) {
+        if (is_nullable) {
+          auto opt_value = sparse_vector<T>().Get(idx);
+          return opt_value && cmp(*opt_value) == 0;
+        }
+        return cmp(sparse_vector<T>().GetNonNull(idx)) == 0;
+      });
+      break;
+    case FilterOp::kGt:
+      row_map().FilterInto(rm, [this, &cmp](uint32_t idx) {
+        if (is_nullable) {
+          auto opt_value = sparse_vector<T>().Get(idx);
+          return opt_value && cmp(*opt_value) > 0;
+        }
+        return cmp(sparse_vector<T>().GetNonNull(idx)) > 0;
+      });
+      break;
+    case FilterOp::kNe:
+      row_map().FilterInto(rm, [this, &cmp](uint32_t idx) {
+        if (is_nullable) {
+          auto opt_value = sparse_vector<T>().Get(idx);
+          return opt_value && cmp(*opt_value) != 0;
+        }
+        return cmp(sparse_vector<T>().GetNonNull(idx)) != 0;
+      });
+      break;
+    case FilterOp::kLe:
+      row_map().FilterInto(rm, [this, &cmp](uint32_t idx) {
+        if (is_nullable) {
+          auto opt_value = sparse_vector<T>().Get(idx);
+          return opt_value && cmp(*opt_value) <= 0;
+        }
+        return cmp(sparse_vector<T>().GetNonNull(idx)) <= 0;
+      });
+      break;
+    case FilterOp::kGe:
+      row_map().FilterInto(rm, [this, &cmp](uint32_t idx) {
+        if (is_nullable) {
+          auto opt_value = sparse_vector<T>().Get(idx);
+          return opt_value && cmp(*opt_value) >= 0;
+        }
+        return cmp(sparse_vector<T>().GetNonNull(idx)) >= 0;
+      });
+      break;
+    case FilterOp::kLike:
+      rm->Intersect(RowMap());
+      break;
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+      PERFETTO_FATAL("Should be handled above");
+  }
+}
+
+void Column::FilterIntoStringSlow(FilterOp op,
+                                  SqlValue value,
+                                  RowMap* rm) const {
+  PERFETTO_DCHECK(type_ == ColumnType::kString);
+
+  if (op == FilterOp::kIsNull) {
+    PERFETTO_DCHECK(value.is_null());
+    row_map().FilterInto(rm, [this](uint32_t row) {
+      return GetStringPoolStringAtIdx(row).data() == nullptr;
+    });
+    return;
+  } else if (op == FilterOp::kIsNotNull) {
+    PERFETTO_DCHECK(value.is_null());
+    row_map().FilterInto(rm, [this](uint32_t row) {
+      return GetStringPoolStringAtIdx(row).data() != nullptr;
+    });
+    return;
+  }
+
+  if (value.type != SqlValue::Type::kString) {
+    rm->Intersect(RowMap());
+    return;
+  }
+
+  NullTermStringView str_value = value.string_value;
+  switch (op) {
+    case FilterOp::kLt:
+      row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
+        auto v = GetStringPoolStringAtIdx(idx);
+        return v.data() != nullptr && v < str_value;
+      });
+      break;
+    case FilterOp::kEq:
+      row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
+        auto v = GetStringPoolStringAtIdx(idx);
+        return v.data() != nullptr && v == str_value;
+      });
+      break;
+    case FilterOp::kGt:
+      row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
+        auto v = GetStringPoolStringAtIdx(idx);
+        return v.data() != nullptr && v > str_value;
+      });
+      break;
+    case FilterOp::kNe:
+      row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
+        auto v = GetStringPoolStringAtIdx(idx);
+        return v.data() != nullptr && v != str_value;
+      });
+      break;
+    case FilterOp::kLe:
+      row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
+        auto v = GetStringPoolStringAtIdx(idx);
+        return v.data() != nullptr && v <= str_value;
+      });
+      break;
+    case FilterOp::kGe:
+      row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
+        auto v = GetStringPoolStringAtIdx(idx);
+        return v.data() != nullptr && v >= str_value;
+      });
+      break;
+    case FilterOp::kLike:
+      // TODO(lalitm): either call through to SQLite or reimplement
+      // like ourselves.
+      PERFETTO_DLOG("Ignoring like constraint on string column");
+      break;
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+      PERFETTO_FATAL("Should be handled above");
+  }
+}
+
+void Column::FilterIntoIdSlow(FilterOp op, SqlValue value, RowMap* rm) const {
+  PERFETTO_DCHECK(type_ == ColumnType::kId);
+
+  if (op == FilterOp::kIsNull) {
+    PERFETTO_DCHECK(value.is_null());
+    rm->Intersect(RowMap());
+    return;
+  } else if (op == FilterOp::kIsNotNull) {
+    PERFETTO_DCHECK(value.is_null());
+    return;
+  }
+
+  if (value.type != SqlValue::Type::kLong) {
+    rm->Intersect(RowMap());
+    return;
+  }
+
+  uint32_t id_value = static_cast<uint32_t>(value.long_value);
+  switch (op) {
+    case FilterOp::kLt:
+      row_map().FilterInto(rm,
+                           [id_value](uint32_t idx) { return idx < id_value; });
+      break;
+    case FilterOp::kEq:
+      row_map().FilterInto(
+          rm, [id_value](uint32_t idx) { return idx == id_value; });
+      break;
+    case FilterOp::kGt:
+      row_map().FilterInto(rm,
+                           [id_value](uint32_t idx) { return idx > id_value; });
+      break;
+    case FilterOp::kNe:
+      row_map().FilterInto(
+          rm, [id_value](uint32_t idx) { return idx != id_value; });
+      break;
+    case FilterOp::kLe:
+      row_map().FilterInto(
+          rm, [id_value](uint32_t idx) { return idx <= id_value; });
+      break;
+    case FilterOp::kGe:
+      row_map().FilterInto(
+          rm, [id_value](uint32_t idx) { return idx >= id_value; });
+      break;
+    case FilterOp::kLike:
+      rm->Intersect(RowMap());
+      break;
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+      PERFETTO_FATAL("Should be handled above");
+  }
+}
+
+template <bool desc>
+void Column::StableSort(std::vector<uint32_t>* out) const {
+  switch (type_) {
+    case ColumnType::kInt32: {
+      if (IsNullable()) {
+        StableSort<desc, int32_t, true /* is_nullable */>(out);
+      } else {
+        StableSort<desc, int32_t, false /* is_nullable */>(out);
+      }
+      break;
+    }
+    case ColumnType::kUint32: {
+      if (IsNullable()) {
+        StableSort<desc, uint32_t, true /* is_nullable */>(out);
+      } else {
+        StableSort<desc, uint32_t, false /* is_nullable */>(out);
+      }
+      break;
+    }
+    case ColumnType::kInt64: {
+      if (IsNullable()) {
+        StableSort<desc, int64_t, true /* is_nullable */>(out);
+      } else {
+        StableSort<desc, int64_t, false /* is_nullable */>(out);
+      }
+      break;
+    }
+    case ColumnType::kDouble: {
+      if (IsNullable()) {
+        StableSort<desc, double, true /* is_nullable */>(out);
+      } else {
+        StableSort<desc, double, false /* is_nullable */>(out);
+      }
+      break;
+    }
+    case ColumnType::kString: {
+      row_map().StableSort(out, [this](uint32_t a_idx, uint32_t b_idx) {
+        auto a_str = GetStringPoolStringAtIdx(a_idx);
+        auto b_str = GetStringPoolStringAtIdx(b_idx);
+        return desc ? b_str < a_str : a_str < b_str;
+      });
+      break;
+    }
+    case ColumnType::kId:
+      row_map().StableSort(out, [](uint32_t a_idx, uint32_t b_idx) {
+        return desc ? b_idx < a_idx : a_idx < b_idx;
+      });
+  }
+}
+
+template <bool desc, typename T, bool is_nullable>
+void Column::StableSort(std::vector<uint32_t>* out) const {
+  PERFETTO_DCHECK(IsNullable() == is_nullable);
+  PERFETTO_DCHECK(ToColumnType<T>() == type_);
+
+  const auto& sv = sparse_vector<T>();
+  row_map().StableSort(out, [&sv](uint32_t a_idx, uint32_t b_idx) {
+    if (is_nullable) {
+      auto a_val = sv.Get(a_idx);
+      auto b_val = sv.Get(b_idx);
+      return desc ? b_val < a_val : a_val < b_val;
+    }
+    auto a_val = sv.GetNonNull(a_idx);
+    auto b_val = sv.GetNonNull(b_idx);
+    return desc ? b_val < a_val : a_val < b_val;
+  });
+}
+
 const RowMap& Column::row_map() const {
   return table_->row_maps_[row_map_idx_];
 }
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 662413c..0117539 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -147,13 +147,7 @@
 
   // Sorts |idx| in ascending or descending order (determined by |desc|) based
   // on the contents of this column.
-  void StableSort(bool desc, std::vector<uint32_t>* idx) const {
-    if (desc) {
-      StableSort<true /* desc */>(idx);
-    } else {
-      StableSort<false /* desc */>(idx);
-    }
-  }
+  void StableSort(bool desc, std::vector<uint32_t>* idx) const;
 
   // Updates the given RowMap by only keeping rows where this column meets the
   // given filter constraint.
@@ -174,85 +168,12 @@
       // If the column is sorted and the value has the same type as the column,
       // we should be able to just do a binary search to find the range of rows
       // instead of a full table scan.
-      const Iterator b(this, 0);
-      const Iterator e(this, row_map().size());
-      switch (op) {
-        case FilterOp::kEq: {
-          uint32_t beg = std::distance(b, std::lower_bound(b, e, value));
-          uint32_t end = std::distance(b, std::upper_bound(b, e, value));
-          rm->Intersect(RowMap(beg, end));
-          return;
-        }
-        case FilterOp::kLe: {
-          uint32_t end = std::distance(b, std::upper_bound(b, e, value));
-          rm->Intersect(RowMap(0, end));
-          return;
-        }
-        case FilterOp::kLt: {
-          uint32_t end = std::distance(b, std::lower_bound(b, e, value));
-          rm->Intersect(RowMap(0, end));
-          return;
-        }
-        case FilterOp::kGe: {
-          uint32_t beg = std::distance(b, std::lower_bound(b, e, value));
-          rm->Intersect(RowMap(beg, row_map().size()));
-          return;
-        }
-        case FilterOp::kGt: {
-          uint32_t beg = std::distance(b, std::upper_bound(b, e, value));
-          rm->Intersect(RowMap(beg, row_map().size()));
-          return;
-        }
-        case FilterOp::kNe:
-        case FilterOp::kIsNull:
-        case FilterOp::kIsNotNull:
-        case FilterOp::kLike:
-          break;
-      }
+      bool handled = FilterIntoSorted(op, value, rm);
+      if (handled)
+        return;
     }
 
-    switch (type_) {
-      case ColumnType::kInt32: {
-        if (IsNullable()) {
-          FilterIntoLongSlow<int32_t, true /* is_nullable */>(op, value, rm);
-        } else {
-          FilterIntoLongSlow<int32_t, false /* is_nullable */>(op, value, rm);
-        }
-        break;
-      }
-      case ColumnType::kUint32: {
-        if (IsNullable()) {
-          FilterIntoLongSlow<uint32_t, true /* is_nullable */>(op, value, rm);
-        } else {
-          FilterIntoLongSlow<uint32_t, false /* is_nullable */>(op, value, rm);
-        }
-        break;
-      }
-      case ColumnType::kInt64: {
-        if (IsNullable()) {
-          FilterIntoLongSlow<int64_t, true /* is_nullable */>(op, value, rm);
-        } else {
-          FilterIntoLongSlow<int64_t, false /* is_nullable */>(op, value, rm);
-        }
-        break;
-      }
-      case ColumnType::kDouble: {
-        if (IsNullable()) {
-          FilterIntoDoubleSlow<true /* is_nullable */>(op, value, rm);
-        } else {
-          FilterIntoDoubleSlow<false /* is_nullable */>(op, value, rm);
-        }
-        break;
-      }
-      case ColumnType::kString: {
-        FilterIntoStringSlow(op, value, rm);
-        break;
-      }
-      case ColumnType::kId: {
-        FilterIntoIdSlow(op, value, rm);
-        break;
-      }
-    }
+    FilterIntoSlow(op, value, rm);
   }
 
   // Returns true if this column is considered an id column.
@@ -264,8 +185,15 @@
   // Returns true if this column is a sorted column.
   bool IsSorted() const { return (flags_ & Flag::kSorted) != 0; }
 
+  // Returns the backing RowMap for this Column.
+  // This function is defined out of line because of a circular dependency
+  // between |Table| and |Column|.
   const RowMap& row_map() const;
+
+  // Returns the name of the column.
   const char* name() const { return name_; }
+
+  // Returns the type of this Column in terms of SqlValue::Type.
   SqlValue::Type type() const {
     switch (type_) {
       case ColumnType::kInt32:
@@ -318,22 +246,30 @@
   JoinKey join_key() const { return JoinKey{col_idx_in_table_}; }
 
  protected:
+  // Returns the string at the index |idx|.
+  // Should only be called when |type_| == ColumnType::kString.
   NullTermStringView GetStringPoolStringAtIdx(uint32_t idx) const {
+    PERFETTO_DCHECK(type_ == ColumnType::kString);
     return string_pool_->Get(sparse_vector<StringPool::Id>().GetNonNull(idx));
   }
 
+  // Returns the backing sparse vector cast to contain data of type T.
+  // Should only be called when |type_| == ToColumnType<T>().
   template <typename T>
   SparseVector<T>* mutable_sparse_vector() {
     PERFETTO_DCHECK(ToColumnType<T>() == type_);
     return static_cast<SparseVector<T>*>(sparse_vector_);
   }
 
+  // Returns the backing sparse vector cast to contain data of type T.
+  // Should only be called when |type_| == ToColumnType<T>().
   template <typename T>
   const SparseVector<T>& sparse_vector() const {
     PERFETTO_DCHECK(ToColumnType<T>() == type_);
     return *static_cast<const SparseVector<T>*>(sparse_vector_);
   }
 
+  // Converts a primitive numeric value to an SqlValue of the correct type.
   template <typename T>
   static SqlValue NumericToSqlValue(T value) {
     if (std::is_same<T, double>::value) {
@@ -402,6 +338,7 @@
 
   friend class Table;
 
+  // Base constructor for this class which all other constructors call into.
   Column(const char* name,
          ColumnType type,
          uint32_t flags,
@@ -442,377 +379,78 @@
     PERFETTO_FATAL("For GCC");
   }
 
+  // Optimized filter method for sorted columns.
+  // Returns whether the constraint was handled by the method.
+  bool FilterIntoSorted(FilterOp op, SqlValue value, RowMap* rm) const {
+    PERFETTO_DCHECK(IsSorted());
+    PERFETTO_DCHECK(value.type == type());
+
+    Iterator b(this, 0);
+    Iterator e(this, row_map().size());
+    switch (op) {
+      case FilterOp::kEq: {
+        uint32_t beg = std::distance(b, std::lower_bound(b, e, value));
+        uint32_t end = std::distance(b, std::upper_bound(b, e, value));
+        rm->Intersect(RowMap(beg, end));
+        return true;
+      }
+      case FilterOp::kLe: {
+        uint32_t end = std::distance(b, std::upper_bound(b, e, value));
+        rm->Intersect(RowMap(0, end));
+        return true;
+      }
+      case FilterOp::kLt: {
+        uint32_t end = std::distance(b, std::lower_bound(b, e, value));
+        rm->Intersect(RowMap(0, end));
+        return true;
+      }
+      case FilterOp::kGe: {
+        uint32_t beg = std::distance(b, std::lower_bound(b, e, value));
+        rm->Intersect(RowMap(beg, row_map().size()));
+        return true;
+      }
+      case FilterOp::kGt: {
+        uint32_t beg = std::distance(b, std::upper_bound(b, e, value));
+        rm->Intersect(RowMap(beg, row_map().size()));
+        return true;
+      }
+      case FilterOp::kNe:
+      case FilterOp::kIsNull:
+      case FilterOp::kIsNotNull:
+      case FilterOp::kLike:
+        break;
+    }
+    return false;
+  }
+
+  // Slow path filter method which will perform a full table scan.
+  void FilterIntoSlow(FilterOp op, SqlValue value, RowMap* rm) const;
+
+  // Slow path filter method for numerics which will perform a full table scan.
   template <typename T, bool is_nullable>
-  void FilterIntoLongSlow(FilterOp op, SqlValue value, RowMap* rm) const {
-    if (op == FilterOp::kIsNull) {
-      PERFETTO_DCHECK(value.is_null());
-      if (is_nullable) {
-        row_map().FilterInto(rm, [this](uint32_t row) {
-          return !sparse_vector<T>().Get(row).has_value();
-        });
-      } else {
-        rm->Intersect(RowMap());
-      }
-      return;
-    } else if (op == FilterOp::kIsNotNull) {
-      PERFETTO_DCHECK(value.is_null());
-      if (is_nullable) {
-        row_map().FilterInto(rm, [this](uint32_t row) {
-          return sparse_vector<T>().Get(row).has_value();
-        });
-      }
-      return;
-    }
+  void FilterIntoNumericSlow(FilterOp op, SqlValue value, RowMap* rm) const;
 
-    if (value.type != SqlValue::Type::kLong) {
-      rm->Intersect(RowMap());
-      return;
-    }
+  // Slow path filter method for numerics with a comparator which will perform a
+  // full table scan.
+  template <typename T, bool is_nullable, typename Comparator = int(T)>
+  void FilterIntoNumericWithComparatorSlow(FilterOp op,
+                                           RowMap* rm,
+                                           Comparator cmp) const;
 
-    int64_t long_value = value.long_value;
-    switch (op) {
-      case FilterOp::kLt:
-        row_map().FilterInto(rm, [this, long_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<T>().Get(idx);
-            return opt_value && *opt_value < long_value;
-          }
-          return sparse_vector<T>().GetNonNull(idx) < long_value;
-        });
-        break;
-      case FilterOp::kEq:
-        row_map().FilterInto(rm, [this, long_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<T>().Get(idx);
-            return opt_value && opt_value == long_value;
-          }
-          return sparse_vector<T>().GetNonNull(idx) == long_value;
-        });
-        break;
-      case FilterOp::kGt:
-        row_map().FilterInto(rm, [this, long_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<T>().Get(idx);
-            return opt_value && opt_value > long_value;
-          }
-          return sparse_vector<T>().GetNonNull(idx) > long_value;
-        });
-        break;
-      case FilterOp::kNe:
-        row_map().FilterInto(rm, [this, long_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<T>().Get(idx);
-            return opt_value && opt_value != long_value;
-          }
-          return sparse_vector<T>().GetNonNull(idx) != long_value;
-        });
-        break;
-      case FilterOp::kLe:
-        row_map().FilterInto(rm, [this, long_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<T>().Get(idx);
-            return opt_value && opt_value <= long_value;
-          }
-          return sparse_vector<T>().GetNonNull(idx) <= long_value;
-        });
-        break;
-      case FilterOp::kGe:
-        row_map().FilterInto(rm, [this, long_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<T>().Get(idx);
-            return opt_value && opt_value >= long_value;
-          }
-          return sparse_vector<T>().GetNonNull(idx) >= long_value;
-        });
-        break;
-      case FilterOp::kLike:
-        rm->Intersect(RowMap());
-        break;
-      case FilterOp::kIsNull:
-      case FilterOp::kIsNotNull:
-        PERFETTO_FATAL("Should be handled above");
-    }
-  }
+  // Slow path filter method for strings which will perform a full table scan.
+  void FilterIntoStringSlow(FilterOp op, SqlValue value, RowMap* rm) const;
 
-  template <bool is_nullable>
-  void FilterIntoDoubleSlow(FilterOp op, SqlValue value, RowMap* rm) const {
-    if (op == FilterOp::kIsNull) {
-      PERFETTO_DCHECK(value.is_null());
-      if (is_nullable) {
-        row_map().FilterInto(rm, [this](uint32_t row) {
-          return !sparse_vector<double>().Get(row).has_value();
-        });
-      } else {
-        rm->Intersect(RowMap());
-      }
-      return;
-    } else if (op == FilterOp::kIsNotNull) {
-      PERFETTO_DCHECK(value.is_null());
-      if (is_nullable) {
-        row_map().FilterInto(rm, [this](uint32_t row) {
-          return sparse_vector<double>().Get(row).has_value();
-        });
-      }
-      return;
-    }
+  // Slow path filter method for ids which will perform a full table scan.
+  void FilterIntoIdSlow(FilterOp op, SqlValue value, RowMap* rm) const;
 
-    if (value.type != SqlValue::Type::kDouble) {
-      rm->Intersect(RowMap());
-      return;
-    }
-
-    double double_value = value.double_value;
-    switch (op) {
-      case FilterOp::kLt:
-        row_map().FilterInto(rm, [this, double_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<double>().Get(idx);
-            return opt_value && opt_value < double_value;
-          }
-          return sparse_vector<double>().GetNonNull(idx) < double_value;
-        });
-        break;
-      case FilterOp::kEq:
-        row_map().FilterInto(rm, [this, double_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<double>().Get(idx);
-            return opt_value &&
-                   std::equal_to<double>()(*opt_value, double_value);
-          }
-          auto v = sparse_vector<double>().GetNonNull(idx);
-          return std::equal_to<double>()(v, double_value);
-        });
-        break;
-      case FilterOp::kGt:
-        row_map().FilterInto(rm, [this, double_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<double>().Get(idx);
-            return opt_value && opt_value > double_value;
-          }
-          return sparse_vector<double>().GetNonNull(idx) > double_value;
-        });
-        break;
-      case FilterOp::kNe:
-        row_map().FilterInto(rm, [this, double_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<double>().Get(idx);
-            return opt_value &&
-                   std::not_equal_to<double>()(*opt_value, double_value);
-          }
-          auto v = sparse_vector<double>().GetNonNull(idx);
-          return std::not_equal_to<double>()(v, double_value);
-        });
-        break;
-      case FilterOp::kLe:
-        row_map().FilterInto(rm, [this, double_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<double>().Get(idx);
-            return opt_value && opt_value <= double_value;
-          }
-          return sparse_vector<double>().GetNonNull(idx) <= double_value;
-        });
-        break;
-      case FilterOp::kGe:
-        row_map().FilterInto(rm, [this, double_value](uint32_t idx) {
-          if (is_nullable) {
-            auto opt_value = sparse_vector<double>().Get(idx);
-            return opt_value && opt_value >= double_value;
-          }
-          return sparse_vector<double>().GetNonNull(idx) >= double_value;
-        });
-        break;
-      case FilterOp::kLike:
-        rm->Intersect(RowMap());
-        break;
-      case FilterOp::kIsNull:
-      case FilterOp::kIsNotNull:
-        PERFETTO_FATAL("Should be handled above");
-    }
-  }
-
-  void FilterIntoStringSlow(FilterOp op, SqlValue value, RowMap* rm) const {
-    if (op == FilterOp::kIsNull) {
-      PERFETTO_DCHECK(value.is_null());
-      row_map().FilterInto(rm, [this](uint32_t row) {
-        return GetStringPoolStringAtIdx(row).data() == nullptr;
-      });
-      return;
-    } else if (op == FilterOp::kIsNotNull) {
-      PERFETTO_DCHECK(value.is_null());
-      row_map().FilterInto(rm, [this](uint32_t row) {
-        return GetStringPoolStringAtIdx(row).data() != nullptr;
-      });
-      return;
-    }
-
-    if (value.type != SqlValue::Type::kString) {
-      rm->Intersect(RowMap());
-      return;
-    }
-
-    NullTermStringView str_value = value.string_value;
-    switch (op) {
-      case FilterOp::kLt:
-        row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
-          auto v = GetStringPoolStringAtIdx(idx);
-          return v.data() != nullptr && v < str_value;
-        });
-        break;
-      case FilterOp::kEq:
-        row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
-          auto v = GetStringPoolStringAtIdx(idx);
-          return v.data() != nullptr && v == str_value;
-        });
-        break;
-      case FilterOp::kGt:
-        row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
-          auto v = GetStringPoolStringAtIdx(idx);
-          return v.data() != nullptr && v > str_value;
-        });
-        break;
-      case FilterOp::kNe:
-        row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
-          auto v = GetStringPoolStringAtIdx(idx);
-          return v.data() != nullptr && v != str_value;
-        });
-        break;
-      case FilterOp::kLe:
-        row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
-          auto v = GetStringPoolStringAtIdx(idx);
-          return v.data() != nullptr && v <= str_value;
-        });
-        break;
-      case FilterOp::kGe:
-        row_map().FilterInto(rm, [this, str_value](uint32_t idx) {
-          auto v = GetStringPoolStringAtIdx(idx);
-          return v.data() != nullptr && v >= str_value;
-        });
-        break;
-      case FilterOp::kLike:
-        // TODO(lalitm): either call through to SQLite or reimplement
-        // like ourselves.
-        PERFETTO_DLOG("Ignoring like constraint on string column");
-        break;
-      case FilterOp::kIsNull:
-      case FilterOp::kIsNotNull:
-        PERFETTO_FATAL("Should be handled above");
-    }
-  }
-
-  void FilterIntoIdSlow(FilterOp op, SqlValue value, RowMap* rm) const {
-    if (op == FilterOp::kIsNull) {
-      PERFETTO_DCHECK(value.is_null());
-      rm->Intersect(RowMap());
-      return;
-    } else if (op == FilterOp::kIsNotNull) {
-      PERFETTO_DCHECK(value.is_null());
-      return;
-    }
-
-    if (value.type != SqlValue::Type::kLong) {
-      rm->Intersect(RowMap());
-      return;
-    }
-
-    uint32_t id_value = static_cast<uint32_t>(value.long_value);
-    switch (op) {
-      case FilterOp::kLt:
-        row_map().FilterInto(
-            rm, [id_value](uint32_t idx) { return idx < id_value; });
-        break;
-      case FilterOp::kEq:
-        row_map().FilterInto(
-            rm, [id_value](uint32_t idx) { return idx == id_value; });
-        break;
-      case FilterOp::kGt:
-        row_map().FilterInto(
-            rm, [id_value](uint32_t idx) { return idx > id_value; });
-        break;
-      case FilterOp::kNe:
-        row_map().FilterInto(
-            rm, [id_value](uint32_t idx) { return idx != id_value; });
-        break;
-      case FilterOp::kLe:
-        row_map().FilterInto(
-            rm, [id_value](uint32_t idx) { return idx <= id_value; });
-        break;
-      case FilterOp::kGe:
-        row_map().FilterInto(
-            rm, [id_value](uint32_t idx) { return idx >= id_value; });
-        break;
-      case FilterOp::kLike:
-        rm->Intersect(RowMap());
-        break;
-      case FilterOp::kIsNull:
-      case FilterOp::kIsNotNull:
-        PERFETTO_FATAL("Should be handled above");
-    }
-  }
-
+  // Stable sorts this column storing the result in |out|.
   template <bool desc>
-  void StableSort(std::vector<uint32_t>* out) const {
-    switch (type_) {
-      case ColumnType::kInt32: {
-        if (IsNullable()) {
-          StableSort<desc, int32_t, true /* is_nullable */>(out);
-        } else {
-          StableSort<desc, int32_t, false /* is_nullable */>(out);
-        }
-        break;
-      }
-      case ColumnType::kUint32: {
-        if (IsNullable()) {
-          StableSort<desc, uint32_t, true /* is_nullable */>(out);
-        } else {
-          StableSort<desc, uint32_t, false /* is_nullable */>(out);
-        }
-        break;
-      }
-      case ColumnType::kInt64: {
-        if (IsNullable()) {
-          StableSort<desc, int64_t, true /* is_nullable */>(out);
-        } else {
-          StableSort<desc, int64_t, false /* is_nullable */>(out);
-        }
-        break;
-      }
-      case ColumnType::kDouble: {
-        if (IsNullable()) {
-          StableSort<desc, double, true /* is_nullable */>(out);
-        } else {
-          StableSort<desc, double, false /* is_nullable */>(out);
-        }
-        break;
-      }
-      case ColumnType::kString: {
-        row_map().StableSort(out, [this](uint32_t a_idx, uint32_t b_idx) {
-          auto a_str = GetStringPoolStringAtIdx(a_idx);
-          auto b_str = GetStringPoolStringAtIdx(b_idx);
-          return desc ? b_str < a_str : a_str < b_str;
-        });
-        break;
-      }
-      case ColumnType::kId:
-        row_map().StableSort(out, [](uint32_t a_idx, uint32_t b_idx) {
-          return desc ? b_idx < a_idx : a_idx < b_idx;
-        });
-    }
-  }
+  void StableSort(std::vector<uint32_t>* out) const;
 
+  // Stable sorts this column storing the result in |out|.
+  // |T| and |is_nullable| should match the type and nullability of this column.
   template <bool desc, typename T, bool is_nullable>
-  void StableSort(std::vector<uint32_t>* out) const {
-    const auto& sv = sparse_vector<T>();
-    row_map().StableSort(out, [&sv](uint32_t a_idx, uint32_t b_idx) {
-      if (is_nullable) {
-        auto a_val = sv.Get(a_idx);
-        auto b_val = sv.Get(b_idx);
-        return desc ? b_val < a_val : a_val < b_val;
-      }
-      auto a_val = sv.GetNonNull(a_idx);
-      auto b_val = sv.GetNonNull(b_idx);
-      return desc ? b_val < a_val : a_val < b_val;
-    });
-  }
+  void StableSort(std::vector<uint32_t>* out) const;
 
   template <typename T>
   static ColumnType ToColumnType() {
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index e7affb4..0ed3e7e 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -442,19 +442,19 @@
 util::Status ExportSlices(const TraceStorage* storage,
                           const ArgsBuilder& args_builder,
                           TraceFormatWriter* writer) {
-  const auto& slices = storage->nestable_slices();
-  for (uint32_t i = 0; i < slices.slice_count(); ++i) {
+  const auto& slices = storage->slice_table();
+  for (uint32_t i = 0; i < slices.size(); ++i) {
     Json::Value event;
-    event["ts"] = Json::Int64(slices.start_ns()[i] / 1000);
-    event["cat"] = GetNonNullString(storage, slices.categories()[i]);
-    event["name"] = GetNonNullString(storage, slices.names()[i]);
+    event["ts"] = Json::Int64(slices.ts()[i] / 1000);
+    event["cat"] = GetNonNullString(storage, slices.category()[i]);
+    event["name"] = GetNonNullString(storage, slices.name()[i]);
     event["pid"] = 0;
     event["tid"] = 0;
 
     int32_t legacy_tid = 0;
 
     event["args"] =
-        args_builder.GetArgs(slices.arg_set_ids()[i]);  // Makes a copy.
+        args_builder.GetArgs(slices.arg_set_id()[i]);  // Makes a copy.
     if (event["args"].isMember(kLegacyEventArgsKey)) {
       ConvertLegacyFlowEventArgs(event["args"][kLegacyEventArgsKey], &event);
 
@@ -489,7 +489,7 @@
     const auto& thread_slices = storage->thread_slices();
     const auto& virtual_track_slices = storage->virtual_track_slices();
 
-    int64_t duration_ns = slices.durations()[i];
+    int64_t duration_ns = slices.dur()[i];
     int64_t thread_ts_ns = 0;
     int64_t thread_duration_ns = 0;
     int64_t thread_instruction_count = 0;
@@ -637,8 +637,7 @@
         // write the end event in this case.
         if (duration_ns > 0) {
           event["ph"] = "e";
-          event["ts"] =
-              Json::Int64((slices.start_ns()[i] + duration_ns) / 1000);
+          event["ts"] = Json::Int64((slices.ts()[i] + duration_ns) / 1000);
           if (thread_ts_ns > 0) {
             event["tts"] =
                 Json::Int64((thread_ts_ns + thread_duration_ns) / 1000);
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 6acfa96..ded2e75 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -119,9 +119,11 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp, kDuration, track, utid, RefType::kRefUtid, cat_id, name_id, 0,
-      0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  context_.storage->mutable_slice_table()->Insert({kTimestamp, kDuration, track,
+                                                   utid, ref_type_id, cat_id,
+                                                   name_id, 0, 0, 0});
   context_.storage->mutable_thread_slices()->AddThreadSlice(
       0, kThreadTimestamp, kThreadDuration, kThreadInstructionCount,
       kThreadInstructionDelta);
@@ -167,9 +169,11 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp, kDuration, track, utid, RefType::kRefUtid, cat_id, name_id, 0,
-      0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  context_.storage->mutable_slice_table()->Insert({kTimestamp, kDuration, track,
+                                                   utid, ref_type_id, cat_id,
+                                                   name_id, 0, 0, 0});
   context_.storage->mutable_thread_slices()->AddThreadSlice(
       0, kThreadTimestamp, kThreadDuration, kThreadInstructionCount,
       kThreadInstructionDelta);
@@ -230,8 +234,10 @@
 
   StringId cat_id = context_.storage->InternString("cat");
   StringId name_id = context_.storage->InternString("name");
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      0, 0, track, track, RefType::kRefTrack, cat_id, name_id, 0, 0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefTrack)]);
+  context_.storage->mutable_slice_table()->Insert(
+      {0, 0, track, track, ref_type_id, cat_id, name_id, 0, 0, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -402,8 +408,10 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  context_.storage->mutable_slice_table()->Insert(
+      {0, 0, track, utid, ref_type_id, cat_id, name_id, 0, 0, 0});
 
   StringId arg_key_id = context_.storage->InternString(
       base::StringView("task.posted_from.file_name"));
@@ -414,7 +422,7 @@
   arg.key = arg_key_id;
   arg.value = Variadic::String(arg_value_id);
   ArgSetId args = context_.storage->mutable_args()->AddArgSet({arg}, 0, 1);
-  context_.storage->mutable_nestable_slices()->set_arg_set_id(0, args);
+  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -447,10 +455,12 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = storage->InternString(base::StringView(kCategory));
   StringId name_id = storage->InternString(base::StringView(kName));
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
   RowId row_id = TraceStorage::CreateRowId(
       kNestableSlices,
-      storage->mutable_nestable_slices()->AddSlice(
-          0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0));
+      storage->mutable_slice_table()->Insert(
+          {0, 0, track, utid, ref_type_id, cat_id, name_id, 0, 0, 0}));
 
   auto add_arg = [&](const char* key, Variadic value) {
     StringId key_id = storage->InternString(key);
@@ -497,8 +507,10 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  context_.storage->mutable_slice_table()->Insert(
+      {0, 0, track, utid, ref_type_id, cat_id, name_id, 0, 0, 0});
 
   StringId arg_flat_key_id = context_.storage->InternString(
       base::StringView("debug.draw_duration_ms"));
@@ -516,7 +528,7 @@
   arg1.value = Variadic::Real(kValues[1]);
   ArgSetId args =
       context_.storage->mutable_args()->AddArgSet({arg0, arg1}, 0, 2);
-  context_.storage->mutable_nestable_slices()->set_arg_set_id(0, args);
+  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -547,8 +559,10 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  context_.storage->mutable_slice_table()->Insert(
+      {0, 0, track, utid, ref_type_id, cat_id, name_id, 0, 0, 0});
 
   StringId arg_key0_id =
       context_.storage->InternString(base::StringView("arg0"));
@@ -564,7 +578,7 @@
   arg1.value = Variadic::Pointer(kValue1);
   ArgSetId args =
       context_.storage->mutable_args()->AddArgSet({arg0, arg1}, 0, 2);
-  context_.storage->mutable_nestable_slices()->set_arg_set_id(0, args);
+  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -593,8 +607,10 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  context_.storage->mutable_slice_table()->Insert(
+      {0, 0, track, utid, ref_type_id, cat_id, name_id, 0, 0, 0});
 
   StringId arg_flat_key_id =
       context_.storage->InternString(base::StringView("a.b"));
@@ -612,7 +628,7 @@
   arg1.value = Variadic::Integer(kValues[1]);
   ArgSetId args =
       context_.storage->mutable_args()->AddArgSet({arg0, arg1}, 0, 2);
-  context_.storage->mutable_nestable_slices()->set_arg_set_id(0, args);
+  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -642,8 +658,10 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  context_.storage->mutable_slice_table()->Insert(
+      {0, 0, track, utid, ref_type_id, cat_id, name_id, 0, 0, 0});
 
   StringId arg_flat_key_id =
       context_.storage->InternString(base::StringView("a"));
@@ -661,7 +679,7 @@
   arg1.value = Variadic::Integer(kValues[1]);
   ArgSetId args =
       context_.storage->mutable_args()->AddArgSet({arg0, arg1}, 0, 2);
-  context_.storage->mutable_nestable_slices()->set_arg_set_id(0, args);
+  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -691,8 +709,10 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  context_.storage->mutable_slice_table()->Insert(
+      {0, 0, track, utid, ref_type_id, cat_id, name_id, 0, 0, 0});
 
   StringId arg_key_id = context_.storage->InternString(base::StringView("a"));
   StringId arg_value_id =
@@ -702,7 +722,7 @@
   arg.key = arg_key_id;
   arg.value = Variadic::Json(arg_value_id);
   ArgSetId args = context_.storage->mutable_args()->AddArgSet({arg}, 0, 1);
-  context_.storage->mutable_nestable_slices()->set_arg_set_id(0, args);
+  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -729,8 +749,9 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp, 0, track, 0, RefType::kRefNoRef, cat_id, name_id, 0, 0, 0);
+  StringId ref_type_id = context_.storage->InternString(base::StringView());
+  context_.storage->mutable_slice_table()->Insert(
+      {kTimestamp, 0, track, 0, ref_type_id, cat_id, name_id, 0, 0, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -761,8 +782,10 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  context_.storage->mutable_slice_table()->Insert(
+      {kTimestamp, 0, track, utid, ref_type_id, cat_id, name_id, 0, 0, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -794,6 +817,8 @@
   UniquePid upid = context_.storage->AddEmptyProcess(kProcessID);
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefTrack)]);
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
@@ -801,9 +826,9 @@
       /*source_scope=*/0);
   context_.args_tracker->Flush();  // Flush track args.
 
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp, kDuration, track, track, RefType::kRefTrack, cat_id, name_id,
-      0, 0, 0);
+  context_.storage->mutable_slice_table()->Insert({kTimestamp, kDuration, track,
+                                                   track, ref_type_id, cat_id,
+                                                   name_id, 0, 0, 0});
   StringId arg_key_id =
       context_.storage->InternString(base::StringView(kArgName));
   TraceStorage::Args::Arg arg;
@@ -811,7 +836,7 @@
   arg.key = arg_key_id;
   arg.value = Variadic::Integer(kArgValue);
   ArgSetId args = context_.storage->mutable_args()->AddArgSet({arg}, 0, 1);
-  context_.storage->mutable_nestable_slices()->set_arg_set_id(0, args);
+  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -858,6 +883,8 @@
   UniquePid upid = context_.storage->AddEmptyProcess(kProcessID);
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefTrack)]);
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
@@ -865,9 +892,9 @@
       /*source_scope=*/0);
   context_.args_tracker->Flush();  // Flush track args.
 
-  auto slice_id = context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp, kDuration, track, track, RefType::kRefTrack, cat_id, name_id,
-      0, 0, 0);
+  auto slice_id = context_.storage->mutable_slice_table()->Insert(
+      {kTimestamp, kDuration, track, track, ref_type_id, cat_id, name_id, 0, 0,
+       0});
   context_.storage->mutable_virtual_track_slices()->AddVirtualTrackSlice(
       slice_id, kThreadTimestamp, kThreadDuration, 0, 0);
 
@@ -914,6 +941,8 @@
   UniquePid upid = context_.storage->AddEmptyProcess(kProcessID);
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefTrack)]);
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
@@ -921,9 +950,9 @@
       /*source_scope=*/0);
   context_.args_tracker->Flush();  // Flush track args.
 
-  auto slice_id = context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp, kDuration, track, track, RefType::kRefTrack, cat_id, name_id,
-      0, 0, 0);
+  auto slice_id = context_.storage->mutable_slice_table()->Insert(
+      {kTimestamp, kDuration, track, track, ref_type_id, cat_id, name_id, 0, 0,
+       0});
   context_.storage->mutable_virtual_track_slices()->AddVirtualTrackSlice(
       slice_id, kThreadTimestamp, kThreadDuration, 0, 0);
 
@@ -958,6 +987,8 @@
   UniquePid upid = context_.storage->AddEmptyProcess(kProcessID);
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefTrack)]);
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
@@ -965,9 +996,8 @@
       /*source_scope=*/0);
   context_.args_tracker->Flush();  // Flush track args.
 
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp, 0, track, track, RefType::kRefTrack, cat_id, name_id, 0, 0,
-      0);
+  context_.storage->mutable_slice_table()->Insert(
+      {kTimestamp, 0, track, track, ref_type_id, cat_id, name_id, 0, 0, 0});
   StringId arg_key_id =
       context_.storage->InternString(base::StringView("arg_name"));
   TraceStorage::Args::Arg arg;
@@ -975,7 +1005,7 @@
   arg.key = arg_key_id;
   arg.value = Variadic::Integer(kArgValue);
   ArgSetId args = context_.storage->mutable_args()->AddArgSet({arg}, 0, 1);
-  context_.storage->mutable_nestable_slices()->set_arg_set_id(0, args);
+  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -1220,13 +1250,15 @@
   StringId arg1_id = context_.storage->InternString(base::StringView("arg1"));
   StringId arg2_id = context_.storage->InternString(base::StringView("arg2"));
   StringId val_id = context_.storage->InternString(base::StringView("val"));
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
 
   std::array<RowId, 3> slice_ids;
   for (size_t i = 0; i < name_ids.size(); i++) {
     slice_ids[i] = TraceStorage::CreateRowId(
-        kNestableSlices, context_.storage->mutable_nestable_slices()->AddSlice(
-                             0, 0, track, utid, RefType::kRefUtid, cat_id,
-                             name_ids[i], 0, 0, 0));
+        kNestableSlices,
+        context_.storage->mutable_slice_table()->Insert(
+            {0, 0, track, utid, ref_type_id, cat_id, name_ids[i], 0, 0, 0}));
   }
 
   for (RowId row : slice_ids) {
@@ -1326,13 +1358,15 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
+  StringId ref_type_id = context_.storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
 
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp1, kDuration, track, utid, RefType::kRefUtid, cat_id, name_id,
-      0, 0, 0);
-  context_.storage->mutable_nestable_slices()->AddSlice(
-      kTimestamp2, kDuration, track, utid, RefType::kRefUtid, cat_id, name_id,
-      0, 0, 0);
+  context_.storage->mutable_slice_table()->Insert({kTimestamp1, kDuration,
+                                                   track, utid, ref_type_id,
+                                                   cat_id, name_id, 0, 0, 0});
+  context_.storage->mutable_slice_table()->Insert({kTimestamp2, kDuration,
+                                                   track, utid, ref_type_id,
+                                                   cat_id, name_id, 0, 0, 0});
 
   auto label_filter = [](const char* label_name) {
     return strcmp(label_name, "traceEvents") == 0;
diff --git a/src/trace_processor/importers/ftrace/ftrace_module.cc b/src/trace_processor/importers/ftrace/ftrace_module.cc
new file mode 100644
index 0000000..840f330
--- /dev/null
+++ b/src/trace_processor/importers/ftrace/ftrace_module.cc
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "src/trace_processor/importers/ftrace/ftrace_module.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+void FtraceModule::ParseFtracePacket(uint32_t /*cpu*/,
+                                     const TimestampedTracePiece&) {}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/ftrace_module.h b/src/trace_processor/importers/ftrace/ftrace_module.h
index fd1ace0..5f43490 100644
--- a/src/trace_processor/importers/ftrace/ftrace_module.h
+++ b/src/trace_processor/importers/ftrace/ftrace_module.h
@@ -29,44 +29,29 @@
 namespace perfetto {
 namespace trace_processor {
 
-class FtraceModule
-    : public ProtoImporterModuleBase<PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)> {
+class FtraceModule : public ProtoImporterModule {
  public:
-  explicit FtraceModule(TraceProcessorContext* context)
-      : ProtoImporterModuleBase(context),
-        tokenizer_(context),
-        parser_(context) {}
+  virtual void ParseFtracePacket(uint32_t cpu,
+                                 const TimestampedTracePiece& ttp);
+};
+
+class FtraceModuleImpl : public FtraceModule {
+ public:
+  FtraceModuleImpl(TraceProcessorContext* context);
 
   ModuleResult TokenizePacket(
       const protos::pbzero::TracePacket::Decoder& decoder,
       TraceBlobView* packet,
-      int64_t /*packet_timestamp*/,
-      PacketSequenceState* /*state*/) {
-    if (decoder.has_ftrace_events()) {
-      auto ftrace_field = decoder.ftrace_events();
-      const size_t fld_off = packet->offset_of(ftrace_field.data);
-      tokenizer_.TokenizeFtraceBundle(
-          packet->slice(fld_off, ftrace_field.size));
-      return ModuleResult::Handled();
-    }
-    return ModuleResult::Ignored();
-  }
+      int64_t packet_timestamp,
+      PacketSequenceState* state,
+      uint32_t field_id) override;
 
-  ModuleResult ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
-                           const TimestampedTracePiece&) {
-    // TODO(eseckler): implement.
-    if (decoder.has_ftrace_stats()) {
-      parser_.ParseFtraceStats(decoder.ftrace_stats());
-      return ModuleResult::Handled();
-    }
+  void ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
+                   const TimestampedTracePiece&,
+                   uint32_t field_id) override;
 
-    return ModuleResult::Ignored();
-  }
-
-  ModuleResult ParseFtracePacket(uint32_t cpu,
-                                 const TimestampedTracePiece& ttp) {
-    return parser_.ParseFtraceEvent(cpu, ttp);
-  }
+  void ParseFtracePacket(uint32_t cpu,
+                         const TimestampedTracePiece& ttp) override;
 
  private:
   FtraceTokenizer tokenizer_;
diff --git a/src/trace_processor/importers/ftrace/ftrace_module_impl.cc b/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
new file mode 100644
index 0000000..2347196
--- /dev/null
+++ b/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "perfetto/base/build_config.h"
+#include "src/trace_processor/importers/ftrace/ftrace_module.h"
+#include "src/trace_processor/importers/ftrace/ftrace_parser.h"
+#include "src/trace_processor/importers/ftrace/ftrace_tokenizer.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
+#include "src/trace_processor/trace_blob_view.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using perfetto::protos::pbzero::TracePacket;
+
+FtraceModuleImpl::FtraceModuleImpl(TraceProcessorContext* context)
+    : tokenizer_(context), parser_(context) {
+  RegisterForField(TracePacket::kFtraceEventsFieldNumber, context);
+  RegisterForField(TracePacket::kFtraceStatsFieldNumber, context);
+}
+
+ModuleResult FtraceModuleImpl::TokenizePacket(
+    const protos::pbzero::TracePacket::Decoder& decoder,
+    TraceBlobView* packet,
+    int64_t /*packet_timestamp*/,
+    PacketSequenceState* /*state*/,
+    uint32_t field_id) {
+  if (field_id == TracePacket::kFtraceEventsFieldNumber) {
+    auto ftrace_field = decoder.ftrace_events();
+    const size_t fld_off = packet->offset_of(ftrace_field.data);
+    tokenizer_.TokenizeFtraceBundle(packet->slice(fld_off, ftrace_field.size));
+    return ModuleResult::Handled();
+  }
+  return ModuleResult::Ignored();
+}
+
+void FtraceModuleImpl::ParsePacket(
+    const protos::pbzero::TracePacket::Decoder& decoder,
+    const TimestampedTracePiece&,
+    uint32_t field_id) {
+  if (field_id == TracePacket::kFtraceStatsFieldNumber) {
+    parser_.ParseFtraceStats(decoder.ftrace_stats());
+  }
+}
+
+void FtraceModuleImpl::ParseFtracePacket(uint32_t cpu,
+                                         const TimestampedTracePiece& ttp) {
+  util::Status res = parser_.ParseFtraceEvent(cpu, ttp);
+  if (!res.ok()) {
+    PERFETTO_ELOG("%s", res.message().c_str());
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/android_probes_module.cc b/src/trace_processor/importers/proto/android_probes_module.cc
new file mode 100644
index 0000000..5f6c034
--- /dev/null
+++ b/src/trace_processor/importers/proto/android_probes_module.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "src/trace_processor/importers/proto/android_probes_module.h"
+#include "perfetto/base/build_config.h"
+#include "src/trace_processor/importers/proto/android_probes_parser.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
+
+#include "protos/perfetto/config/trace_config.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using perfetto::protos::pbzero::TracePacket;
+
+AndroidProbesModule::AndroidProbesModule(TraceProcessorContext* context)
+    : parser_(context) {
+  RegisterForField(TracePacket::kBatteryFieldNumber, context);
+  RegisterForField(TracePacket::kPowerRailsFieldNumber, context);
+  RegisterForField(TracePacket::kAndroidLogFieldNumber, context);
+  RegisterForField(TracePacket::kPackagesListFieldNumber, context);
+}
+
+void AndroidProbesModule::ParsePacket(const TracePacket::Decoder& decoder,
+                                      const TimestampedTracePiece& ttp,
+                                      uint32_t field_id) {
+  switch (field_id) {
+    case TracePacket::kBatteryFieldNumber:
+      parser_.ParseBatteryCounters(ttp.timestamp, decoder.battery());
+      return;
+    case TracePacket::kPowerRailsFieldNumber:
+      parser_.ParsePowerRails(ttp.timestamp, decoder.power_rails());
+      return;
+    case TracePacket::kAndroidLogFieldNumber:
+      parser_.ParseAndroidLogPacket(decoder.android_log());
+      return;
+    case TracePacket::kPackagesListFieldNumber:
+      parser_.ParseAndroidPackagesList(decoder.packages_list());
+      return;
+  }
+}
+
+void AndroidProbesModule::ParseTraceConfig(
+    const protos::pbzero::TraceConfig::Decoder& decoder) {
+  if (decoder.has_statsd_metadata()) {
+    parser_.ParseStatsdMetadata(decoder.statsd_metadata());
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/android_probes_module.h b/src/trace_processor/importers/proto/android_probes_module.h
index 94d90a5..2ba7b19 100644
--- a/src/trace_processor/importers/proto/android_probes_module.h
+++ b/src/trace_processor/importers/proto/android_probes_module.h
@@ -28,45 +28,16 @@
 namespace perfetto {
 namespace trace_processor {
 
-class AndroidProbesModule : public ProtoImporterModuleBase<PERFETTO_BUILDFLAG(
-                                PERFETTO_TP_ANDROID_PROBES)> {
+class AndroidProbesModule : public ProtoImporterModule {
  public:
-  explicit AndroidProbesModule(TraceProcessorContext* context)
-      : ProtoImporterModuleBase(context), parser_(context) {}
+  explicit AndroidProbesModule(TraceProcessorContext* context);
 
-  ModuleResult ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
-                           const TimestampedTracePiece& ttp) {
-    if (decoder.has_battery()) {
-      parser_.ParseBatteryCounters(ttp.timestamp, decoder.battery());
-      return ModuleResult::Handled();
-    }
+  void ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
+                   const TimestampedTracePiece& ttp,
+                   uint32_t field_id) override;
 
-    if (decoder.has_power_rails()) {
-      parser_.ParsePowerRails(ttp.timestamp, decoder.power_rails());
-      return ModuleResult::Handled();
-    }
-
-    if (decoder.has_android_log()) {
-      parser_.ParseAndroidLogPacket(decoder.android_log());
-      return ModuleResult::Handled();
-    }
-
-    if (decoder.has_packages_list()) {
-      parser_.ParseAndroidPackagesList(decoder.packages_list());
-      return ModuleResult::Handled();
-    }
-
-    return ModuleResult::Ignored();
-  }
-
-  ModuleResult ParseTraceConfig(
-      const protos::pbzero::TraceConfig::Decoder& decoder) {
-    if (decoder.has_statsd_metadata()) {
-      parser_.ParseStatsdMetadata(decoder.statsd_metadata());
-      return ModuleResult::Handled();
-    }
-    return ModuleResult::Ignored();
-  }
+  void ParseTraceConfig(
+      const protos::pbzero::TraceConfig::Decoder& decoder) override;
 
  private:
   AndroidProbesParser parser_;
diff --git a/src/trace_processor/importers/proto/graphics_event_module.h b/src/trace_processor/importers/proto/graphics_event_module.h
index 6774c62..f7a36dd 100644
--- a/src/trace_processor/importers/proto/graphics_event_module.h
+++ b/src/trace_processor/importers/proto/graphics_event_module.h
@@ -27,7 +27,7 @@
 namespace perfetto {
 namespace trace_processor {
 
-class GraphicsEventModule : public NewProtoImporterModule {
+class GraphicsEventModule : public ProtoImporterModule {
  public:
   explicit GraphicsEventModule(TraceProcessorContext* context);
 
diff --git a/src/trace_processor/importers/proto/graphics_event_parser.cc b/src/trace_processor/importers/proto/graphics_event_parser.cc
index 7008386..504ea5f 100644
--- a/src/trace_processor/importers/proto/graphics_event_parser.cc
+++ b/src/trace_processor/importers/proto/graphics_event_parser.cc
@@ -381,7 +381,7 @@
         if (present_slice_id_end) {
           // The slice could have had additional buffers in it, so we need to
           // update the name.
-          context_->storage->mutable_nestable_slices()->set_name(
+          context_->storage->mutable_slice_table()->mutable_name()->Set(
               present_slice_id_end.value(),
               context_->storage->InternString(
                   present_frame_name_.GetStringView()));
diff --git a/src/trace_processor/importers/proto/heap_graph_module.cc b/src/trace_processor/importers/proto/heap_graph_module.cc
index f89aafd..8823010 100644
--- a/src/trace_processor/importers/proto/heap_graph_module.cc
+++ b/src/trace_processor/importers/proto/heap_graph_module.cc
@@ -89,6 +89,29 @@
 
 }  // namespace
 
+using perfetto::protos::pbzero::TracePacket;
+
+HeapGraphModule::HeapGraphModule(TraceProcessorContext* context)
+    : context_(context) {
+  context_->heap_graph_tracker.reset(new HeapGraphTracker(context_));
+  RegisterForField(TracePacket::kHeapGraphFieldNumber, context);
+  RegisterForField(TracePacket::kDeobfuscationMappingFieldNumber, context);
+}
+
+void HeapGraphModule::ParsePacket(
+    const protos::pbzero::TracePacket::Decoder& decoder,
+    const TimestampedTracePiece& ttp,
+    uint32_t field_id) {
+  switch (field_id) {
+    case TracePacket::kHeapGraphFieldNumber:
+      ParseHeapGraph(ttp.timestamp, decoder.heap_graph());
+      return;
+    case TracePacket::kDeobfuscationMappingFieldNumber:
+      ParseDeobfuscationMapping(decoder.deobfuscation_mapping());
+      return;
+  }
+}
+
 void HeapGraphModule::ParseHeapGraph(int64_t ts, protozero::ConstBytes blob) {
   protos::pbzero::HeapGraph::Decoder heap_graph(blob.data, blob.size);
   UniquePid upid = context_->process_tracker->GetOrCreateProcess(
diff --git a/src/trace_processor/importers/proto/heap_graph_module.h b/src/trace_processor/importers/proto/heap_graph_module.h
index 5d9755e..6e3bf72 100644
--- a/src/trace_processor/importers/proto/heap_graph_module.h
+++ b/src/trace_processor/importers/proto/heap_graph_module.h
@@ -27,31 +27,19 @@
 namespace perfetto {
 namespace trace_processor {
 
-class HeapGraphModule : public ProtoImporterModuleBase<PERFETTO_BUILDFLAG(
-                            PERFETTO_TP_HEAP_GRAPHS)> {
+class HeapGraphModule : public ProtoImporterModule {
  public:
-  explicit HeapGraphModule(TraceProcessorContext* context)
-      : ProtoImporterModuleBase(context) {
-    context_->heap_graph_tracker.reset(new HeapGraphTracker(context_));
-  }
+  explicit HeapGraphModule(TraceProcessorContext* context);
 
-  ModuleResult ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
-                           const TimestampedTracePiece& ttp) {
-    if (decoder.has_heap_graph()) {
-      ParseHeapGraph(ttp.timestamp, decoder.heap_graph());
-      return ModuleResult::Handled();
-    }
-
-    if (decoder.has_deobfuscation_mapping()) {
-      ParseDeobfuscationMapping(decoder.deobfuscation_mapping());
-      return ModuleResult::Handled();
-    }
-    return ModuleResult::Ignored();
-  }
+  void ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
+                   const TimestampedTracePiece& ttp,
+                   uint32_t field_id) override;
 
  private:
   void ParseHeapGraph(int64_t ts, protozero::ConstBytes);
   void ParseDeobfuscationMapping(protozero::ConstBytes);
+
+  TraceProcessorContext* context_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/proto_importer_module.cc b/src/trace_processor/importers/proto/proto_importer_module.cc
index 48f41ef..4524384 100644
--- a/src/trace_processor/importers/proto/proto_importer_module.cc
+++ b/src/trace_processor/importers/proto/proto_importer_module.cc
@@ -20,11 +20,11 @@
 namespace perfetto {
 namespace trace_processor {
 
-NewProtoImporterModule::NewProtoImporterModule() {}
+ProtoImporterModule::ProtoImporterModule() {}
 
-NewProtoImporterModule::~NewProtoImporterModule() {}
+ProtoImporterModule::~ProtoImporterModule() {}
 
-ModuleResult NewProtoImporterModule::TokenizePacket(
+ModuleResult ProtoImporterModule::TokenizePacket(
     const protos::pbzero::TracePacket_Decoder&,
     TraceBlobView* /*packet*/,
     int64_t /*packet_timestamp*/,
@@ -33,16 +33,16 @@
   return ModuleResult::Ignored();
 }
 
-void NewProtoImporterModule::ParsePacket(
+void ProtoImporterModule::ParsePacket(
     const protos::pbzero::TracePacket_Decoder&,
     const TimestampedTracePiece&,
     uint32_t /*field_id*/) {}
 
-void NewProtoImporterModule::ParseTraceConfig(
+void ProtoImporterModule::ParseTraceConfig(
     const protos::pbzero::TraceConfig_Decoder&) {}
 
-void NewProtoImporterModule::RegisterForField(uint32_t field_id,
-                                              TraceProcessorContext* context) {
+void ProtoImporterModule::RegisterForField(uint32_t field_id,
+                                           TraceProcessorContext* context) {
   if (context->modules_by_field.size() <= field_id) {
     context->modules_by_field.resize(field_id + 1, nullptr);
   }
diff --git a/src/trace_processor/importers/proto/proto_importer_module.h b/src/trace_processor/importers/proto/proto_importer_module.h
index 8579213..60a0218 100644
--- a/src/trace_processor/importers/proto/proto_importer_module.h
+++ b/src/trace_processor/importers/proto/proto_importer_module.h
@@ -36,23 +36,18 @@
 struct TimestampedTracePiece;
 class TraceProcessorContext;
 
-// This file contains helper and base class templates for
-// ProtoTraceTokenizer/Parser modules. A module implements support for a subset
-// of features of the TracePacket proto format. Modules inherit from
-// ProtoImporterModuleBase, and should be instantiated using the
-// ProtoImporterModule<> wrapper template in trace_processor_context.h.
-//
+// This file contains a base class for ProtoTraceTokenizer/Parser modules.
+// A module implements support for a subset of features of the TracePacket
+// proto format.
 // To add and integrate a new module:
-// (1) Add MyModule as a subclass of ProtoImporterModuleBase<IsEnabled>,
-//     defining the TokenizePacket() and/or ParsePacket() methods.
-//     Typically, a build-time macro will inform the value of IsEnabled.
-//     See ftrace_module.h for an example.
-// (2) Add a member of type std::unique_ptr<ProtoImporterModule<MyModule>> to
-//     TraceProcessorContext (trace_processor_context.h) and init it from
-//     TraceProcessorImpl() and appropriate tests.
-// (3) Add an include of my_module.h and calls to your module's TokenizePacket /
-//     ParsePacket methods in ProtoTraceTokenizer and/or ProtoTraceParser
-//     (proxying via the wrapper).
+// (1) Add MyModule as a subclass of ProtoImporterModule,
+//     overriding the TokenizePacket(), ParsePacket() and/or ParseTraceConfig()
+//     methods.
+// (2) In the constructor call the RegisterForField method for every field
+//     that the module knows how to handle.
+// (3) Create a module instance and add it to the |modules| vector in
+//     TraceProcessorContext.
+// See GraphicsEventModule for an example.
 
 class ModuleResult {
  public:
@@ -96,119 +91,12 @@
   base::Optional<std::string> error_;
 };
 
-// Wrapper class for a module. This wrapper allows modules to be disabled
-// disabled at compile time to remove support for its features from the trace
-// processor.
-//
-// The trace processor will instantiate enabled modules for each
-// TraceProcessorContext. The tokenizer and parser notify individual modules
-// about trace data by calling their respective methods via the wrapper class.
-// If the module is enabled, the wrapper will forward the call to the module
-// implementation. This way, we avoid virtual methods, so that calling any of
-// the module's methods is zero overhead - they can be inlined by the compiler
-// at callsites directly.
-template <class ModuleType>
+// Base class for modules.
 class ProtoImporterModule {
  public:
-  ProtoImporterModule(TraceProcessorContext* context) {
-    if (ModuleType::kEnabled)
-      impl_.reset(new ModuleType(context));
-  }
+  ProtoImporterModule();
 
-  // ModuleType may specify methods with the signatures below.
-  // ProtoImporterModule<ModuleType> acts as a wrapper for these methods.
-  // ModuleType only needs to specify the methods that
-  // ProtoTraceParser/Tokenizer actually calls on the respective module.
-
-  // Wraps ModuleType::TokenizePacket(). If the module is disabled, compiles
-  // into a noop in optimized builds. Called by ProtoTraceTokenizer for each
-  // TracePacket during the tokenization stage, i.e. before sorting. If this
-  // returns a result other than ModuleResult::Ignored(), tokenization of the
-  // packet will be aborted after the module.
-  ModuleResult TokenizePacket(
-      const protos::pbzero::TracePacket_Decoder& decoder,
-      TraceBlobView* packet,
-      int64_t packet_timestamp,
-      PacketSequenceState* state) {
-    if (ModuleType::kEnabled) {
-      return impl_->TokenizePacket(decoder, packet, packet_timestamp, state);
-    }
-    return ModuleResult::Ignored();
-  }
-
-  // Wraps ModuleType::ParsePacket(). If the module is disabled, compiles into a
-  // noop in optimized builds. Called by ProtoTraceParser for each non-ftrace
-  // TracePacket after the sorting stage. If this returns a result other than
-  // ModuleResult::Ignored(), parsing of the packet will be aborted after the
-  // module.
-  ModuleResult ParsePacket(const protos::pbzero::TracePacket_Decoder& decoder,
-                           const TimestampedTracePiece& ttp) {
-    if (ModuleType::kEnabled)
-      return impl_->ParsePacket(decoder, ttp);
-    return ModuleResult::Ignored();
-  }
-
-  // Wraps ModuleType::ParseTraceConfig(). If the module is disabled, compiles
-  // into a noop in optimized builds. Called by ProtoTraceParser for trace
-  // config packets after the sorting stage.
-  ModuleResult ParseTraceConfig(
-      const protos::pbzero::TraceConfig_Decoder& decoder) {
-    if (ModuleType::kEnabled)
-      return impl_->ParseTraceConfig(decoder);
-    return ModuleResult::Ignored();
-  }
-
-  // For FtraceModule only. Wraps ModuleType::ParseFtracePacket(). If the module
-  // is disabled, compiles into a noop in optimized builds. Called by
-  // ProtoTraceParser for each ftrace TracePacket after the sorting stage.
-  // Ftrace packets are handled specially here because they are sorted in
-  // separate queues per CPU. If this returns a result other than
-  // ModuleResult::Ignored(), parsing of the packet will be aborted after the
-  // module.
-  ModuleResult ParseFtracePacket(uint32_t cpu,
-                                 const TimestampedTracePiece& ttp) {
-    if (ModuleType::kEnabled)
-      return impl_->ParseFtracePacket(cpu, ttp);
-    return ModuleResult::Ignored();
-  }
-
- private:
-  // Only initialized if the module is enabled.
-  std::unique_ptr<ModuleType> impl_;
-};
-
-// Base class for a proto trace module that can be disabled at compile time.
-// Typically, a build-time macro will inform the value of IsEnabled.
-template <int IsEnabled>
-class ProtoImporterModuleBase {
- public:
-  static constexpr bool kEnabled = static_cast<bool>(IsEnabled);
-
-  explicit ProtoImporterModuleBase(TraceProcessorContext* context)
-      : context_(context) {}
-  ~ProtoImporterModuleBase() {}
-
-  // See ProtoTraceModule<> for the public methods subclasses may implement.
-
- protected:
-  TraceProcessorContext* context_;
-};
-
-// This is a new module superclass that allows registering modules at runtime.
-// To add and integrate a new module:
-// (1) Add MyModule as a subclass of NewProtoImporterModule,
-//     overriding the TokenizePacket(), ParsePacket() and/or ParseTraceConfig()
-//     methods.
-// (2) In the constructor call the RegisterForField method for every field
-//     that the module knows how to handle.
-// See GraphicsEventModule for an example.
-// TODO(b/141459049): Rename this to ProtoImporterModule after all modules
-// are based on it.
-class NewProtoImporterModule {
- public:
-  NewProtoImporterModule();
-
-  virtual ~NewProtoImporterModule();
+  virtual ~ProtoImporterModule();
 
   // Called by ProtoTraceTokenizer during the tokenization stage, i.e. before
   // sorting. It's called for each TracePacket that contains fields for which
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.cc b/src/trace_processor/importers/proto/proto_trace_parser.cc
index b4bbaf9..e6d3e35 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser.cc
@@ -30,14 +30,11 @@
 #include "perfetto/ext/base/uuid.h"
 #include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/args_tracker.h"
+#include "src/trace_processor/clock_tracker.h"
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/heap_profile_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
-#include "src/trace_processor/importers/proto/android_probes_module.h"
-#include "src/trace_processor/importers/proto/heap_graph_module.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/importers/proto/system_probes_module.h"
-#include "src/trace_processor/importers/proto/track_event_module.h"
 #include "src/trace_processor/metadata.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/slice_tracker.h"
@@ -203,21 +200,6 @@
     TimestampedTracePiece ttp,
     const protos::pbzero::TracePacket::Decoder& packet) {
   // TODO(eseckler): Propagate statuses from modules.
-  if (!context_->ftrace_module->ParsePacket(packet, ttp).ignored())
-    return;
-
-  if (!context_->track_event_module->ParsePacket(packet, ttp).ignored())
-    return;
-
-  if (!context_->system_probes_module->ParsePacket(packet, ttp).ignored())
-    return;
-
-  if (!context_->android_probes_module->ParsePacket(packet, ttp).ignored())
-    return;
-
-  if (!context_->heap_graph_module->ParsePacket(packet, ttp).ignored())
-    return;
-
   auto& modules = context_->modules_by_field;
   for (uint32_t field_id = 1; field_id < modules.size(); ++field_id) {
     if (modules[field_id] && packet.Get(field_id).valid()) {
@@ -266,13 +248,8 @@
                                          int64_t /*ts*/,
                                          TimestampedTracePiece ttp) {
   PERFETTO_DCHECK(ttp.json_value == nullptr);
-
-  ModuleResult res = context_->ftrace_module->ParseFtracePacket(cpu, ttp);
-  PERFETTO_DCHECK(!res.ignored());
-  // TODO(eseckler): Propagate status.
-  if (!res.ok()) {
-    PERFETTO_ELOG("%s", res.message().c_str());
-  }
+  PERFETTO_DCHECK(context_->ftrace_module);
+  context_->ftrace_module->ParseFtracePacket(cpu, ttp);
 
   // TODO(lalitm): maybe move this to the flush method in the trace processor
   // once we have it. This may reduce performance in the ArgsTracker though so
@@ -381,6 +358,17 @@
   for (auto it = packet.process_dumps(); it; ++it) {
     protos::pbzero::ProfilePacket::ProcessHeapSamples::Decoder entry(*it);
 
+    auto maybe_timestamp = context_->clock_tracker->ToTraceTime(
+        protos::pbzero::ClockSnapshot::Clock::MONOTONIC_COARSE,
+        static_cast<int64_t>(entry.timestamp()));
+
+    if (!maybe_timestamp) {
+      context_->storage->IncrementStats(stats::clock_sync_failure);
+      continue;
+    }
+
+    int64_t timestamp = *maybe_timestamp;
+
     int pid = static_cast<int>(entry.pid());
 
     if (entry.buffer_corrupted())
@@ -398,7 +386,7 @@
 
       HeapProfileTracker::SourceAllocation src_allocation;
       src_allocation.pid = entry.pid();
-      src_allocation.timestamp = static_cast<int64_t>(entry.timestamp());
+      src_allocation.timestamp = timestamp;
       src_allocation.callstack_id = sample.callstack_id();
       src_allocation.self_allocated = sample.self_allocated();
       src_allocation.self_freed = sample.self_freed();
@@ -605,8 +593,6 @@
   protos::pbzero::TraceConfig::Decoder trace_config(blob.data, blob.size);
 
   // TODO(eseckler): Propagate statuses from modules.
-  context_->android_probes_module->ParseTraceConfig(trace_config);
-
   for (auto& module : context_->modules) {
     module->ParseTraceConfig(trace_config);
   }
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index d19edae..6d7f711 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -261,17 +261,25 @@
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_GRAPHICS)
     context_.vulkan_memory_tracker.reset(new VulkanMemoryTracker(&context_));
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_GRAPHICS)
-    context_.ftrace_module.reset(
-        new ProtoImporterModule<FtraceModule>(&context_));
-    context_.track_event_module.reset(
-        new ProtoImporterModule<TrackEventModule>(&context_));
-    context_.system_probes_module.reset(
-        new ProtoImporterModule<SystemProbesModule>(&context_));
-    context_.android_probes_module.reset(
-        new ProtoImporterModule<AndroidProbesModule>(&context_));
-    context_.heap_graph_module.reset(
-        new ProtoImporterModule<HeapGraphModule>(&context_));
 
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
+    context_.modules.emplace_back(new FtraceModuleImpl(&context_));
+#else
+    context_.modules.emplace_back(new FtraceModule());
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
+    context_.ftrace_module =
+        static_cast<FtraceModule*>(context_.modules.back().get());
+
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_HEAP_GRAPHS)
+    context_.modules.emplace_back(new HeapGraphModule(&context_));
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_HEAP_GRAPHS)
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
+    context_.modules.emplace_back(new AndroidProbesModule(&context_));
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
+    context_.modules.emplace_back(new SystemProbesModule(&context_));
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
+    context_.modules.emplace_back(new TrackEventModule(&context_));
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_GRAPHICS)
     context_.modules.emplace_back(new GraphicsEventModule(&context_));
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_GRAPHICS)
diff --git a/src/trace_processor/importers/proto/proto_trace_tokenizer.cc b/src/trace_processor/importers/proto/proto_trace_tokenizer.cc
index b75ccf4..97d7689 100644
--- a/src/trace_processor/importers/proto/proto_trace_tokenizer.cc
+++ b/src/trace_processor/importers/proto/proto_trace_tokenizer.cc
@@ -32,7 +32,6 @@
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/proto_incremental_state.h"
-#include "src/trace_processor/importers/proto/track_event_module.h"
 #include "src/trace_processor/stats.h"
 #include "src/trace_processor/trace_sorter.h"
 #include "src/trace_processor/trace_storage.h"
@@ -283,22 +282,11 @@
     ParseInternedData(decoder, packet.slice(offset, field.size));
   }
 
-  ModuleResult res = ModuleResult::Ignored();
-  res = context_->ftrace_module->TokenizePacket(decoder, &packet, timestamp,
-                                                state);
-  if (!res.ignored())
-    return res.ToStatus();
-
-  res = context_->track_event_module->TokenizePacket(decoder, &packet,
-                                                     timestamp, state);
-  if (!res.ignored())
-    return res.ToStatus();
-
   auto& modules = context_->modules_by_field;
   for (uint32_t field_id = 1; field_id < modules.size(); ++field_id) {
     if (modules[field_id] && decoder.Get(field_id).valid()) {
-      modules[field_id]->TokenizePacket(decoder, &packet, timestamp, state,
-                                        field_id);
+      ModuleResult res = modules[field_id]->TokenizePacket(
+          decoder, &packet, timestamp, state, field_id);
       if (!res.ignored())
         return res.ToStatus();
     }
diff --git a/src/trace_processor/importers/proto/system_probes_module.cc b/src/trace_processor/importers/proto/system_probes_module.cc
new file mode 100644
index 0000000..7d5f254
--- /dev/null
+++ b/src/trace_processor/importers/proto/system_probes_module.cc
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "src/trace_processor/importers/proto/system_probes_module.h"
+#include "perfetto/base/build_config.h"
+#include "src/trace_processor/importers/proto/system_probes_parser.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using perfetto::protos::pbzero::TracePacket;
+
+SystemProbesModule::SystemProbesModule(TraceProcessorContext* context)
+    : parser_(context) {
+  RegisterForField(TracePacket::kProcessTreeFieldNumber, context);
+  RegisterForField(TracePacket::kProcessStatsFieldNumber, context);
+  RegisterForField(TracePacket::kSysStatsFieldNumber, context);
+  RegisterForField(TracePacket::kSystemInfoFieldNumber, context);
+}
+
+void SystemProbesModule::ParsePacket(const TracePacket::Decoder& decoder,
+                                     const TimestampedTracePiece& ttp,
+                                     uint32_t field_id) {
+  switch (field_id) {
+    case TracePacket::kProcessTreeFieldNumber:
+      parser_.ParseProcessTree(decoder.process_tree());
+      return;
+    case TracePacket::kProcessStatsFieldNumber:
+      parser_.ParseProcessStats(ttp.timestamp, decoder.process_stats());
+      return;
+    case TracePacket::kSysStatsFieldNumber:
+      parser_.ParseSysStats(ttp.timestamp, decoder.sys_stats());
+      return;
+    case TracePacket::kSystemInfoFieldNumber:
+      parser_.ParseSystemInfo(decoder.system_info());
+      return;
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/system_probes_module.h b/src/trace_processor/importers/proto/system_probes_module.h
index 53db890..438eabe 100644
--- a/src/trace_processor/importers/proto/system_probes_module.h
+++ b/src/trace_processor/importers/proto/system_probes_module.h
@@ -20,43 +20,19 @@
 #include "perfetto/base/build_config.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/importers/proto/system_probes_parser.h"
-#include "src/trace_processor/timestamped_trace_piece.h"
 
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-class SystemProbesModule : public ProtoImporterModuleBase<PERFETTO_BUILDFLAG(
-                               PERFETTO_TP_SYSTEM_PROBES)> {
+class SystemProbesModule : public ProtoImporterModule {
  public:
-  explicit SystemProbesModule(TraceProcessorContext* context)
-      : ProtoImporterModuleBase(context), parser_(context) {}
+  explicit SystemProbesModule(TraceProcessorContext* context);
 
-  ModuleResult ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
-                           const TimestampedTracePiece& ttp) {
-    if (decoder.has_process_tree()) {
-      parser_.ParseProcessTree(decoder.process_tree());
-      return ModuleResult::Handled();
-    }
-
-    if (decoder.has_process_stats()) {
-      parser_.ParseProcessStats(ttp.timestamp, decoder.process_stats());
-      return ModuleResult::Handled();
-    }
-
-    if (decoder.has_sys_stats()) {
-      parser_.ParseSysStats(ttp.timestamp, decoder.sys_stats());
-      return ModuleResult::Handled();
-    }
-
-    if (decoder.has_system_info()) {
-      parser_.ParseSystemInfo(decoder.system_info());
-      return ModuleResult::Handled();
-    }
-
-    return ModuleResult::Ignored();
-  }
+  void ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
+                   const TimestampedTracePiece& ttp,
+                   uint32_t field_id) override;
 
  private:
   SystemProbesParser parser_;
diff --git a/src/trace_processor/importers/proto/track_event_module.cc b/src/trace_processor/importers/proto/track_event_module.cc
new file mode 100644
index 0000000..8e80a80
--- /dev/null
+++ b/src/trace_processor/importers/proto/track_event_module.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+#include "src/trace_processor/importers/proto/track_event_module.h"
+#include "perfetto/base/build_config.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
+
+#include "protos/perfetto/config/trace_config.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using perfetto::protos::pbzero::TracePacket;
+
+TrackEventModule::TrackEventModule(TraceProcessorContext* context)
+    : tokenizer_(context), parser_(context) {
+  RegisterForField(TracePacket::kTrackEventFieldNumber, context);
+  RegisterForField(TracePacket::kTrackDescriptorFieldNumber, context);
+  RegisterForField(TracePacket::kThreadDescriptorFieldNumber, context);
+  RegisterForField(TracePacket::kProcessDescriptorFieldNumber, context);
+}
+
+TrackEventModule::~TrackEventModule() = default;
+
+ModuleResult TrackEventModule::TokenizePacket(
+    const TracePacket::Decoder& decoder,
+    TraceBlobView* packet,
+    int64_t packet_timestamp,
+    PacketSequenceState* state,
+    uint32_t field_id) {
+  switch (field_id) {
+    case TracePacket::kTrackDescriptorFieldNumber:
+      tokenizer_.TokenizeTrackDescriptorPacket(decoder);
+      return ModuleResult::Handled();
+    case TracePacket::kTrackEventFieldNumber:
+      tokenizer_.TokenizeTrackEventPacket(state, decoder, packet,
+                                          packet_timestamp);
+      return ModuleResult::Handled();
+    case TracePacket::kThreadDescriptorFieldNumber:
+      // TODO(eseckler): Remove these once Chrome has switched fully over to
+      // TrackDescriptors.
+      tokenizer_.TokenizeThreadDescriptorPacket(state, decoder);
+      return ModuleResult::Handled();
+    case TracePacket::kProcessDescriptorFieldNumber:
+      tokenizer_.TokenizeProcessDescriptorPacket(decoder);
+      return ModuleResult::Handled();
+  }
+  return ModuleResult::Ignored();
+}
+
+void TrackEventModule::ParsePacket(const TracePacket::Decoder& decoder,
+                                   const TimestampedTracePiece& ttp,
+                                   uint32_t field_id) {
+  if (field_id == TracePacket::kTrackEventFieldNumber) {
+    parser_.ParseTrackEvent(
+        ttp.timestamp, ttp.thread_timestamp, ttp.thread_instruction_count,
+        ttp.packet_sequence_state, ttp.packet_sequence_state_generation,
+        decoder.track_event());
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/track_event_module.h b/src/trace_processor/importers/proto/track_event_module.h
index b58b8bc..de31d47 100644
--- a/src/trace_processor/importers/proto/track_event_module.h
+++ b/src/trace_processor/importers/proto/track_event_module.h
@@ -17,66 +17,31 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_MODULE_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_MODULE_H_
 
-#include "perfetto/base/build_config.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/importers/proto/track_event_parser.h"
 #include "src/trace_processor/importers/proto/track_event_tokenizer.h"
-#include "src/trace_processor/timestamped_trace_piece.h"
 
-#include "protos/perfetto/config/trace_config.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-class TrackEventModule : public ProtoImporterModuleBase</*IsEnabled=*/1> {
+class TrackEventModule : public ProtoImporterModule {
  public:
-  explicit TrackEventModule(TraceProcessorContext* context)
-      : ProtoImporterModuleBase(context),
-        tokenizer_(context),
-        parser_(context) {}
+  explicit TrackEventModule(TraceProcessorContext* context);
+
+  ~TrackEventModule() override;
 
   ModuleResult TokenizePacket(
       const protos::pbzero::TracePacket::Decoder& decoder,
       TraceBlobView* packet,
       int64_t packet_timestamp,
-      PacketSequenceState* state) {
-    if (decoder.has_track_descriptor()) {
-      tokenizer_.TokenizeTrackDescriptorPacket(decoder);
-      return ModuleResult::Handled();
-    }
+      PacketSequenceState* state,
+      uint32_t field_id) override;
 
-    if (decoder.has_track_event()) {
-      tokenizer_.TokenizeTrackEventPacket(state, decoder, packet,
-                                          packet_timestamp);
-      return ModuleResult::Handled();
-    }
-
-    // TODO(eseckler): Remove these once Chrome has switched fully over to
-    // TrackDescriptors.
-    if (decoder.has_thread_descriptor()) {
-      tokenizer_.TokenizeThreadDescriptorPacket(state, decoder);
-      return ModuleResult::Handled();
-    }
-    if (decoder.has_process_descriptor()) {
-      tokenizer_.TokenizeProcessDescriptorPacket(decoder);
-      return ModuleResult::Handled();
-    }
-
-    return ModuleResult::Ignored();
-  }
-
-  ModuleResult ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
-                           const TimestampedTracePiece& ttp) {
-    if (decoder.has_track_event()) {
-      parser_.ParseTrackEvent(
-          ttp.timestamp, ttp.thread_timestamp, ttp.thread_instruction_count,
-          ttp.packet_sequence_state, ttp.packet_sequence_state_generation,
-          decoder.track_event());
-      return ModuleResult::Handled();
-    }
-    return ModuleResult::Ignored();
-  }
+  void ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
+                   const TimestampedTracePiece& ttp,
+                   uint32_t field_id) override;
 
  private:
   TrackEventTokenizer tokenizer_;
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index f99a29d..66dc3e7 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -101,13 +101,29 @@
   switch (point.phase) {
     case 'B': {
       StringId name_id = context_->storage->InternString(point.name);
-      context_->slice_tracker->BeginAndroid(ts, pid, point.tgid, 0 /*cat_id*/,
-                                            name_id);
+      UniqueTid utid = context_->process_tracker->UpdateThread(pid, point.tgid);
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      context_->slice_tracker->Begin(ts, track_id, utid, RefType::kRefUtid,
+                                     0 /* cat */, name_id);
       break;
     }
 
     case 'E': {
-      context_->slice_tracker->EndAndroid(ts, pid, point.tgid);
+      // |point.tgid| can be 0 in older android versions where the end event
+      // would not contain the value.
+      UniqueTid utid;
+      if (point.tgid == 0) {
+        // If we haven't seen this thread before, there can't have been a Begin
+        // event for it so just ignore the event.
+        auto opt_utid = context_->process_tracker->GetThreadOrNull(pid);
+        if (!opt_utid)
+          break;
+        utid = *opt_utid;
+      } else {
+        utid = context_->process_tracker->UpdateThread(pid, point.tgid);
+      }
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      context_->slice_tracker->End(ts, track_id);
       break;
     }
 
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index f943b80..e09d0ae 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -190,7 +190,7 @@
   // Write the column descriptors.
   for (uint32_t col_idx = 0; col_idx < it.ColumnCount(); ++col_idx) {
     auto* descriptor = result->add_column_descriptors();
-    std::string col_name = it.GetColumName(col_idx);
+    std::string col_name = it.GetColumnName(col_idx);
     descriptor->set_name(col_name.data(), col_name.size());
     descriptor->set_type(col_types[col_idx]);
   }
diff --git a/src/trace_processor/slice_table.cc b/src/trace_processor/slice_table.cc
deleted file mode 100644
index 2570674..0000000
--- a/src/trace_processor/slice_table.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * 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.
- */
-
-#include "src/trace_processor/slice_table.h"
-
-#include "src/trace_processor/storage_columns.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-SliceTable::SliceTable(sqlite3*, const TraceStorage* storage)
-    : storage_(storage) {}
-
-void SliceTable::RegisterTable(sqlite3* db, const TraceStorage* storage) {
-  SqliteTable::Register<SliceTable>(db, storage, "internal_slice");
-}
-
-StorageSchema SliceTable::CreateStorageSchema() {
-  const auto& slices = storage_->nestable_slices();
-  return StorageSchema::Builder()
-      .AddGenericNumericColumn("slice_id", RowAccessor())
-      .AddOrderedNumericColumn("ts", &slices.start_ns())
-      .AddNumericColumn("dur", &slices.durations())
-      .AddNumericColumn("track_id", &slices.track_id())
-      .AddNumericColumn("ref", &slices.refs())
-      .AddStringColumn("ref_type", &slices.types(), &GetRefTypeStringMap())
-      .AddStringColumn("category", &slices.categories(),
-                       &storage_->string_pool())
-      .AddStringColumn("name", &slices.names(), &storage_->string_pool())
-      .AddNumericColumn("depth", &slices.depths())
-      .AddNumericColumn("stack_id", &slices.stack_ids())
-      .AddNumericColumn("parent_stack_id", &slices.parent_stack_ids())
-      .AddNumericColumn("arg_set_id", &slices.arg_set_ids())
-      .Build({"slice_id"});
-}
-
-uint32_t SliceTable::RowCount() {
-  return static_cast<uint32_t>(storage_->nestable_slices().slice_count());
-}
-
-int SliceTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) {
-  info->estimated_cost = EstimateCost(qc);
-  info->sqlite_omit_order_by = true;
-
-  // Only the string columns are handled by SQLite
-  size_t name_index = schema().ColumnIndexFromName("name");
-  size_t cat_index = schema().ColumnIndexFromName("category");
-  size_t ref_type_index = schema().ColumnIndexFromName("ref_type");
-  for (size_t i = 0; i < qc.constraints().size(); i++) {
-    auto col = static_cast<size_t>(qc.constraints()[i].column);
-    info->sqlite_omit_constraint[i] =
-        col != name_index && col != cat_index && col != ref_type_index;
-  }
-  return SQLITE_OK;
-}
-
-uint32_t SliceTable::EstimateCost(const QueryConstraints& qc) {
-  // slice_id is row index, so we can filter efficiently for equality.
-  if (HasEqConstraint(qc, "slice_id"))
-    return 1;
-
-  auto eq_ts = HasEqConstraint(qc, "ts");
-  auto eq_ref = HasEqConstraint(qc, "ref");
-  auto eq_ref_type = HasEqConstraint(qc, "ref_type");
-  auto eq_depth = HasEqConstraint(qc, "depth");
-  auto eq_name = HasEqConstraint(qc, "name");
-
-  // ref + ref_type + ts + depth is a unique key. others are estimates.
-  if (eq_ref && eq_ref_type && eq_ts && eq_depth)
-    return 1;
-  else if (eq_ref && eq_ref_type && eq_ts)
-    return 10;
-  else if (eq_ts && eq_name)
-    return 10;
-  else if (eq_ts || eq_name)
-    return 100;
-  return RowCount();
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/slice_table.h b/src/trace_processor/slice_table.h
deleted file mode 100644
index 5276900..0000000
--- a/src/trace_processor/slice_table.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * 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.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_SLICE_TABLE_H_
-#define SRC_TRACE_PROCESSOR_SLICE_TABLE_H_
-
-#include "src/trace_processor/storage_table.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-class QueryConstraints;
-class TraceStorage;
-
-// A virtual table that allows to query slices coming from userspace events
-// such as chromium TRACE_EVENT macros. Conversely to "shced" slices, these
-// slices can be nested and form stacks.
-// The current implementation of this table is extremely simple and not
-// particularly efficient, as it delegates all the sorting and filtering to
-// the SQLite query engine.
-class SliceTable : public StorageTable {
- public:
-  SliceTable(sqlite3*, const TraceStorage* storage);
-
-  static void RegisterTable(sqlite3* db, const TraceStorage* storage);
-
-  // StorageTable implementation.
-  StorageSchema CreateStorageSchema() override;
-  uint32_t RowCount() override;
-  int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
-
- private:
-  uint32_t EstimateCost(const QueryConstraints&);
-
-  std::vector<const char*> ref_types_;
-  const TraceStorage* const storage_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_SLICE_TABLE_H_
diff --git a/src/trace_processor/slice_tracker.cc b/src/trace_processor/slice_tracker.cc
index 22c85d9..39d4058 100644
--- a/src/trace_processor/slice_tracker.cc
+++ b/src/trace_processor/slice_tracker.cc
@@ -38,19 +38,6 @@
 
 SliceTracker::~SliceTracker() = default;
 
-base::Optional<uint32_t> SliceTracker::BeginAndroid(int64_t timestamp,
-                                                    uint32_t ftrace_tid,
-                                                    uint32_t atrace_tgid,
-                                                    StringId category,
-                                                    StringId name) {
-  UniqueTid utid =
-      context_->process_tracker->UpdateThread(ftrace_tid, atrace_tgid);
-  ftrace_to_atrace_tgid_[ftrace_tid] = atrace_tgid;
-
-  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
-  return Begin(timestamp, track_id, utid, RefType::kRefUtid, category, name);
-}
-
 base::Optional<uint32_t> SliceTracker::Begin(int64_t timestamp,
                                              TrackId track_id,
                                              int64_t ref,
@@ -101,7 +88,7 @@
     StringId name,
     SetArgsCallback args_callback) {
   auto* stack = &stacks_[track_id];
-  auto* slices = context_->storage->mutable_nestable_slices();
+  auto* slices = context_->storage->mutable_slice_table();
 
   const uint8_t depth = static_cast<uint8_t>(stack->size());
   if (depth >= std::numeric_limits<uint8_t>::max()) {
@@ -109,10 +96,14 @@
     return base::nullopt;
   }
   int64_t parent_stack_id =
-      depth == 0 ? 0 : slices->stack_ids()[stack->back().first];
-  uint32_t slice_idx =
-      slices->AddSlice(timestamp, duration, track_id, ref, ref_type, category,
-                       name, depth, 0, parent_stack_id);
+      depth == 0 ? 0 : slices->stack_id()[stack->back().first];
+
+  StringId ref_type_id = context_->storage->InternString(
+      GetRefTypeStringMap()[static_cast<uint32_t>(ref_type)]);
+
+  tables::SliceTable::Row row(timestamp, duration, track_id, ref, ref_type_id,
+                              category, name, depth, 0, parent_stack_id);
+  uint32_t slice_idx = slices->Insert(row);
   stack->emplace_back(std::make_pair(slice_idx, ArgsTracker(context_)));
 
   if (args_callback) {
@@ -120,39 +111,10 @@
         &stack->back().second,
         TraceStorage::CreateRowId(TableId::kNestableSlices, slice_idx));
   }
-  slices->set_stack_id(slice_idx, GetStackHash(*stack));
+  slices->mutable_stack_id()->Set(slice_idx, GetStackHash(*stack));
   return slice_idx;
 }
 
-base::Optional<uint32_t> SliceTracker::EndAndroid(int64_t timestamp,
-                                                  uint32_t ftrace_tid,
-                                                  uint32_t atrace_tgid) {
-  auto map_tgid_it = ftrace_to_atrace_tgid_.find(ftrace_tid);
-  bool has_map_tgid = map_tgid_it != ftrace_to_atrace_tgid_.end();
-
-  // atrace_tgid can be 0 in older android versions where the end event would
-  // not contain the value.
-  if (atrace_tgid == 0) {
-    if (!has_map_tgid) {
-      // This is possible if we start tracing after a begin slice.
-      PERFETTO_DLOG("Unknown tgid for ftrace tid %u", ftrace_tid);
-      return base::nullopt;
-    }
-  } else {
-    if (has_map_tgid && atrace_tgid != map_tgid_it->second) {
-      PERFETTO_DLOG("Mismatched atrace pid %u and looked up pid %u",
-                    atrace_tgid, map_tgid_it->second);
-      context_->storage->IncrementStats(stats::atrace_tgid_mismatch);
-    }
-  }
-
-  uint32_t actual_tgid = atrace_tgid == 0 ? map_tgid_it->second : atrace_tgid;
-  UniqueTid utid =
-      context_->process_tracker->UpdateThread(ftrace_tid, actual_tgid);
-  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
-  return End(timestamp, track_id);
-}
-
 // Returns the first incomplete slice in the stack with matching name and
 // category. We assume null category/name matches everything. Returns
 // nullopt if no matching slice is found.
@@ -160,16 +122,16 @@
     SlicesStack& stack,
     StringId name,
     StringId category) {
-  auto* slices = context_->storage->mutable_nestable_slices();
+  auto* slices = context_->storage->mutable_slice_table();
   for (int i = static_cast<int>(stack.size()) - 1; i >= 0; i--) {
     uint32_t slice_idx = stack[static_cast<size_t>(i)].first;
-    if (slices->durations()[slice_idx] != kPendingDuration)
+    if (slices->dur()[slice_idx] != kPendingDuration)
       continue;
-    const StringId& other_category = slices->categories()[slice_idx];
+    const StringId& other_category = slices->category()[slice_idx];
     if (!category.is_null() && !other_category.is_null() &&
         category != other_category)
       continue;
-    const StringId& other_name = slices->names()[slice_idx];
+    const StringId& other_name = slices->name()[slice_idx];
     if (!name.is_null() && !other_name.is_null() && name != other_name)
       continue;
     return static_cast<size_t>(i);
@@ -195,7 +157,7 @@
   if (stack.empty())
     return base::nullopt;
 
-  auto* slices = context_->storage->mutable_nestable_slices();
+  auto* slices = context_->storage->mutable_slice_table();
   base::Optional<size_t> stack_idx =
       MatchingIncompleteSliceIndex(stack, name, category);
 
@@ -220,8 +182,8 @@
 
   uint32_t slice_idx = stack[stack_idx.value()].first;
 
-  PERFETTO_DCHECK(slices->durations()[slice_idx] == kPendingDuration);
-  slices->set_duration(slice_idx, timestamp - slices->start_ns()[slice_idx]);
+  PERFETTO_DCHECK(slices->dur()[slice_idx] == kPendingDuration);
+  slices->mutable_dur()->Set(slice_idx, timestamp - slices->ts()[slice_idx]);
 
   if (args_callback) {
     args_callback(
@@ -252,13 +214,13 @@
 }
 
 void SliceTracker::MaybeCloseStack(int64_t ts, SlicesStack* stack) {
-  const auto& slices = context_->storage->nestable_slices();
+  const auto& slices = context_->storage->slice_table();
   bool pending_dur_descendent = false;
   for (int i = static_cast<int>(stack->size()) - 1; i >= 0; i--) {
     uint32_t slice_idx = (*stack)[static_cast<size_t>(i)].first;
 
-    int64_t start_ts = slices.start_ns()[slice_idx];
-    int64_t dur = slices.durations()[slice_idx];
+    int64_t start_ts = slices.ts()[slice_idx];
+    int64_t dur = slices.dur()[slice_idx];
     int64_t end_ts = start_ts + dur;
     if (dur == kPendingDuration) {
       pending_dur_descendent = true;
@@ -290,19 +252,15 @@
 int64_t SliceTracker::GetStackHash(const SlicesStack& stack) {
   PERFETTO_DCHECK(!stack.empty());
 
-  const auto& slices = context_->storage->nestable_slices();
+  const auto& slices = context_->storage->slice_table();
 
-  std::string s;
-  s.reserve(stack.size() * sizeof(uint64_t) * 2);
+  base::Hash hash;
   for (size_t i = 0; i < stack.size(); i++) {
     uint32_t slice_idx = stack[i].first;
-    s.append(reinterpret_cast<const char*>(&slices.categories()[slice_idx]),
-             sizeof(slices.categories()[slice_idx]));
-    s.append(reinterpret_cast<const char*>(&slices.names()[slice_idx]),
-             sizeof(slices.names()[slice_idx]));
+    hash.Update(slices.category()[slice_idx]);
+    hash.Update(slices.name()[slice_idx]);
   }
-  constexpr uint64_t kMask = uint64_t(-1) >> 1;
-  return static_cast<int64_t>((std::hash<std::string>{}(s)) & kMask);
+  return static_cast<int64_t>(hash.digest());
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/slice_tracker.h b/src/trace_processor/slice_tracker.h
index 726c25f..2347aea 100644
--- a/src/trace_processor/slice_tracker.h
+++ b/src/trace_processor/slice_tracker.h
@@ -34,12 +34,6 @@
   explicit SliceTracker(TraceProcessorContext*);
   virtual ~SliceTracker();
 
-  base::Optional<uint32_t> BeginAndroid(int64_t timestamp,
-                                        uint32_t ftrace_tid,
-                                        uint32_t atrace_tgid,
-                                        StringId category,
-                                        StringId name);
-
   // virtual for testing
   virtual base::Optional<uint32_t> Begin(
       int64_t timestamp,
@@ -61,10 +55,6 @@
       int64_t duration,
       SetArgsCallback args_callback = SetArgsCallback());
 
-  base::Optional<uint32_t> EndAndroid(int64_t timestamp,
-                                      uint32_t ftrace_tid,
-                                      uint32_t atrace_tgid);
-
   // virtual for testing
   virtual base::Optional<uint32_t> End(
       int64_t timestamp,
@@ -101,7 +91,6 @@
 
   TraceProcessorContext* const context_;
   StackMap stacks_;
-  std::unordered_map<uint32_t, uint32_t> ftrace_to_atrace_tgid_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/slice_tracker_unittest.cc b/src/trace_processor/slice_tracker_unittest.cc
index 33f0d5e..3b709ce 100644
--- a/src/trace_processor/slice_tracker_unittest.cc
+++ b/src/trace_processor/slice_tracker_unittest.cc
@@ -42,10 +42,10 @@
   *os << "SliceInfo{" << info.start << ", " << info.duration << "}";
 }
 
-std::vector<SliceInfo> ToSliceInfo(const TraceStorage::NestableSlices& slices) {
+std::vector<SliceInfo> ToSliceInfo(const tables::SliceTable& slices) {
   std::vector<SliceInfo> infos;
-  for (size_t i = 0; i < slices.slice_count(); i++) {
-    infos.emplace_back(SliceInfo{slices.start_ns()[i], slices.durations()[i]});
+  for (uint32_t i = 0; i < slices.size(); i++) {
+    infos.emplace_back(SliceInfo{slices.ts()[i], slices.dur()[i]});
   }
   return infos;
 }
@@ -60,17 +60,18 @@
                 1 /*name*/);
   tracker.End(10 /*ts*/, track, 0 /*cat*/, 1 /*name*/);
 
-  auto slices = context.storage->nestable_slices();
-  EXPECT_EQ(slices.slice_count(), 1u);
-  EXPECT_EQ(slices.start_ns()[0], 2);
-  EXPECT_EQ(slices.durations()[0], 8);
+  const auto& slices = context.storage->slice_table();
+  EXPECT_EQ(slices.size(), 1u);
+  EXPECT_EQ(slices.ts()[0], 2);
+  EXPECT_EQ(slices.dur()[0], 8);
   EXPECT_EQ(slices.track_id()[0], track);
-  EXPECT_EQ(slices.categories()[0], 0u);
-  EXPECT_EQ(slices.names()[0], 1u);
-  EXPECT_EQ(slices.refs()[0], 42);
-  EXPECT_EQ(slices.types()[0], RefType::kRefUtid);
-  EXPECT_EQ(slices.depths()[0], 0);
-  EXPECT_EQ(slices.arg_set_ids()[0], kInvalidArgSetId);
+  EXPECT_EQ(slices.category()[0], 0u);
+  EXPECT_EQ(slices.name()[0], 1u);
+  EXPECT_EQ(slices.ref()[0], 42);
+  EXPECT_EQ(slices.ref_type().GetString(0),
+            GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  EXPECT_EQ(slices.depth()[0], 0u);
+  EXPECT_EQ(slices.arg_set_id()[0], kInvalidArgSetId);
 }
 
 TEST(SliceTrackerTest, OneSliceWithArgs) {
@@ -90,17 +91,18 @@
                                      /*value=*/Variadic::Integer(20));
               });
 
-  auto slices = context.storage->nestable_slices();
-  EXPECT_EQ(slices.slice_count(), 1u);
-  EXPECT_EQ(slices.start_ns()[0], 2);
-  EXPECT_EQ(slices.durations()[0], 8);
+  const auto& slices = context.storage->slice_table();
+  EXPECT_EQ(slices.size(), 1u);
+  EXPECT_EQ(slices.ts()[0], 2);
+  EXPECT_EQ(slices.dur()[0], 8);
   EXPECT_EQ(slices.track_id()[0], track);
-  EXPECT_EQ(slices.categories()[0], 0u);
-  EXPECT_EQ(slices.names()[0], 1u);
-  EXPECT_EQ(slices.refs()[0], 42);
-  EXPECT_EQ(slices.types()[0], RefType::kRefUtid);
-  EXPECT_EQ(slices.depths()[0], 0);
-  auto set_id = slices.arg_set_ids()[0];
+  EXPECT_EQ(slices.category()[0], 0u);
+  EXPECT_EQ(slices.name()[0], 1u);
+  EXPECT_EQ(slices.ref()[0], 42);
+  EXPECT_EQ(slices.ref_type().GetString(0),
+            GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  EXPECT_EQ(slices.depth()[0], 0u);
+  auto set_id = slices.arg_set_id()[0];
 
   auto args = context.storage->args();
   EXPECT_EQ(args.set_ids()[0], set_id);
@@ -126,32 +128,34 @@
   tracker.End(5 /*ts*/, track);
   tracker.End(10 /*ts*/, track);
 
-  auto slices = context.storage->nestable_slices();
+  const auto& slices = context.storage->slice_table();
 
-  EXPECT_EQ(slices.slice_count(), 2u);
+  EXPECT_EQ(slices.size(), 2u);
 
-  size_t idx = 0;
-  EXPECT_EQ(slices.start_ns()[idx], 2);
-  EXPECT_EQ(slices.durations()[idx], 8);
+  uint32_t idx = 0;
+  EXPECT_EQ(slices.ts()[idx], 2);
+  EXPECT_EQ(slices.dur()[idx], 8);
   EXPECT_EQ(slices.track_id()[idx], track);
-  EXPECT_EQ(slices.categories()[idx], 0u);
-  EXPECT_EQ(slices.names()[idx], 1u);
-  EXPECT_EQ(slices.refs()[idx], 42);
-  EXPECT_EQ(slices.types()[idx], RefType::kRefUtid);
-  EXPECT_EQ(slices.depths()[idx++], 0);
+  EXPECT_EQ(slices.category()[idx], 0u);
+  EXPECT_EQ(slices.name()[idx], 1u);
+  EXPECT_EQ(slices.ref()[idx], 42);
+  EXPECT_EQ(slices.ref_type().GetString(idx),
+            GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  EXPECT_EQ(slices.depth()[idx++], 0u);
 
-  EXPECT_EQ(slices.start_ns()[idx], 3);
-  EXPECT_EQ(slices.durations()[idx], 2);
+  EXPECT_EQ(slices.ts()[idx], 3);
+  EXPECT_EQ(slices.dur()[idx], 2);
   EXPECT_EQ(slices.track_id()[idx], track);
-  EXPECT_EQ(slices.categories()[idx], 0u);
-  EXPECT_EQ(slices.names()[idx], 2u);
-  EXPECT_EQ(slices.refs()[idx], 42);
-  EXPECT_EQ(slices.types()[idx], RefType::kRefUtid);
-  EXPECT_EQ(slices.depths()[idx], 1);
+  EXPECT_EQ(slices.category()[idx], 0u);
+  EXPECT_EQ(slices.name()[idx], 2u);
+  EXPECT_EQ(slices.ref()[idx], 42);
+  EXPECT_EQ(slices.ref_type().GetString(idx),
+            GetRefTypeStringMap()[static_cast<uint32_t>(RefType::kRefUtid)]);
+  EXPECT_EQ(slices.depth()[idx], 1u);
 
-  EXPECT_EQ(slices.parent_stack_ids()[0], 0);
-  EXPECT_EQ(slices.stack_ids()[0], slices.parent_stack_ids()[1]);
-  EXPECT_NE(slices.stack_ids()[1], 0);
+  EXPECT_EQ(slices.parent_stack_id()[0], 0);
+  EXPECT_EQ(slices.stack_id()[0], slices.parent_stack_id()[1]);
+  EXPECT_NE(slices.stack_id()[1], 0);
 }
 
 TEST(SliceTrackerTest, Scoped) {
@@ -166,7 +170,7 @@
   tracker.End(9 /*ts*/, track);
   tracker.End(10 /*ts*/, track);
 
-  auto slices = ToSliceInfo(context.storage->nestable_slices());
+  auto slices = ToSliceInfo(context.storage->slice_table());
   EXPECT_THAT(slices,
               ElementsAre(SliceInfo{0, 10}, SliceInfo{1, 8}, SliceInfo{2, 6}));
 }
@@ -183,7 +187,7 @@
   tracker.End(4 /*ts*/, track, 0 /*cat*/, 2 /*name*/);
   tracker.End(5 /*ts*/, track, 5 /*cat*/, 1 /*name*/);
 
-  auto slices = ToSliceInfo(context.storage->nestable_slices());
+  auto slices = ToSliceInfo(context.storage->slice_table());
   EXPECT_THAT(slices, ElementsAre(SliceInfo{2, 3}));
 }
 
@@ -205,7 +209,7 @@
   tracker.Scoped(13 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
                  1 /*name*/, 1 /* dur */);
 
-  auto slices = ToSliceInfo(context.storage->nestable_slices());
+  auto slices = ToSliceInfo(context.storage->slice_table());
   EXPECT_THAT(slices, ElementsAre(SliceInfo{2, 10}, SliceInfo{2, 0},
                                   SliceInfo{12, 1}, SliceInfo{13, 1}));
 }
@@ -223,16 +227,16 @@
   tracker.End(10 /*ts*/, track_a);
   tracker.FlushPendingSlices();
 
-  auto slices = ToSliceInfo(context.storage->nestable_slices());
+  auto slices = ToSliceInfo(context.storage->slice_table());
   EXPECT_THAT(slices,
               ElementsAre(SliceInfo{0, 10}, SliceInfo{2, 6}, SliceInfo{3, 4}));
 
-  EXPECT_EQ(context.storage->nestable_slices().track_id()[0], track_a);
-  EXPECT_EQ(context.storage->nestable_slices().track_id()[1], track_b);
-  EXPECT_EQ(context.storage->nestable_slices().track_id()[2], track_b);
-  EXPECT_EQ(context.storage->nestable_slices().depths()[0], 0);
-  EXPECT_EQ(context.storage->nestable_slices().depths()[1], 0);
-  EXPECT_EQ(context.storage->nestable_slices().depths()[2], 1);
+  EXPECT_EQ(context.storage->slice_table().track_id()[0], track_a);
+  EXPECT_EQ(context.storage->slice_table().track_id()[1], track_b);
+  EXPECT_EQ(context.storage->slice_table().track_id()[2], track_b);
+  EXPECT_EQ(context.storage->slice_table().depth()[0], 0u);
+  EXPECT_EQ(context.storage->slice_table().depth()[1], 0u);
+  EXPECT_EQ(context.storage->slice_table().depth()[2], 1u);
 }
 
 TEST(SliceTrackerTest, EndEventOutOfOrder) {
@@ -267,15 +271,15 @@
 
   tracker.FlushPendingSlices();
 
-  auto slices = ToSliceInfo(context.storage->nestable_slices());
+  auto slices = ToSliceInfo(context.storage->slice_table());
   EXPECT_THAT(slices, ElementsAre(SliceInfo{50, 100}, SliceInfo{100, 400},
                                   SliceInfo{450, 100}, SliceInfo{800, 200},
                                   SliceInfo{1100, -1}, SliceInfo{1300, 0 - 1}));
 
-  EXPECT_EQ(context.storage->nestable_slices().depths()[0], 0);
-  EXPECT_EQ(context.storage->nestable_slices().depths()[1], 1);
-  EXPECT_EQ(context.storage->nestable_slices().depths()[2], 2);
-  EXPECT_EQ(context.storage->nestable_slices().depths()[3], 0);
+  EXPECT_EQ(context.storage->slice_table().depth()[0], 0u);
+  EXPECT_EQ(context.storage->slice_table().depth()[1], 1u);
+  EXPECT_EQ(context.storage->slice_table().depth()[2], 2u);
+  EXPECT_EQ(context.storage->slice_table().depth()[3], 0u);
 }
 
 }  // namespace
diff --git a/src/trace_processor/stats.h b/src/trace_processor/stats.h
index 97d968b..b64ad6b 100644
--- a/src/trace_processor/stats.h
+++ b/src/trace_processor/stats.h
@@ -29,7 +29,6 @@
   F(android_log_num_failed,                   kSingle,  kError,    kTrace),    \
   F(android_log_num_skipped,                  kSingle,  kError,    kTrace),    \
   F(android_log_num_total,                    kSingle,  kInfo,     kTrace),    \
-  F(atrace_tgid_mismatch,                     kSingle,  kError,    kTrace),    \
   F(counter_events_out_of_order,              kSingle,  kError,    kAnalysis), \
   F(ftrace_bundle_tokenizer_errors,           kSingle,  kError,    kAnalysis), \
   F(ftrace_cpu_bytes_read_begin,              kIndexed, kInfo,     kTrace),    \
diff --git a/src/trace_processor/storage_columns.h b/src/trace_processor/storage_columns.h
index a10f4ba..20678cd 100644
--- a/src/trace_processor/storage_columns.h
+++ b/src/trace_processor/storage_columns.h
@@ -327,7 +327,7 @@
 class StringVectorAccessor : public Accessor<NullTermStringView> {
  public:
   StringVectorAccessor(const std::deque<Id>* deque,
-                       const std::vector<const char*>* string_map)
+                       const std::vector<NullTermStringView>* string_map)
       : deque_(deque), string_map_(string_map) {}
   ~StringVectorAccessor() override = default;
 
@@ -336,13 +336,12 @@
   }
 
   NullTermStringView Get(uint32_t idx) const override {
-    const char* ptr = (*string_map_)[static_cast<size_t>((*deque_)[idx])];
-    return ptr ? NullTermStringView(ptr) : NullTermStringView();
+    return (*string_map_)[static_cast<size_t>((*deque_)[idx])];
   }
 
  private:
   const std::deque<Id>* deque_;
-  const std::vector<const char*>* string_map_;
+  const std::vector<NullTermStringView>* string_map_;
 };
 
 // An accessor implementation for numeric columns which uses a deque as the
diff --git a/src/trace_processor/storage_schema.h b/src/trace_processor/storage_schema.h
index fe7955c..db032bc 100644
--- a/src/trace_processor/storage_schema.h
+++ b/src/trace_processor/storage_schema.h
@@ -76,9 +76,10 @@
     }
 
     template <class Id>
-    Builder& AddStringColumn(std::string column_name,
-                             const std::deque<Id>* ids,
-                             const std::vector<const char*>* string_map) {
+    Builder& AddStringColumn(
+        std::string column_name,
+        const std::deque<Id>* ids,
+        const std::vector<NullTermStringView>* string_map) {
       StringVectorAccessor<Id> accessor(ids, string_map);
       columns_.emplace_back(
           new StringColumn<decltype(accessor)>(column_name, accessor));
diff --git a/src/trace_processor/tables/macros_unittest.cc b/src/trace_processor/tables/macros_unittest.cc
index e1e06b8..46e03da 100644
--- a/src/trace_processor/tables/macros_unittest.cc
+++ b/src/trace_processor/tables/macros_unittest.cc
@@ -189,6 +189,42 @@
   ASSERT_EQ(dur->Get(1).long_value, 200);
 }
 
+TEST_F(TableMacrosUnittest, NullableLongCompareWithDouble) {
+  slice_.Insert({});
+
+  TestSliceTable::Row row;
+  row.dur = 100;
+  slice_.Insert(row);
+
+  row.dur = std::numeric_limits<int64_t>::max();
+  slice_.Insert(row);
+
+  row.dur = std::numeric_limits<int64_t>::min();
+  slice_.Insert(row);
+
+  Table out = slice_.Filter({slice_.dur().eq_value(SqlValue::Double(100.0))});
+  const Column* dur = out.GetColumnByName("dur");
+  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(dur->Get(0).long_value, 100);
+
+  out = slice_.Filter({slice_.dur().le_value(SqlValue::Double(99.9999))});
+  dur = out.GetColumnByName("dur");
+  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(dur->Get(0).long_value, std::numeric_limits<int64_t>::min());
+
+  out = slice_.Filter({slice_.dur().ge_value(SqlValue::Double(99.9999))});
+  dur = out.GetColumnByName("dur");
+  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(dur->Get(0).long_value, 100);
+  ASSERT_EQ(dur->Get(1).long_value, std::numeric_limits<int64_t>::max());
+
+  out = slice_.Filter({slice_.dur().eq_value(
+      SqlValue::Double(std::numeric_limits<int64_t>::min()))});
+  dur = out.GetColumnByName("dur");
+  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(dur->Get(0).long_value, std::numeric_limits<int64_t>::min());
+}
+
 TEST_F(TableMacrosUnittest, NullableLongCompareWrongType) {
   slice_.Insert({});
 
@@ -209,9 +245,101 @@
 
   out = slice_.Filter({slice_.dur().eq_value(SqlValue::String("100"))});
   ASSERT_EQ(out.size(), 0u);
+}
 
-  out = slice_.Filter({slice_.dur().eq_value(SqlValue::Double(100.0))});
-  ASSERT_EQ(out.size(), 0u);
+TEST_F(TableMacrosUnittest, NullableDoubleComparision) {
+  counter_.Insert({});
+
+  TestCounterTable::Row row;
+  row.value = 100.0;
+  counter_.Insert(row);
+
+  row.value = 101.0;
+  counter_.Insert(row);
+
+  row.value = 200.0;
+  counter_.Insert(row);
+
+  counter_.Insert({});
+
+  Table out = counter_.Filter({counter_.value().is_null()});
+  const auto* value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(value->Get(0).type, SqlValue::kNull);
+  ASSERT_EQ(value->Get(1).type, SqlValue::kNull);
+
+  out = counter_.Filter({counter_.value().is_not_null()});
+  value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 3u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
+  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 101);
+  ASSERT_DOUBLE_EQ(value->Get(2).double_value, 200);
+
+  out = counter_.Filter({counter_.value().lt(101)});
+  value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 1u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
+
+  out = counter_.Filter({counter_.value().eq(101)});
+  value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 1u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
+
+  out = counter_.Filter({counter_.value().gt(101)});
+  value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 1u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 200);
+
+  out = counter_.Filter({counter_.value().ne(100)});
+  value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 2u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
+  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 200);
+
+  out = counter_.Filter({counter_.value().le(101)});
+  value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 2u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
+  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 101);
+
+  out = counter_.Filter({counter_.value().ge(101)});
+  value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 2u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
+  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 200);
+}
+
+TEST_F(TableMacrosUnittest, NullableDoubleCompareWithLong) {
+  counter_.Insert({});
+
+  TestCounterTable::Row row;
+  row.value = 100.0;
+  counter_.Insert(row);
+
+  row.value = 99.9999;
+  counter_.Insert(row);
+
+  row.value = std::numeric_limits<int64_t>::min();
+  counter_.Insert(row);
+
+  Table out = counter_.Filter({counter_.value().eq_value(SqlValue::Long(100))});
+  const Column* value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 1u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100.0);
+
+  out = counter_.Filter({counter_.value().lt_value(SqlValue::Long(100))});
+  value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 2u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 99.9999);
+  ASSERT_DOUBLE_EQ(value->Get(1).double_value,
+                   std::numeric_limits<int64_t>::min());
+
+  out = counter_.Filter({counter_.value().eq_value(
+      SqlValue::Long(std::numeric_limits<int64_t>::min()))});
+  value = out.GetColumnByName("value");
+  ASSERT_EQ(out.size(), 1u);
+  ASSERT_DOUBLE_EQ(value->Get(0).double_value,
+                   std::numeric_limits<int64_t>::min());
 }
 
 TEST_F(TableMacrosUnittest, StringComparision) {
@@ -291,68 +419,6 @@
   ASSERT_STREQ(end_state->Get(0).string_value, "D");
 }
 
-TEST_F(TableMacrosUnittest, NullableDoubleComparision) {
-  counter_.Insert({});
-
-  TestCounterTable::Row row;
-  row.value = 100.0;
-  counter_.Insert(row);
-
-  row.value = 101.0;
-  counter_.Insert(row);
-
-  row.value = 200.0;
-  counter_.Insert(row);
-
-  counter_.Insert({});
-
-  Table out = counter_.Filter({counter_.value().is_null()});
-  const auto* value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 2u);
-  ASSERT_EQ(value->Get(0).type, SqlValue::kNull);
-  ASSERT_EQ(value->Get(1).type, SqlValue::kNull);
-
-  out = counter_.Filter({counter_.value().is_not_null()});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 3u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
-  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 101);
-  ASSERT_DOUBLE_EQ(value->Get(2).double_value, 200);
-
-  out = counter_.Filter({counter_.value().lt(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 1u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
-
-  out = counter_.Filter({counter_.value().eq(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 1u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
-
-  out = counter_.Filter({counter_.value().gt(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 1u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 200);
-
-  out = counter_.Filter({counter_.value().ne(100)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 2u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
-  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 200);
-
-  out = counter_.Filter({counter_.value().le(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 2u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
-  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 101);
-
-  out = counter_.Filter({counter_.value().ge(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 2u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
-  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 200);
-}
-
 TEST_F(TableMacrosUnittest, Sort) {
   ASSERT_TRUE(event_.ts().IsSorted());
 
diff --git a/src/trace_processor/tables/slice_tables.h b/src/trace_processor/tables/slice_tables.h
index 06ca534..d4485fb 100644
--- a/src/trace_processor/tables/slice_tables.h
+++ b/src/trace_processor/tables/slice_tables.h
@@ -23,6 +23,23 @@
 namespace trace_processor {
 namespace tables {
 
+#define PERFETTO_TP_SLICE_TABLE_DEF(NAME, PARENT, C) \
+  NAME(SliceTable, "internal_slice")                 \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                  \
+  C(int64_t, ts, Column::Flag::kSorted)              \
+  C(int64_t, dur)                                    \
+  C(uint32_t, track_id)                              \
+  C(int64_t, ref)                                    \
+  C(StringPool::Id, ref_type)                        \
+  C(StringPool::Id, category)                        \
+  C(StringPool::Id, name)                            \
+  C(uint32_t, depth)                                 \
+  C(int64_t, stack_id)                               \
+  C(int64_t, parent_stack_id)                        \
+  C(uint32_t, arg_set_id)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_SLICE_TABLE_DEF);
+
 #define PERFETTO_TP_GPU_SLICES_DEF(NAME, PARENT, C) \
   NAME(GpuSliceTable, "internal_gpu_slice")         \
   PERFETTO_TP_ROOT_TABLE(PARENT, C)                 \
diff --git a/src/trace_processor/trace_processor.cc b/src/trace_processor/trace_processor.cc
index 0a4942a..9be89e5 100644
--- a/src/trace_processor/trace_processor.cc
+++ b/src/trace_processor/trace_processor.cc
@@ -47,7 +47,7 @@
   return iterator_->Get(col);
 }
 
-std::string TraceProcessor::Iterator::GetColumName(uint32_t col) {
+std::string TraceProcessor::Iterator::GetColumnName(uint32_t col) {
   return iterator_->GetColumnName(col);
 }
 
diff --git a/src/trace_processor/trace_processor_context.h b/src/trace_processor/trace_processor_context.h
index 784b130..7229c15 100644
--- a/src/trace_processor/trace_processor_context.h
+++ b/src/trace_processor/trace_processor_context.h
@@ -26,27 +26,22 @@
 namespace perfetto {
 namespace trace_processor {
 
-class AndroidProbesModule;
 class ArgsTracker;
 class BinderTracker;
 class ChunkedTraceReader;
 class ClockTracker;
 class EventTracker;
 class FtraceModule;
-class GraphicsEventModule;
-class HeapGraphModule;
 class HeapGraphTracker;
 class HeapProfileTracker;
 class ProcessTracker;
 class SchedEventTracker;
 class SliceTracker;
 class SyscallTracker;
-class SystemProbesModule;
 class SystraceParser;
 class TraceParser;
 class TraceSorter;
 class TraceStorage;
-class TrackEventModule;
 class TrackTracker;
 class VulkanMemoryTracker;
 
@@ -75,17 +70,11 @@
   std::unique_ptr<VulkanMemoryTracker> vulkan_memory_tracker;
   std::unique_ptr<BinderTracker> binder_tracker;
 
-  std::unique_ptr<ProtoImporterModule<FtraceModule>> ftrace_module;
-  std::unique_ptr<ProtoImporterModule<TrackEventModule>> track_event_module;
-  std::unique_ptr<ProtoImporterModule<SystemProbesModule>> system_probes_module;
-  std::unique_ptr<ProtoImporterModule<AndroidProbesModule>>
-      android_probes_module;
-  std::unique_ptr<ProtoImporterModule<HeapGraphModule>> heap_graph_module;
-
   // The module at the index N is registered to handle field id N in
   // TracePacket.
-  std::vector<NewProtoImporterModule*> modules_by_field;
-  std::vector<std::unique_ptr<NewProtoImporterModule>> modules;
+  std::vector<ProtoImporterModule*> modules_by_field;
+  std::vector<std::unique_ptr<ProtoImporterModule>> modules;
+  FtraceModule* ftrace_module;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 1a891f6..c1b3873 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -32,7 +32,6 @@
 #include "src/trace_processor/process_table.h"
 #include "src/trace_processor/raw_table.h"
 #include "src/trace_processor/sched_slice_table.h"
-#include "src/trace_processor/slice_table.h"
 #include "src/trace_processor/span_join_operator_table.h"
 #include "src/trace_processor/sql_stats_table.h"
 #include "src/trace_processor/sqlite/db_sqlite_table.h"
@@ -178,6 +177,7 @@
                "SELECT "
                "  *, "
                "  category AS cat, "
+               "  id AS slice_id, "
                "  CASE ref_type "
                "    WHEN 'utid' THEN ref "
                "    ELSE NULL "
@@ -193,7 +193,8 @@
                "CREATE VIEW gpu_slice AS "
                "SELECT "
                "* "
-               "FROM internal_gpu_slice join internal_slice using(slice_id);",
+               "FROM internal_gpu_slice join internal_slice "
+               "ON internal_gpu_slice.slice_id = internal_slice.id;",
                0, 0, &error);
   if (error) {
     PERFETTO_ELOG("Error initializing: %s", error);
@@ -376,7 +377,6 @@
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
   SchedSliceTable::RegisterTable(*db_, context_.storage.get());
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
-  SliceTable::RegisterTable(*db_, context_.storage.get());
   SqlStatsTable::RegisterTable(*db_, context_.storage.get());
   ThreadTable::RegisterTable(*db_, context_.storage.get());
   SpanJoinOperatorTable::RegisterTable(*db_, context_.storage.get());
@@ -394,14 +394,17 @@
   // New style db-backed tables.
   const TraceStorage* storage = context_.storage.get();
 
+  DbSqliteTable::RegisterTable(*db_, &storage->slice_table(),
+                               storage->slice_table().table_name());
+  DbSqliteTable::RegisterTable(*db_, &storage->gpu_slice_table(),
+                               storage->gpu_slice_table().table_name());
+
   DbSqliteTable::RegisterTable(*db_, &storage->track_table(),
                                storage->track_table().table_name());
   DbSqliteTable::RegisterTable(*db_, &storage->thread_track_table(),
                                storage->thread_track_table().table_name());
   DbSqliteTable::RegisterTable(*db_, &storage->process_track_table(),
                                storage->process_track_table().table_name());
-  DbSqliteTable::RegisterTable(*db_, &storage->gpu_slice_table(),
-                               storage->gpu_slice_table().table_name());
   DbSqliteTable::RegisterTable(*db_, &storage->gpu_track_table(),
                                storage->gpu_track_table().table_name());
 
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index f7a2877..50877fa 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -171,7 +171,7 @@
       fprintf(stderr, "Error stats for this trace:\n");
 
       for (uint32_t i = 0; i < it.ColumnCount(); i++)
-        fprintf(stderr, "%40s ", it.GetColumName(i).c_str());
+        fprintf(stderr, "%40s ", it.GetColumnName(i).c_str());
       fprintf(stderr, "\n");
 
       for (uint32_t i = 0; i < it.ColumnCount(); i++)
@@ -398,7 +398,7 @@
       }
       for (uint32_t i = 0; i < it->ColumnCount(); i++)
         printf("%-*.*s ", column_width, column_width,
-               it->GetColumName(i).c_str());
+               it->GetColumnName(i).c_str());
       printf("\n");
 
       std::string divider(column_width, '-');
@@ -489,7 +489,7 @@
   for (uint32_t c = 0; c < it->ColumnCount(); c++) {
     if (c > 0)
       fprintf(output, ",");
-    fprintf(output, "\"%s\"", it->GetColumName(c).c_str());
+    fprintf(output, "\"%s\"", it->GetColumnName(c).c_str());
   }
   fprintf(output, "\n");
 
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index da5521b..2ab4afa 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -68,17 +68,27 @@
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_GRAPHICS)
   context_.vulkan_memory_tracker.reset(new VulkanMemoryTracker(&context_));
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_GRAPHICS)
-  context_.ftrace_module.reset(
-      new ProtoImporterModule<FtraceModule>(&context_));
-  context_.track_event_module.reset(
-      new ProtoImporterModule<TrackEventModule>(&context_));
-  context_.system_probes_module.reset(
-      new ProtoImporterModule<SystemProbesModule>(&context_));
-  context_.android_probes_module.reset(
-      new ProtoImporterModule<AndroidProbesModule>(&context_));
-  context_.heap_graph_module.reset(
-      new ProtoImporterModule<HeapGraphModule>(&context_));
 
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
+  context_.modules.emplace_back(new FtraceModuleImpl(&context_));
+#else
+  context_.modules.emplace_back(new FtraceModule());
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
+  // Ftrace module is special, because it has one extra method for parsing
+  // ftrace packets. So we need to store a pointer to it separately.
+  context_.ftrace_module =
+      static_cast<FtraceModule*>(context_.modules.back().get());
+
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_HEAP_GRAPHS)
+  context_.modules.emplace_back(new HeapGraphModule(&context_));
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_HEAP_GRAPHS)
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
+  context_.modules.emplace_back(new AndroidProbesModule(&context_));
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
+  context_.modules.emplace_back(new SystemProbesModule(&context_));
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
+  context_.modules.emplace_back(new TrackEventModule(&context_));
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_GRAPHICS)
   context_.modules.emplace_back(new GraphicsEventModule(&context_));
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_GRAPHICS)
diff --git a/src/trace_processor/trace_storage.cc b/src/trace_processor/trace_storage.cc
index 70c2d7e..2f4e872 100644
--- a/src/trace_processor/trace_storage.cc
+++ b/src/trace_processor/trace_storage.cc
@@ -51,9 +51,9 @@
   *max_value = std::max(*max_value, column[column.row_map().size() - 1]);
 }
 
-std::vector<const char*> CreateRefTypeStringMap() {
-  std::vector<const char*> map(static_cast<size_t>(RefType::kRefMax));
-  map[static_cast<size_t>(RefType::kRefNoRef)] = nullptr;
+std::vector<NullTermStringView> CreateRefTypeStringMap() {
+  std::vector<NullTermStringView> map(static_cast<size_t>(RefType::kRefMax));
+  map[static_cast<size_t>(RefType::kRefNoRef)] = NullTermStringView();
   map[static_cast<size_t>(RefType::kRefUtid)] = "utid";
   map[static_cast<size_t>(RefType::kRefCpuId)] = "cpu";
   map[static_cast<size_t>(RefType::kRefGpuId)] = "gpu";
@@ -66,8 +66,8 @@
 
 }  // namespace
 
-const std::vector<const char*>& GetRefTypeStringMap() {
-  static const base::NoDestructor<std::vector<const char*>> map(
+const std::vector<NullTermStringView>& GetRefTypeStringMap() {
+  static const base::NoDestructor<std::vector<NullTermStringView>> map(
       CreateRefTypeStringMap());
   return map.ref();
 }
@@ -128,8 +128,6 @@
                     &start_ns, &end_ns);
   MaybeUpdateMinMax(instants_.timestamps().begin(),
                     instants_.timestamps().end(), &start_ns, &end_ns);
-  MaybeUpdateMinMax(nestable_slices_.start_ns().begin(),
-                    nestable_slices_.start_ns().end(), &start_ns, &end_ns);
   MaybeUpdateMinMax(android_log_.timestamps().begin(),
                     android_log_.timestamps().end(), &start_ns, &end_ns);
   MaybeUpdateMinMax(raw_events_.timestamps().begin(),
@@ -139,6 +137,7 @@
                     &end_ns);
 
   DbTableMaybeUpdateMinMax(counter_table_.ts(), &start_ns, &end_ns);
+  DbTableMaybeUpdateMinMax(slice_table_.ts(), &start_ns, &end_ns);
 
   if (start_ns == std::numeric_limits<int64_t>::max()) {
     return std::make_pair(0, 0);
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 61eb528..56c92a0 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -98,7 +98,7 @@
   kRefMax
 };
 
-const std::vector<const char*>& GetRefTypeStringMap();
+const std::vector<NullTermStringView>& GetRefTypeStringMap();
 
 // Stores a data inside a trace file in a columnar form. This makes it efficient
 // to read or search across a single field of the trace (e.g. all the thread
@@ -324,78 +324,6 @@
     std::deque<std::vector<uint32_t>> rows_for_utids_;
   };
 
-  class NestableSlices {
-   public:
-    inline uint32_t AddSlice(int64_t start_ns,
-                             int64_t duration_ns,
-                             TrackId track_id,
-                             int64_t ref,
-                             RefType type,
-                             StringId category,
-                             StringId name,
-                             uint8_t depth,
-                             int64_t stack_id,
-                             int64_t parent_stack_id) {
-      start_ns_.emplace_back(start_ns);
-      durations_.emplace_back(duration_ns);
-      track_id_.emplace_back(track_id);
-      refs_.emplace_back(ref);
-      types_.emplace_back(type);
-      categories_.emplace_back(category);
-      names_.emplace_back(name);
-      depths_.emplace_back(depth);
-      stack_ids_.emplace_back(stack_id);
-      parent_stack_ids_.emplace_back(parent_stack_id);
-      arg_set_ids_.emplace_back(kInvalidArgSetId);
-      return slice_count() - 1;
-    }
-
-    void set_name(uint32_t index, StringId name) { names_[index] = name; }
-
-    void set_duration(uint32_t index, int64_t duration_ns) {
-      durations_[index] = duration_ns;
-    }
-
-    void set_stack_id(uint32_t index, int64_t stack_id) {
-      stack_ids_[index] = stack_id;
-    }
-
-    void set_arg_set_id(uint32_t index, ArgSetId id) {
-      arg_set_ids_[index] = id;
-    }
-
-    uint32_t slice_count() const {
-      return static_cast<uint32_t>(start_ns_.size());
-    }
-
-    const std::deque<int64_t>& start_ns() const { return start_ns_; }
-    const std::deque<int64_t>& durations() const { return durations_; }
-    const std::deque<TrackId>& track_id() const { return track_id_; }
-    const std::deque<int64_t>& refs() const { return refs_; }
-    const std::deque<RefType>& types() const { return types_; }
-    const std::deque<StringId>& categories() const { return categories_; }
-    const std::deque<StringId>& names() const { return names_; }
-    const std::deque<uint8_t>& depths() const { return depths_; }
-    const std::deque<int64_t>& stack_ids() const { return stack_ids_; }
-    const std::deque<int64_t>& parent_stack_ids() const {
-      return parent_stack_ids_;
-    }
-    const std::deque<ArgSetId>& arg_set_ids() const { return arg_set_ids_; }
-
-   private:
-    std::deque<int64_t> start_ns_;
-    std::deque<int64_t> durations_;
-    std::deque<TrackId> track_id_;
-    std::deque<int64_t> refs_;
-    std::deque<RefType> types_;
-    std::deque<StringId> categories_;
-    std::deque<StringId> names_;
-    std::deque<uint8_t> depths_;
-    std::deque<int64_t> stack_ids_;
-    std::deque<int64_t> parent_stack_ids_;
-    std::deque<ArgSetId> arg_set_ids_;
-  };
-
   class ThreadSlices {
    public:
     inline uint32_t AddThreadSlice(uint32_t slice_id,
@@ -1115,8 +1043,8 @@
   const Slices& slices() const { return slices_; }
   Slices* mutable_slices() { return &slices_; }
 
-  const NestableSlices& nestable_slices() const { return nestable_slices_; }
-  NestableSlices* mutable_nestable_slices() { return &nestable_slices_; }
+  const tables::SliceTable& slice_table() const { return slice_table_; }
+  tables::SliceTable* mutable_slice_table() { return &slice_table_; }
 
   const ThreadSlices& thread_slices() const { return thread_slices_; }
   ThreadSlices* mutable_thread_slices() { return &thread_slices_; }
@@ -1304,7 +1232,7 @@
   std::deque<Thread> unique_threads_;
 
   // Slices coming from userspace events (e.g. Chromium TRACE_EVENT macros).
-  NestableSlices nestable_slices_;
+  tables::SliceTable slice_table_{&string_pool_, nullptr};
 
   // Additional attributes for threads slices (sub-type of NestableSlices).
   ThreadSlices thread_slices_;
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 9ee7477..6850c0b 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -75,7 +75,7 @@
 constexpr int kMaxBuffersPerConsumer = 128;
 constexpr base::TimeMillis kSnapshotsInterval(10 * 1000);
 constexpr int kDefaultWriteIntoFilePeriodMs = 5000;
-constexpr int kMaxConcurrentTracingSessions = 5;
+constexpr int kMaxConcurrentTracingSessions = 15;
 
 constexpr uint32_t kMillisPerHour = 3600000;
 constexpr uint32_t kMaxTracingDurationMillis = 7 * 24 * kMillisPerHour;
diff --git a/test/metrics/heap_profile.textproto b/test/metrics/heap_profile.textproto
index d7dcfd7..7ca4b08 100644
--- a/test/metrics/heap_profile.textproto
+++ b/test/metrics/heap_profile.textproto
@@ -14,6 +14,20 @@
     }
   }
 }
+
+packet {
+  clock_snapshot {
+    clocks: {
+      clock_id: 6 # BOOTTIME
+      timestamp: 0
+    }
+    clocks: {
+      clock_id: 4 # MONOTONIC_COARSE
+      timestamp: 10
+    }
+  }
+}
+
 packet {
   trusted_packet_sequence_id: 999
   previous_packet_dropped: true
diff --git a/tools/dev_server b/tools/dev_server
new file mode 100755
index 0000000..b73e28a
--- /dev/null
+++ b/tools/dev_server
@@ -0,0 +1,167 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# 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.
+
+#!/usr/bin/env python
+from __future__ import print_function
+import sys
+import os
+import time
+import argparse
+import socket
+import subprocess
+
+try:
+  import socketserver
+  from http.server import SimpleHTTPRequestHandler
+except ImportError:
+  import SocketServer as socketserver
+  import SimpleHTTPServer
+  SimpleHTTPRequestHandler = SimpleHTTPServer.SimpleHTTPRequestHandler
+
+
+class TCPServer(socketserver.TCPServer):
+
+  def server_bind(self):
+    self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+    self.socket.bind(self.server_address)
+
+
+class Server(object):
+
+  def __init__(self, port, directory, rebuilder):
+    self.port = port
+    self.directory = directory
+    self.rebuilder = rebuilder
+
+  def serve(self):
+    this = self
+
+    class Handler(SimpleHTTPRequestHandler):
+
+      def __init__(self, *args, **kwargs):
+        SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
+
+      def translate_path(self, path):
+        path = SimpleHTTPRequestHandler.translate_path(self, path)
+        relpath = os.path.relpath(path, os.getcwd())
+        fullpath = os.path.join(this.directory, relpath)
+        return fullpath
+
+      def do_GET(self):
+        try:
+          this.rebuilder.rebuild_if_needed()
+        except RebuildFailed as e:
+          self.send_response(200)
+          self.send_header("Content-type", "text/html")
+          self.end_headers()
+          self.wfile.write(e.stdout_and_stderr)
+          return
+        return SimpleHTTPRequestHandler.do_GET(self)
+
+    print('Starting server at http://localhost:{}'.format(self.port))
+    httpd = TCPServer(('', self.port), Handler)
+    try:
+      httpd.serve_forever()
+    except KeyboardInterrupt:
+      httpd.shutdown()
+    httpd.server_close()
+
+
+class RebuildFailed(Exception):
+
+  def __init__(self, stdout_and_stderr):
+    self.stdout_and_stderr = stdout_and_stderr
+
+
+class Rebuilder(object):
+
+  def __init__(self, command, ignored_paths):
+    self.command = command
+    self.ignored_paths = ignored_paths
+    self.last_disk_state = None
+
+  def rebuild_if_needed(self):
+    if self.last_disk_state == self.last_modified_time():
+      return
+    stdout_and_stderr, success = self.rebuild()
+    if not success:
+      message = b"Failed to build! Command output:\n\n" + stdout_and_stderr
+      raise RebuildFailed(message)
+    self.last_disk_state = self.last_modified_time()
+
+  def last_modified_time(self):
+    start_time = time.time()
+    max_mtime = 0
+    for dirpath, dirnames, filenames in os.walk('.', topdown=True):
+      dirnames[:] = [
+          n for n in dirnames
+          if not self.should_ignore_path(os.path.join(dirpath, n))
+      ]
+      for filename in filenames:
+        path = os.path.join(dirpath, filename)
+        if self.should_ignore_path(path):
+          continue
+        mtime = os.stat(path).st_mtime
+        max_mtime = max(max_mtime, mtime)
+    print(' scanned in {:.03f}s'.format(time.time() - start_time).rjust(
+        80, '='))
+    return max_mtime
+
+  def should_ignore_path(self, path):
+    return os.path.abspath(path) in (
+        os.path.abspath(p) for p in self.ignored_paths)
+
+  def rebuild(self):
+    print('Running command: {}'.format(self.command))
+    print(' begin build'.rjust(80, '='))
+    start_time = time.time()
+    status = 0
+    try:
+      stdout_and_stderr = subprocess.check_output(
+          self.command, shell=True, stderr=subprocess.STDOUT)
+    except subprocess.CalledProcessError as e:
+      status = e.returncode
+      stdout_and_stderr = e.output
+    print(stdout_and_stderr.decode('utf8'))
+    print(' built in {:.03f}s'.format(time.time() - start_time).rjust(80, '='))
+    return stdout_and_stderr, status == 0
+
+
+def main(argv):
+  parser = argparse.ArgumentParser(description='HTTP server for UI development')
+  parser.add_argument(
+      '-p',
+      '--port',
+      help='port number (default: 3000)',
+      type=int,
+      default=3000)
+  parser.add_argument(
+      '-i', '--ignore', default=[], action='append', help='Ignore this path')
+  parser.add_argument(
+      '-s',
+      '--serve',
+      default=os.getcwd(),
+      help='Serve this directory (default: current directory)')
+  parser.add_argument('command', default='', nargs='?', help='Command to run')
+
+  args = parser.parse_args(argv)
+
+  rebuilder = Rebuilder(args.command, args.ignore)
+  server = Server(args.port, args.serve, rebuilder)
+  server.serve()
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/ui/bs-config.js b/ui/bs-config.js
deleted file mode 100644
index b93c74c..0000000
--- a/ui/bs-config.js
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// 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.
-
-/**
- * Configuration file for lite-server. Contains configuration for auto rerunning
- * ninja on file change.
- */
-'use strict';
-
-const { spawn } = require('child_process');
-const path = require('path');
-
-// Print without added new line.
-const print = data => process.stdout.write(data);
-const printErr = data => process.stderr.write(data);
-
-const ninjaOutDir = process.env.OUT_DIR;
-const uiOutDir = path.join(ninjaOutDir, 'ui');
-const perfettoRoot = process.env.ROOT_DIR;
-const ninjaPath = path.join(perfettoRoot, 'tools', 'ninja');
-let ninjaRunning = false;
-
-function rebasePath(relative_path) {
-  return path.join(perfettoRoot, relative_path);
-}
-
-module.exports = function(bs) {
-  return {
-    files: [
-      {
-        match: [
-          "ui/**",
-          "src/trace_processor/**",
-          "protos/**",
-        ].map(rebasePath),
-        fn: function(event, file) {
-          console.log(`Change detected on ${file}`);
-          if (ninjaRunning) {
-            console.log("Already have a ninja build running. Doing nothing.");
-            return;
-          }
-
-          ninjaRunning = true;
-
-          console.log(`Executing: ninja -C ${ninjaOutDir} ui`);
-          const ninja = spawn(ninjaPath, ['-C', ninjaOutDir, 'ui']);
-          ninja.stdout.on('data', data => print(data.toString()));
-          ninja.stderr.on('data', data => printErr(data.toString()));
-
-          // We can be smarter and load just the file we need. Need to
-          // resolve to the dist/location of the file in that case.
-          // For now, we're reloading the whole page.
-          ninja.on('exit', () => {
-            ninjaRunning = false;
-            bs.reload();
-          });
-        },
-        options: {
-          ignored: [
-            "ui/dist/",
-            "ui/.git/",
-            "ui/node_modules/",
-          ].map(rebasePath),
-          ignoreInitial: true,
-        }
-      }
-    ],
-    server: {
-      baseDir: uiOutDir,
-    },
-  };
-};
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 61d7ad6..2b06d8c 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -200,16 +200,6 @@
       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
       "dev": true
     },
-    "accepts": {
-      "version": "1.3.5",
-      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
-      "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
-      "dev": true,
-      "requires": {
-        "mime-types": "~2.1.18",
-        "negotiator": "0.6.1"
-      }
-    },
     "acorn": {
       "version": "5.7.3",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
@@ -240,12 +230,6 @@
       "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==",
       "dev": true
     },
-    "after": {
-      "version": "0.8.2",
-      "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
-      "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
-      "dev": true
-    },
     "ajv": {
       "version": "6.9.1",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz",
@@ -661,12 +645,6 @@
       "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
       "dev": true
     },
-    "arraybuffer.slice": {
-      "version": "0.0.7",
-      "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
-      "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
-      "dev": true
-    },
     "arrify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
@@ -709,18 +687,6 @@
         "lodash": "^4.17.11"
       }
     },
-    "async-each": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
-      "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
-      "dev": true
-    },
-    "async-each-series": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz",
-      "integrity": "sha1-dhfBkXQB/Yykooqtzj266Yr+tDI=",
-      "dev": true
-    },
     "async-foreach": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
@@ -983,12 +949,6 @@
       "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
       "dev": true
     },
-    "backo2": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
-      "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
-      "dev": true
-    },
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -1062,24 +1022,6 @@
         }
       }
     },
-    "base64-arraybuffer": {
-      "version": "0.1.5",
-      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
-      "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=",
-      "dev": true
-    },
-    "base64id": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
-      "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=",
-      "dev": true
-    },
-    "batch": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
-      "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
-      "dev": true
-    },
     "bcrypt-pbkdf": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -1089,27 +1031,6 @@
         "tweetnacl": "^0.14.3"
       }
     },
-    "better-assert": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
-      "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
-      "dev": true,
-      "requires": {
-        "callsite": "1.0.0"
-      }
-    },
-    "binary-extensions": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz",
-      "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==",
-      "dev": true
-    },
-    "blob": {
-      "version": "0.0.5",
-      "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
-      "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==",
-      "dev": true
-    },
     "block-stream": {
       "version": "0.0.9",
       "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
@@ -1163,565 +1084,6 @@
         }
       }
     },
-    "browser-sync": {
-      "version": "2.26.7",
-      "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.26.7.tgz",
-      "integrity": "sha512-lY3emme0OyvA2ujEMpRmyRy9LY6gHLuTr2/ABxhIm3lADOiRXzP4dgekvnDrQqZ/Ec2Fz19lEjm6kglSG5766w==",
-      "dev": true,
-      "requires": {
-        "browser-sync-client": "^2.26.6",
-        "browser-sync-ui": "^2.26.4",
-        "bs-recipes": "1.3.4",
-        "bs-snippet-injector": "^2.0.1",
-        "chokidar": "^2.0.4",
-        "connect": "3.6.6",
-        "connect-history-api-fallback": "^1",
-        "dev-ip": "^1.0.1",
-        "easy-extender": "^2.3.4",
-        "eazy-logger": "^3",
-        "etag": "^1.8.1",
-        "fresh": "^0.5.2",
-        "fs-extra": "3.0.1",
-        "http-proxy": "1.15.2",
-        "immutable": "^3",
-        "localtunnel": "1.9.2",
-        "micromatch": "^3.1.10",
-        "opn": "5.3.0",
-        "portscanner": "2.1.1",
-        "qs": "6.2.3",
-        "raw-body": "^2.3.2",
-        "resp-modifier": "6.0.2",
-        "rx": "4.1.0",
-        "send": "0.16.2",
-        "serve-index": "1.9.1",
-        "serve-static": "1.13.2",
-        "server-destroy": "1.0.1",
-        "socket.io": "2.1.1",
-        "ua-parser-js": "0.7.17",
-        "yargs": "6.4.0"
-      },
-      "dependencies": {
-        "arr-diff": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
-          "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
-          "dev": true
-        },
-        "array-unique": {
-          "version": "0.3.2",
-          "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
-          "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
-          "dev": true
-        },
-        "axios": {
-          "version": "0.19.0",
-          "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
-          "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
-          "dev": true,
-          "requires": {
-            "follow-redirects": "1.5.10",
-            "is-buffer": "^2.0.2"
-          }
-        },
-        "braces": {
-          "version": "2.3.2",
-          "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
-          "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
-          "dev": true,
-          "requires": {
-            "arr-flatten": "^1.1.0",
-            "array-unique": "^0.3.2",
-            "extend-shallow": "^2.0.1",
-            "fill-range": "^4.0.0",
-            "isobject": "^3.0.1",
-            "repeat-element": "^1.1.2",
-            "snapdragon": "^0.8.1",
-            "snapdragon-node": "^2.0.1",
-            "split-string": "^3.0.2",
-            "to-regex": "^3.0.1"
-          },
-          "dependencies": {
-            "extend-shallow": {
-              "version": "2.0.1",
-              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-              "dev": true,
-              "requires": {
-                "is-extendable": "^0.1.0"
-              }
-            }
-          }
-        },
-        "browser-sync-client": {
-          "version": "2.26.6",
-          "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.26.6.tgz",
-          "integrity": "sha512-mGrkZdNzttKdf/16I+y+2dTQxoMCIpKbVIMJ/uP8ZpnKu9f9qa/2CYVtLtbjZG8nsM14EwiCrjuFTGBEnT3Gjw==",
-          "dev": true,
-          "requires": {
-            "etag": "1.8.1",
-            "fresh": "0.5.2",
-            "mitt": "^1.1.3",
-            "rxjs": "^5.5.6"
-          }
-        },
-        "browser-sync-ui": {
-          "version": "2.26.4",
-          "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.26.4.tgz",
-          "integrity": "sha512-u20P3EsZoM8Pt+puoi3BU3KlbQAH1lAcV+/O4saF26qokrBqIDotmGonfWwoRbUmdxZkM9MBmA0K39ZTG1h4sA==",
-          "dev": true,
-          "requires": {
-            "async-each-series": "0.1.1",
-            "connect-history-api-fallback": "^1",
-            "immutable": "^3",
-            "server-destroy": "1.0.1",
-            "socket.io-client": "^2.0.4",
-            "stream-throttle": "^0.1.3"
-          }
-        },
-        "camelcase": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
-          "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
-          "dev": true
-        },
-        "cliui": {
-          "version": "3.2.0",
-          "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
-          "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
-          "dev": true,
-          "requires": {
-            "string-width": "^1.0.1",
-            "strip-ansi": "^3.0.1",
-            "wrap-ansi": "^2.0.0"
-          }
-        },
-        "debug": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-          "dev": true,
-          "requires": {
-            "ms": "^2.1.1"
-          },
-          "dependencies": {
-            "ms": {
-              "version": "2.1.2",
-              "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-              "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-              "dev": true
-            }
-          }
-        },
-        "expand-brackets": {
-          "version": "2.1.4",
-          "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
-          "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
-          "dev": true,
-          "requires": {
-            "debug": "^2.3.3",
-            "define-property": "^0.2.5",
-            "extend-shallow": "^2.0.1",
-            "posix-character-classes": "^0.1.0",
-            "regex-not": "^1.0.0",
-            "snapdragon": "^0.8.1",
-            "to-regex": "^3.0.1"
-          },
-          "dependencies": {
-            "debug": {
-              "version": "2.6.9",
-              "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-              "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-              "dev": true,
-              "requires": {
-                "ms": "2.0.0"
-              }
-            },
-            "define-property": {
-              "version": "0.2.5",
-              "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-              "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-              "dev": true,
-              "requires": {
-                "is-descriptor": "^0.1.0"
-              }
-            },
-            "extend-shallow": {
-              "version": "2.0.1",
-              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-              "dev": true,
-              "requires": {
-                "is-extendable": "^0.1.0"
-              }
-            },
-            "is-accessor-descriptor": {
-              "version": "0.1.6",
-              "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
-              "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
-              "dev": true,
-              "requires": {
-                "kind-of": "^3.0.2"
-              },
-              "dependencies": {
-                "kind-of": {
-                  "version": "3.2.2",
-                  "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                  "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-                  "dev": true,
-                  "requires": {
-                    "is-buffer": "^1.1.5"
-                  }
-                }
-              }
-            },
-            "is-buffer": {
-              "version": "1.1.6",
-              "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-              "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
-              "dev": true
-            },
-            "is-data-descriptor": {
-              "version": "0.1.4",
-              "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
-              "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
-              "dev": true,
-              "requires": {
-                "kind-of": "^3.0.2"
-              },
-              "dependencies": {
-                "kind-of": {
-                  "version": "3.2.2",
-                  "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                  "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-                  "dev": true,
-                  "requires": {
-                    "is-buffer": "^1.1.5"
-                  }
-                }
-              }
-            },
-            "is-descriptor": {
-              "version": "0.1.6",
-              "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
-              "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
-              "dev": true,
-              "requires": {
-                "is-accessor-descriptor": "^0.1.6",
-                "is-data-descriptor": "^0.1.4",
-                "kind-of": "^5.0.0"
-              }
-            },
-            "kind-of": {
-              "version": "5.1.0",
-              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
-              "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
-              "dev": true
-            }
-          }
-        },
-        "extglob": {
-          "version": "2.0.4",
-          "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
-          "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
-          "dev": true,
-          "requires": {
-            "array-unique": "^0.3.2",
-            "define-property": "^1.0.0",
-            "expand-brackets": "^2.1.4",
-            "extend-shallow": "^2.0.1",
-            "fragment-cache": "^0.2.1",
-            "regex-not": "^1.0.0",
-            "snapdragon": "^0.8.1",
-            "to-regex": "^3.0.1"
-          },
-          "dependencies": {
-            "define-property": {
-              "version": "1.0.0",
-              "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-              "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
-              "dev": true,
-              "requires": {
-                "is-descriptor": "^1.0.0"
-              }
-            },
-            "extend-shallow": {
-              "version": "2.0.1",
-              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-              "dev": true,
-              "requires": {
-                "is-extendable": "^0.1.0"
-              }
-            }
-          }
-        },
-        "fill-range": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
-          "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
-          "dev": true,
-          "requires": {
-            "extend-shallow": "^2.0.1",
-            "is-number": "^3.0.0",
-            "repeat-string": "^1.6.1",
-            "to-regex-range": "^2.1.0"
-          },
-          "dependencies": {
-            "extend-shallow": {
-              "version": "2.0.1",
-              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-              "dev": true,
-              "requires": {
-                "is-extendable": "^0.1.0"
-              }
-            }
-          }
-        },
-        "follow-redirects": {
-          "version": "1.5.10",
-          "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
-          "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
-          "dev": true,
-          "requires": {
-            "debug": "=3.1.0"
-          },
-          "dependencies": {
-            "debug": {
-              "version": "3.1.0",
-              "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-              "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-              "dev": true,
-              "requires": {
-                "ms": "2.0.0"
-              }
-            }
-          }
-        },
-        "is-accessor-descriptor": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
-          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
-          "dev": true,
-          "requires": {
-            "kind-of": "^6.0.0"
-          }
-        },
-        "is-buffer": {
-          "version": "2.0.4",
-          "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
-          "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
-          "dev": true
-        },
-        "is-data-descriptor": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
-          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
-          "dev": true,
-          "requires": {
-            "kind-of": "^6.0.0"
-          }
-        },
-        "is-descriptor": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
-          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
-          "dev": true,
-          "requires": {
-            "is-accessor-descriptor": "^1.0.0",
-            "is-data-descriptor": "^1.0.0",
-            "kind-of": "^6.0.2"
-          }
-        },
-        "is-fullwidth-code-point": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
-          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
-          "dev": true,
-          "requires": {
-            "number-is-nan": "^1.0.0"
-          }
-        },
-        "is-number": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
-          "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
-          "dev": true,
-          "requires": {
-            "kind-of": "^3.0.2"
-          },
-          "dependencies": {
-            "is-buffer": {
-              "version": "1.1.6",
-              "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-              "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
-              "dev": true
-            },
-            "kind-of": {
-              "version": "3.2.2",
-              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-              "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-              "dev": true,
-              "requires": {
-                "is-buffer": "^1.1.5"
-              }
-            }
-          }
-        },
-        "isobject": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
-          "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
-          "dev": true
-        },
-        "kind-of": {
-          "version": "6.0.2",
-          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
-          "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
-          "dev": true
-        },
-        "localtunnel": {
-          "version": "1.9.2",
-          "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-1.9.2.tgz",
-          "integrity": "sha512-NEKF7bDJE9U3xzJu3kbayF0WTvng6Pww7tzqNb/XtEARYwqw7CKEX7BvOMg98FtE9es2CRizl61gkV3hS8dqYg==",
-          "dev": true,
-          "requires": {
-            "axios": "0.19.0",
-            "debug": "4.1.1",
-            "openurl": "1.1.1",
-            "yargs": "6.6.0"
-          },
-          "dependencies": {
-            "yargs": {
-              "version": "6.6.0",
-              "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz",
-              "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=",
-              "dev": true,
-              "requires": {
-                "camelcase": "^3.0.0",
-                "cliui": "^3.2.0",
-                "decamelize": "^1.1.1",
-                "get-caller-file": "^1.0.1",
-                "os-locale": "^1.4.0",
-                "read-pkg-up": "^1.0.1",
-                "require-directory": "^2.1.1",
-                "require-main-filename": "^1.0.1",
-                "set-blocking": "^2.0.0",
-                "string-width": "^1.0.2",
-                "which-module": "^1.0.0",
-                "y18n": "^3.2.1",
-                "yargs-parser": "^4.2.0"
-              }
-            }
-          }
-        },
-        "micromatch": {
-          "version": "3.1.10",
-          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
-          "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
-          "dev": true,
-          "requires": {
-            "arr-diff": "^4.0.0",
-            "array-unique": "^0.3.2",
-            "braces": "^2.3.1",
-            "define-property": "^2.0.2",
-            "extend-shallow": "^3.0.2",
-            "extglob": "^2.0.4",
-            "fragment-cache": "^0.2.1",
-            "kind-of": "^6.0.2",
-            "nanomatch": "^1.2.9",
-            "object.pick": "^1.3.0",
-            "regex-not": "^1.0.0",
-            "snapdragon": "^0.8.1",
-            "to-regex": "^3.0.2"
-          }
-        },
-        "os-locale": {
-          "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
-          "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
-          "dev": true,
-          "requires": {
-            "lcid": "^1.0.0"
-          }
-        },
-        "qs": {
-          "version": "6.2.3",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz",
-          "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=",
-          "dev": true
-        },
-        "string-width": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
-          "dev": true,
-          "requires": {
-            "code-point-at": "^1.0.0",
-            "is-fullwidth-code-point": "^1.0.0",
-            "strip-ansi": "^3.0.0"
-          }
-        },
-        "strip-ansi": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
-          "dev": true,
-          "requires": {
-            "ansi-regex": "^2.0.0"
-          }
-        },
-        "which-module": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
-          "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
-          "dev": true
-        },
-        "yargs": {
-          "version": "6.4.0",
-          "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.4.0.tgz",
-          "integrity": "sha1-gW4ahm1VmMzzTlWW3c4i2S2kkNQ=",
-          "dev": true,
-          "requires": {
-            "camelcase": "^3.0.0",
-            "cliui": "^3.2.0",
-            "decamelize": "^1.1.1",
-            "get-caller-file": "^1.0.1",
-            "os-locale": "^1.4.0",
-            "read-pkg-up": "^1.0.1",
-            "require-directory": "^2.1.1",
-            "require-main-filename": "^1.0.1",
-            "set-blocking": "^2.0.0",
-            "string-width": "^1.0.2",
-            "which-module": "^1.0.0",
-            "window-size": "^0.2.0",
-            "y18n": "^3.2.1",
-            "yargs-parser": "^4.1.0"
-          }
-        },
-        "yargs-parser": {
-          "version": "4.2.1",
-          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz",
-          "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=",
-          "dev": true,
-          "requires": {
-            "camelcase": "^3.0.0"
-          }
-        }
-      }
-    },
-    "bs-recipes": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/bs-recipes/-/bs-recipes-1.3.4.tgz",
-      "integrity": "sha1-DS1NSKcYyMBEdp/cT4lZLci2lYU=",
-      "dev": true
-    },
-    "bs-snippet-injector": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/bs-snippet-injector/-/bs-snippet-injector-2.0.1.tgz",
-      "integrity": "sha1-YbU5PxH1JVntEgaTEANDtu2wTdU=",
-      "dev": true
-    },
     "bser": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz",
@@ -1749,12 +1111,6 @@
       "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
       "dev": true
     },
-    "bytes": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
-      "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
-      "dev": true
-    },
     "cache-base": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
@@ -1780,12 +1136,6 @@
         }
       }
     },
-    "callsite": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
-      "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
-      "dev": true
-    },
     "callsites": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
@@ -1842,130 +1192,6 @@
         "supports-color": "^5.3.0"
       }
     },
-    "chokidar": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.1.tgz",
-      "integrity": "sha512-gfw3p2oQV2wEt+8VuMlNsPjCxDxvvgnm/kz+uATu805mWVF8IJN7uz9DN7iBz+RMJISmiVbCOBFs9qBGMjtPfQ==",
-      "dev": true,
-      "requires": {
-        "anymatch": "^2.0.0",
-        "async-each": "^1.0.1",
-        "braces": "^2.3.2",
-        "fsevents": "^1.2.7",
-        "glob-parent": "^3.1.0",
-        "inherits": "^2.0.3",
-        "is-binary-path": "^1.0.0",
-        "is-glob": "^4.0.0",
-        "normalize-path": "^3.0.0",
-        "path-is-absolute": "^1.0.0",
-        "readdirp": "^2.2.1",
-        "upath": "^1.1.0"
-      },
-      "dependencies": {
-        "array-unique": {
-          "version": "0.3.2",
-          "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
-          "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
-          "dev": true
-        },
-        "braces": {
-          "version": "2.3.2",
-          "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
-          "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
-          "dev": true,
-          "requires": {
-            "arr-flatten": "^1.1.0",
-            "array-unique": "^0.3.2",
-            "extend-shallow": "^2.0.1",
-            "fill-range": "^4.0.0",
-            "isobject": "^3.0.1",
-            "repeat-element": "^1.1.2",
-            "snapdragon": "^0.8.1",
-            "snapdragon-node": "^2.0.1",
-            "split-string": "^3.0.2",
-            "to-regex": "^3.0.1"
-          }
-        },
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        },
-        "fill-range": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
-          "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
-          "dev": true,
-          "requires": {
-            "extend-shallow": "^2.0.1",
-            "is-number": "^3.0.0",
-            "repeat-string": "^1.6.1",
-            "to-regex-range": "^2.1.0"
-          }
-        },
-        "glob-parent": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
-          "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
-          "dev": true,
-          "requires": {
-            "is-glob": "^3.1.0",
-            "path-dirname": "^1.0.0"
-          },
-          "dependencies": {
-            "is-glob": {
-              "version": "3.1.0",
-              "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
-              "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
-              "dev": true,
-              "requires": {
-                "is-extglob": "^2.1.0"
-              }
-            }
-          }
-        },
-        "is-extglob": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-          "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
-          "dev": true
-        },
-        "is-glob": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
-          "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
-          "dev": true,
-          "requires": {
-            "is-extglob": "^2.1.1"
-          }
-        },
-        "is-number": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
-          "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
-          "dev": true,
-          "requires": {
-            "kind-of": "^3.0.2"
-          }
-        },
-        "isobject": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
-          "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
-          "dev": true
-        },
-        "normalize-path": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
-          "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
-          "dev": true
-        }
-      }
-    },
     "ci-info": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
@@ -2062,24 +1288,12 @@
       "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
       "dev": true
     },
-    "component-bind": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
-      "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=",
-      "dev": true
-    },
     "component-emitter": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
       "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
       "dev": true
     },
-    "component-inherit": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
-      "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=",
-      "dev": true
-    },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2098,33 +1312,6 @@
         "typedarray": "^0.0.6"
       }
     },
-    "connect": {
-      "version": "3.6.6",
-      "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz",
-      "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=",
-      "dev": true,
-      "requires": {
-        "debug": "2.6.9",
-        "finalhandler": "1.1.0",
-        "parseurl": "~1.3.2",
-        "utils-merge": "1.0.1"
-      }
-    },
-    "connect-history-api-fallback": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
-      "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
-      "dev": true
-    },
-    "connect-logger": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/connect-logger/-/connect-logger-0.0.1.tgz",
-      "integrity": "sha1-TZmZeKHSC7RgjnzUNNdBZSJVF0s=",
-      "dev": true,
-      "requires": {
-        "moment": "*"
-      }
-    },
     "console-control-strings": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -2140,12 +1327,6 @@
         "safe-buffer": "~5.1.1"
       }
     },
-    "cookie": {
-      "version": "0.3.1",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
-      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
-      "dev": true
-    },
     "copy-descriptor": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
@@ -2344,18 +1525,6 @@
       "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
       "dev": true
     },
-    "depd": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
-      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
-      "dev": true
-    },
-    "destroy": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
-      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
-      "dev": true
-    },
     "detect-indent": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
@@ -2371,12 +1540,6 @@
       "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
       "dev": true
     },
-    "dev-ip": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz",
-      "integrity": "sha1-p2o+0YVb56ASu4rBbLgPPADcKPA=",
-      "dev": true
-    },
     "devtools-protocol": {
       "version": "0.0.681549",
       "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.681549.tgz",
@@ -2403,24 +1566,6 @@
         "webidl-conversions": "^4.0.2"
       }
     },
-    "easy-extender": {
-      "version": "2.3.4",
-      "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz",
-      "integrity": "sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q==",
-      "dev": true,
-      "requires": {
-        "lodash": "^4.17.10"
-      }
-    },
-    "eazy-logger": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-3.0.2.tgz",
-      "integrity": "sha1-oyWqXlPROiIliJsqxBE7K5Y29Pw=",
-      "dev": true,
-      "requires": {
-        "tfunk": "^3.0.1"
-      }
-    },
     "ecc-jsbn": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -2431,106 +1576,6 @@
         "safer-buffer": "^2.1.0"
       }
     },
-    "ee-first": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
-      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
-      "dev": true
-    },
-    "encodeurl": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
-      "dev": true
-    },
-    "engine.io": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz",
-      "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==",
-      "dev": true,
-      "requires": {
-        "accepts": "~1.3.4",
-        "base64id": "1.0.0",
-        "cookie": "0.3.1",
-        "debug": "~3.1.0",
-        "engine.io-parser": "~2.1.0",
-        "ws": "~3.3.1"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-          "dev": true,
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "ws": {
-          "version": "3.3.3",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
-          "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
-          "dev": true,
-          "requires": {
-            "async-limiter": "~1.0.0",
-            "safe-buffer": "~5.1.0",
-            "ultron": "~1.1.0"
-          }
-        }
-      }
-    },
-    "engine.io-client": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz",
-      "integrity": "sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==",
-      "dev": true,
-      "requires": {
-        "component-emitter": "1.2.1",
-        "component-inherit": "0.0.3",
-        "debug": "~3.1.0",
-        "engine.io-parser": "~2.1.1",
-        "has-cors": "1.1.0",
-        "indexof": "0.0.1",
-        "parseqs": "0.0.5",
-        "parseuri": "0.0.5",
-        "ws": "~6.1.0",
-        "xmlhttprequest-ssl": "~1.5.4",
-        "yeast": "0.1.2"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-          "dev": true,
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "ws": {
-          "version": "6.1.3",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.3.tgz",
-          "integrity": "sha512-tbSxiT+qJI223AP4iLfQbkbxkwdFcneYinM2+x46Gx2wgvbaOMO36czfdfVUBRTHvzAMRhDd98sA5d/BuWbQdg==",
-          "dev": true,
-          "requires": {
-            "async-limiter": "~1.0.0"
-          }
-        }
-      }
-    },
-    "engine.io-parser": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz",
-      "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==",
-      "dev": true,
-      "requires": {
-        "after": "0.8.2",
-        "arraybuffer.slice": "~0.0.7",
-        "base64-arraybuffer": "0.1.5",
-        "blob": "0.0.5",
-        "has-binary2": "~1.0.2"
-      }
-    },
     "error-ex": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -2586,12 +1631,6 @@
         }
       }
     },
-    "escape-html": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
-      "dev": true
-    },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -2650,18 +1689,6 @@
       "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
       "dev": true
     },
-    "etag": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
-      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
-      "dev": true
-    },
-    "eventemitter3": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz",
-      "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=",
-      "dev": true
-    },
     "events": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
@@ -2848,21 +1875,6 @@
         "repeat-string": "^1.5.2"
       }
     },
-    "finalhandler": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz",
-      "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=",
-      "dev": true,
-      "requires": {
-        "debug": "2.6.9",
-        "encodeurl": "~1.0.1",
-        "escape-html": "~1.0.3",
-        "on-finished": "~2.3.0",
-        "parseurl": "~1.3.2",
-        "statuses": "~1.3.1",
-        "unpipe": "~1.0.0"
-      }
-    },
     "find-up": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
@@ -2913,23 +1925,6 @@
         "map-cache": "^0.2.2"
       }
     },
-    "fresh": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
-      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
-      "dev": true
-    },
-    "fs-extra": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz",
-      "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=",
-      "dev": true,
-      "requires": {
-        "graceful-fs": "^4.1.2",
-        "jsonfile": "^3.0.0",
-        "universalify": "^0.1.0"
-      }
-    },
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3686,29 +2681,6 @@
         "ansi-regex": "^2.0.0"
       }
     },
-    "has-binary2": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
-      "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
-      "dev": true,
-      "requires": {
-        "isarray": "2.0.1"
-      },
-      "dependencies": {
-        "isarray": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
-          "dev": true
-        }
-      }
-    },
-    "has-cors": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
-      "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=",
-      "dev": true
-    },
     "has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -3811,36 +2783,6 @@
         "whatwg-encoding": "^1.0.1"
       }
     },
-    "http-errors": {
-      "version": "1.6.3",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
-      "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
-      "dev": true,
-      "requires": {
-        "depd": "~1.1.2",
-        "inherits": "2.0.3",
-        "setprototypeof": "1.1.0",
-        "statuses": ">= 1.4.0 < 2"
-      },
-      "dependencies": {
-        "statuses": {
-          "version": "1.5.0",
-          "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
-          "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
-          "dev": true
-        }
-      }
-    },
-    "http-proxy": {
-      "version": "1.15.2",
-      "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.15.2.tgz",
-      "integrity": "sha1-ZC/cr/5S00SNK9o7AHnpQJBk2jE=",
-      "dev": true,
-      "requires": {
-        "eventemitter3": "1.x.x",
-        "requires-port": "1.x.x"
-      }
-    },
     "http-signature": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@@ -3902,12 +2844,6 @@
       "resolved": "https://registry.npmjs.org/immer/-/immer-1.12.1.tgz",
       "integrity": "sha512-3fmKM6ovaqDt0CdC9daXpNi5x/YCYS3i4cwLdTVkhJdk5jrDXoPs7lCm3IqM3yhfSnz4tjjxbRG2CziQ7m8ztg=="
     },
-    "immutable": {
-      "version": "3.8.2",
-      "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
-      "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=",
-      "dev": true
-    },
     "import-local": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz",
@@ -3939,12 +2875,6 @@
         "repeating": "^2.0.0"
       }
     },
-    "indexof": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
-      "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
-      "dev": true
-    },
     "inflight": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -3995,15 +2925,6 @@
       "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
       "dev": true
     },
-    "is-binary-path": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
-      "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
-      "dev": true,
-      "requires": {
-        "binary-extensions": "^1.0.0"
-      }
-    },
     "is-buffer": {
       "version": "1.1.6",
       "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
@@ -4134,15 +3055,6 @@
         "kind-of": "^3.0.2"
       }
     },
-    "is-number-like": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz",
-      "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==",
-      "dev": true,
-      "requires": {
-        "lodash.isfinite": "^3.3.2"
-      }
-    },
     "is-plain-object": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -4862,15 +3774,6 @@
       "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
       "dev": true
     },
-    "jsonfile": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz",
-      "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=",
-      "dev": true,
-      "requires": {
-        "graceful-fs": "^4.1.6"
-      }
-    },
     "jsprim": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -4929,25 +3832,6 @@
         "type-check": "~0.3.2"
       }
     },
-    "limiter": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.4.tgz",
-      "integrity": "sha512-XCpr5bElgDI65vVgstP8TWjv6/QKWm9GU5UG0Pr5sLQ3QLo8NVKsioe+Jed5/3vFOe3IQuqE7DKwTvKQkjTHvg==",
-      "dev": true
-    },
-    "lite-server": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/lite-server/-/lite-server-2.4.0.tgz",
-      "integrity": "sha512-Vo06tHpXrqm37i6T7tVdq5PSbrFmvQRw64+dlFXdh1tltv6KCvpE+xzXz2+x6KWJ8ja+GgwSy4P13GUWyhaDHQ==",
-      "dev": true,
-      "requires": {
-        "browser-sync": "^2.24.4",
-        "connect-history-api-fallback": "^1.2.0",
-        "connect-logger": "0.0.1",
-        "lodash": "^4.11.1",
-        "minimist": "1.2.0"
-      }
-    },
     "load-json-file": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@@ -4977,12 +3861,6 @@
       "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
       "dev": true
     },
-    "lodash.isfinite": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz",
-      "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=",
-      "dev": true
-    },
     "lodash.sortby": {
       "version": "4.7.0",
       "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -5144,12 +4022,6 @@
       "resolved": "https://registry.npmjs.org/micromodal/-/micromodal-0.4.0.tgz",
       "integrity": "sha512-YDku9Fi57S4Sm6oitSy3sr786qSp5L6NbatuH2kEeXf0jStvZgZk4bLBKaoSONBaq3BEvFz3hAaoUa7/pV1Kgg=="
     },
-    "mime": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
-      "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
-      "dev": true
-    },
     "mime-db": {
       "version": "1.38.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
@@ -5191,12 +4063,6 @@
       "resolved": "https://registry.npmjs.org/mithril/-/mithril-1.1.7.tgz",
       "integrity": "sha512-1SAkGeVrIVvkUHlPHvR3pXdWzNfTzmS/fBAe+rC2ApEBfZFFc+idi8Qg/M5JoW/sZkIDXSfQYVgvENMIhBIVAg=="
     },
-    "mitt": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.1.3.tgz",
-      "integrity": "sha512-mUDCnVNsAi+eD6qA0HkRkwYczbLHJ49z17BGe2PYRhZL4wpZUFZGJHU7/5tmvohoma+Hdn0Vh/oJTiPEmgSruA==",
-      "dev": true
-    },
     "mixin-deep": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
@@ -5235,12 +4101,6 @@
         }
       }
     },
-    "moment": {
-      "version": "2.24.0",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
-      "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
-      "dev": true
-    },
     "ms": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -5299,12 +4159,6 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
       "dev": true
     },
-    "negotiator": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
-      "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
-      "dev": true
-    },
     "neo-async": {
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
@@ -5532,12 +4386,6 @@
       "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
       "dev": true
     },
-    "object-component": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
-      "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=",
-      "dev": true
-    },
     "object-copy": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
@@ -5565,12 +4413,6 @@
       "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz",
       "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg=="
     },
-    "object-path": {
-      "version": "0.9.2",
-      "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.9.2.tgz",
-      "integrity": "sha1-D9mnT8X60a45aLWGvaXGMr1sBaU=",
-      "dev": true
-    },
     "object-visit": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
@@ -5636,15 +4478,6 @@
         }
       }
     },
-    "on-finished": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
-      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
-      "dev": true,
-      "requires": {
-        "ee-first": "1.1.1"
-      }
-    },
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -5654,21 +4487,6 @@
         "wrappy": "1"
       }
     },
-    "openurl": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz",
-      "integrity": "sha1-OHW0sO96UsFW8NtB1GCduw+Us4c=",
-      "dev": true
-    },
-    "opn": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
-      "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==",
-      "dev": true,
-      "requires": {
-        "is-wsl": "^1.1.0"
-      }
-    },
     "optimist": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
@@ -5804,42 +4622,12 @@
       "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==",
       "dev": true
     },
-    "parseqs": {
-      "version": "0.0.5",
-      "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
-      "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
-      "dev": true,
-      "requires": {
-        "better-assert": "~1.0.0"
-      }
-    },
-    "parseuri": {
-      "version": "0.0.5",
-      "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
-      "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
-      "dev": true,
-      "requires": {
-        "better-assert": "~1.0.0"
-      }
-    },
-    "parseurl": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
-      "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=",
-      "dev": true
-    },
     "pascalcase": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
       "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
       "dev": true
     },
-    "path-dirname": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
-      "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
-      "dev": true
-    },
     "path-exists": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
@@ -5923,24 +4711,6 @@
       "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
       "dev": true
     },
-    "portscanner": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.1.1.tgz",
-      "integrity": "sha1-6rtAnk3iSVD1oqUW01rnaTQ/u5Y=",
-      "dev": true,
-      "requires": {
-        "async": "1.5.2",
-        "is-number-like": "^1.0.3"
-      },
-      "dependencies": {
-        "async": {
-          "version": "1.5.2",
-          "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
-          "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
-          "dev": true
-        }
-      }
-    },
     "posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -6135,35 +4905,6 @@
         }
       }
     },
-    "range-parser": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
-      "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
-      "dev": true
-    },
-    "raw-body": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
-      "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
-      "dev": true,
-      "requires": {
-        "bytes": "3.0.0",
-        "http-errors": "1.6.3",
-        "iconv-lite": "0.4.23",
-        "unpipe": "1.0.0"
-      },
-      "dependencies": {
-        "iconv-lite": {
-          "version": "0.4.23",
-          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
-          "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
-          "dev": true,
-          "requires": {
-            "safer-buffer": ">= 2.1.2 < 3"
-          }
-        }
-      }
-    },
     "read-pkg": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
@@ -6221,293 +4962,6 @@
         "util-deprecate": "~1.0.1"
       }
     },
-    "readdirp": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
-      "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
-      "dev": true,
-      "requires": {
-        "graceful-fs": "^4.1.11",
-        "micromatch": "^3.1.10",
-        "readable-stream": "^2.0.2"
-      },
-      "dependencies": {
-        "arr-diff": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
-          "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
-          "dev": true
-        },
-        "array-unique": {
-          "version": "0.3.2",
-          "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
-          "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
-          "dev": true
-        },
-        "braces": {
-          "version": "2.3.2",
-          "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
-          "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
-          "dev": true,
-          "requires": {
-            "arr-flatten": "^1.1.0",
-            "array-unique": "^0.3.2",
-            "extend-shallow": "^2.0.1",
-            "fill-range": "^4.0.0",
-            "isobject": "^3.0.1",
-            "repeat-element": "^1.1.2",
-            "snapdragon": "^0.8.1",
-            "snapdragon-node": "^2.0.1",
-            "split-string": "^3.0.2",
-            "to-regex": "^3.0.1"
-          },
-          "dependencies": {
-            "extend-shallow": {
-              "version": "2.0.1",
-              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-              "dev": true,
-              "requires": {
-                "is-extendable": "^0.1.0"
-              }
-            }
-          }
-        },
-        "expand-brackets": {
-          "version": "2.1.4",
-          "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
-          "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
-          "dev": true,
-          "requires": {
-            "debug": "^2.3.3",
-            "define-property": "^0.2.5",
-            "extend-shallow": "^2.0.1",
-            "posix-character-classes": "^0.1.0",
-            "regex-not": "^1.0.0",
-            "snapdragon": "^0.8.1",
-            "to-regex": "^3.0.1"
-          },
-          "dependencies": {
-            "define-property": {
-              "version": "0.2.5",
-              "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-              "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-              "dev": true,
-              "requires": {
-                "is-descriptor": "^0.1.0"
-              }
-            },
-            "extend-shallow": {
-              "version": "2.0.1",
-              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-              "dev": true,
-              "requires": {
-                "is-extendable": "^0.1.0"
-              }
-            },
-            "is-accessor-descriptor": {
-              "version": "0.1.6",
-              "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
-              "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
-              "dev": true,
-              "requires": {
-                "kind-of": "^3.0.2"
-              },
-              "dependencies": {
-                "kind-of": {
-                  "version": "3.2.2",
-                  "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                  "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-                  "dev": true,
-                  "requires": {
-                    "is-buffer": "^1.1.5"
-                  }
-                }
-              }
-            },
-            "is-data-descriptor": {
-              "version": "0.1.4",
-              "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
-              "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
-              "dev": true,
-              "requires": {
-                "kind-of": "^3.0.2"
-              },
-              "dependencies": {
-                "kind-of": {
-                  "version": "3.2.2",
-                  "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                  "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-                  "dev": true,
-                  "requires": {
-                    "is-buffer": "^1.1.5"
-                  }
-                }
-              }
-            },
-            "is-descriptor": {
-              "version": "0.1.6",
-              "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
-              "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
-              "dev": true,
-              "requires": {
-                "is-accessor-descriptor": "^0.1.6",
-                "is-data-descriptor": "^0.1.4",
-                "kind-of": "^5.0.0"
-              }
-            },
-            "kind-of": {
-              "version": "5.1.0",
-              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
-              "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
-              "dev": true
-            }
-          }
-        },
-        "extglob": {
-          "version": "2.0.4",
-          "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
-          "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
-          "dev": true,
-          "requires": {
-            "array-unique": "^0.3.2",
-            "define-property": "^1.0.0",
-            "expand-brackets": "^2.1.4",
-            "extend-shallow": "^2.0.1",
-            "fragment-cache": "^0.2.1",
-            "regex-not": "^1.0.0",
-            "snapdragon": "^0.8.1",
-            "to-regex": "^3.0.1"
-          },
-          "dependencies": {
-            "define-property": {
-              "version": "1.0.0",
-              "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-              "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
-              "dev": true,
-              "requires": {
-                "is-descriptor": "^1.0.0"
-              }
-            },
-            "extend-shallow": {
-              "version": "2.0.1",
-              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-              "dev": true,
-              "requires": {
-                "is-extendable": "^0.1.0"
-              }
-            }
-          }
-        },
-        "fill-range": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
-          "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
-          "dev": true,
-          "requires": {
-            "extend-shallow": "^2.0.1",
-            "is-number": "^3.0.0",
-            "repeat-string": "^1.6.1",
-            "to-regex-range": "^2.1.0"
-          },
-          "dependencies": {
-            "extend-shallow": {
-              "version": "2.0.1",
-              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-              "dev": true,
-              "requires": {
-                "is-extendable": "^0.1.0"
-              }
-            }
-          }
-        },
-        "is-accessor-descriptor": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
-          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
-          "dev": true,
-          "requires": {
-            "kind-of": "^6.0.0"
-          }
-        },
-        "is-data-descriptor": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
-          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
-          "dev": true,
-          "requires": {
-            "kind-of": "^6.0.0"
-          }
-        },
-        "is-descriptor": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
-          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
-          "dev": true,
-          "requires": {
-            "is-accessor-descriptor": "^1.0.0",
-            "is-data-descriptor": "^1.0.0",
-            "kind-of": "^6.0.2"
-          }
-        },
-        "is-number": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
-          "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
-          "dev": true,
-          "requires": {
-            "kind-of": "^3.0.2"
-          },
-          "dependencies": {
-            "kind-of": {
-              "version": "3.2.2",
-              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-              "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-              "dev": true,
-              "requires": {
-                "is-buffer": "^1.1.5"
-              }
-            }
-          }
-        },
-        "isobject": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
-          "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
-          "dev": true
-        },
-        "kind-of": {
-          "version": "6.0.2",
-          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
-          "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
-          "dev": true
-        },
-        "micromatch": {
-          "version": "3.1.10",
-          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
-          "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
-          "dev": true,
-          "requires": {
-            "arr-diff": "^4.0.0",
-            "array-unique": "^0.3.2",
-            "braces": "^2.3.1",
-            "define-property": "^2.0.2",
-            "extend-shallow": "^3.0.2",
-            "extglob": "^2.0.4",
-            "fragment-cache": "^0.2.1",
-            "kind-of": "^6.0.2",
-            "nanomatch": "^1.2.9",
-            "object.pick": "^1.3.0",
-            "regex-not": "^1.0.0",
-            "snapdragon": "^0.8.1",
-            "to-regex": "^3.0.2"
-          }
-        }
-      }
-    },
     "realpath-native": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz",
@@ -6657,12 +5111,6 @@
       "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
       "dev": true
     },
-    "requires-port": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
-      "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
-      "dev": true
-    },
     "resolve": {
       "version": "1.11.0",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz",
@@ -6701,16 +5149,6 @@
       "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
       "dev": true
     },
-    "resp-modifier": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/resp-modifier/-/resp-modifier-6.0.2.tgz",
-      "integrity": "sha1-sSTeXE+6/LpUH0j/pzlw9KpFa08=",
-      "dev": true,
-      "requires": {
-        "debug": "^2.2.0",
-        "minimatch": "^3.0.2"
-      }
-    },
     "ret": {
       "version": "0.1.15",
       "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
@@ -6798,21 +5236,6 @@
       "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==",
       "dev": true
     },
-    "rx": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
-      "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=",
-      "dev": true
-    },
-    "rxjs": {
-      "version": "5.5.12",
-      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz",
-      "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==",
-      "dev": true,
-      "requires": {
-        "symbol-observable": "1.0.1"
-      }
-    },
     "safe-buffer": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -7276,68 +5699,6 @@
       "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
       "dev": true
     },
-    "send": {
-      "version": "0.16.2",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
-      "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
-      "dev": true,
-      "requires": {
-        "debug": "2.6.9",
-        "depd": "~1.1.2",
-        "destroy": "~1.0.4",
-        "encodeurl": "~1.0.2",
-        "escape-html": "~1.0.3",
-        "etag": "~1.8.1",
-        "fresh": "0.5.2",
-        "http-errors": "~1.6.2",
-        "mime": "1.4.1",
-        "ms": "2.0.0",
-        "on-finished": "~2.3.0",
-        "range-parser": "~1.2.0",
-        "statuses": "~1.4.0"
-      },
-      "dependencies": {
-        "statuses": {
-          "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
-          "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
-          "dev": true
-        }
-      }
-    },
-    "serve-index": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
-      "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
-      "dev": true,
-      "requires": {
-        "accepts": "~1.3.4",
-        "batch": "0.6.1",
-        "debug": "2.6.9",
-        "escape-html": "~1.0.3",
-        "http-errors": "~1.6.2",
-        "mime-types": "~2.1.17",
-        "parseurl": "~1.3.2"
-      }
-    },
-    "serve-static": {
-      "version": "1.13.2",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
-      "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
-      "dev": true,
-      "requires": {
-        "encodeurl": "~1.0.2",
-        "escape-html": "~1.0.3",
-        "parseurl": "~1.3.2",
-        "send": "0.16.2"
-      }
-    },
-    "server-destroy": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
-      "integrity": "sha1-8Tv5KOQrnD55OD5hzDmYtdFObN0=",
-      "dev": true
-    },
     "set-blocking": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -7367,12 +5728,6 @@
         }
       }
     },
-    "setprototypeof": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
-      "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
-      "dev": true
-    },
     "shebang-command": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@@ -7520,167 +5875,6 @@
         "kind-of": "^3.2.0"
       }
     },
-    "socket.io": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz",
-      "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==",
-      "dev": true,
-      "requires": {
-        "debug": "~3.1.0",
-        "engine.io": "~3.2.0",
-        "has-binary2": "~1.0.2",
-        "socket.io-adapter": "~1.1.0",
-        "socket.io-client": "2.1.1",
-        "socket.io-parser": "~3.2.0"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-          "dev": true,
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "engine.io-client": {
-          "version": "3.2.1",
-          "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
-          "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==",
-          "dev": true,
-          "requires": {
-            "component-emitter": "1.2.1",
-            "component-inherit": "0.0.3",
-            "debug": "~3.1.0",
-            "engine.io-parser": "~2.1.1",
-            "has-cors": "1.1.0",
-            "indexof": "0.0.1",
-            "parseqs": "0.0.5",
-            "parseuri": "0.0.5",
-            "ws": "~3.3.1",
-            "xmlhttprequest-ssl": "~1.5.4",
-            "yeast": "0.1.2"
-          }
-        },
-        "isarray": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
-          "dev": true
-        },
-        "socket.io-client": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz",
-          "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==",
-          "dev": true,
-          "requires": {
-            "backo2": "1.0.2",
-            "base64-arraybuffer": "0.1.5",
-            "component-bind": "1.0.0",
-            "component-emitter": "1.2.1",
-            "debug": "~3.1.0",
-            "engine.io-client": "~3.2.0",
-            "has-binary2": "~1.0.2",
-            "has-cors": "1.1.0",
-            "indexof": "0.0.1",
-            "object-component": "0.0.3",
-            "parseqs": "0.0.5",
-            "parseuri": "0.0.5",
-            "socket.io-parser": "~3.2.0",
-            "to-array": "0.1.4"
-          }
-        },
-        "socket.io-parser": {
-          "version": "3.2.0",
-          "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
-          "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==",
-          "dev": true,
-          "requires": {
-            "component-emitter": "1.2.1",
-            "debug": "~3.1.0",
-            "isarray": "2.0.1"
-          }
-        },
-        "ws": {
-          "version": "3.3.3",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
-          "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
-          "dev": true,
-          "requires": {
-            "async-limiter": "~1.0.0",
-            "safe-buffer": "~5.1.0",
-            "ultron": "~1.1.0"
-          }
-        }
-      }
-    },
-    "socket.io-adapter": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz",
-      "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=",
-      "dev": true
-    },
-    "socket.io-client": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.2.0.tgz",
-      "integrity": "sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==",
-      "dev": true,
-      "requires": {
-        "backo2": "1.0.2",
-        "base64-arraybuffer": "0.1.5",
-        "component-bind": "1.0.0",
-        "component-emitter": "1.2.1",
-        "debug": "~3.1.0",
-        "engine.io-client": "~3.3.1",
-        "has-binary2": "~1.0.2",
-        "has-cors": "1.1.0",
-        "indexof": "0.0.1",
-        "object-component": "0.0.3",
-        "parseqs": "0.0.5",
-        "parseuri": "0.0.5",
-        "socket.io-parser": "~3.3.0",
-        "to-array": "0.1.4"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-          "dev": true,
-          "requires": {
-            "ms": "2.0.0"
-          }
-        }
-      }
-    },
-    "socket.io-parser": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz",
-      "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
-      "dev": true,
-      "requires": {
-        "component-emitter": "1.2.1",
-        "debug": "~3.1.0",
-        "isarray": "2.0.1"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-          "dev": true,
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "isarray": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
-          "dev": true
-        }
-      }
-    },
     "sorcery": {
       "version": "0.10.0",
       "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz",
@@ -7824,12 +6018,6 @@
         }
       }
     },
-    "statuses": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
-      "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=",
-      "dev": true
-    },
     "stdout-stream": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
@@ -7845,16 +6033,6 @@
       "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
       "dev": true
     },
-    "stream-throttle": {
-      "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz",
-      "integrity": "sha1-rdV8jXzHOoFjDTHNVdOWHPr7qcM=",
-      "dev": true,
-      "requires": {
-        "commander": "^2.2.0",
-        "limiter": "^1.0.5"
-      }
-    },
     "string-length": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz",
@@ -7934,12 +6112,6 @@
         "has-flag": "^3.0.0"
       }
     },
-    "symbol-observable": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
-      "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=",
-      "dev": true
-    },
     "symbol-tree": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
@@ -7970,52 +6142,6 @@
         "require-main-filename": "^1.0.1"
       }
     },
-    "tfunk": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/tfunk/-/tfunk-3.1.0.tgz",
-      "integrity": "sha1-OORBT8ZJd9h6/apy+sttKfgve1s=",
-      "dev": true,
-      "requires": {
-        "chalk": "^1.1.1",
-        "object-path": "^0.9.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "2.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
-          "dev": true
-        },
-        "chalk": {
-          "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^2.2.1",
-            "escape-string-regexp": "^1.0.2",
-            "has-ansi": "^2.0.0",
-            "strip-ansi": "^3.0.0",
-            "supports-color": "^2.0.0"
-          }
-        },
-        "strip-ansi": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
-          "dev": true,
-          "requires": {
-            "ansi-regex": "^2.0.0"
-          }
-        },
-        "supports-color": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
-          "dev": true
-        }
-      }
-    },
     "throat": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz",
@@ -8028,12 +6154,6 @@
       "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=",
       "dev": true
     },
-    "to-array": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
-      "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=",
-      "dev": true
-    },
     "to-fast-properties": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
@@ -8212,12 +6332,6 @@
       "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==",
       "dev": true
     },
-    "ua-parser-js": {
-      "version": "0.7.17",
-      "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
-      "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==",
-      "dev": true
-    },
     "uglify-js": {
       "version": "3.4.9",
       "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
@@ -8238,12 +6352,6 @@
         }
       }
     },
-    "ultron": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
-      "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
-      "dev": true
-    },
     "union-value": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -8256,18 +6364,6 @@
         "set-value": "^2.0.1"
       }
     },
-    "universalify": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
-      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
-      "dev": true
-    },
-    "unpipe": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
-      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
-      "dev": true
-    },
     "unset-value": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
@@ -8314,12 +6410,6 @@
         }
       }
     },
-    "upath": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
-      "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==",
-      "dev": true
-    },
     "uri-js": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
@@ -8369,12 +6459,6 @@
         "object.getownpropertydescriptors": "^2.0.3"
       }
     },
-    "utils-merge": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
-      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
-      "dev": true
-    },
     "uuid": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
@@ -8485,12 +6569,6 @@
         "string-width": "^1.0.2 || 2"
       }
     },
-    "window-size": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz",
-      "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=",
-      "dev": true
-    },
     "wordwrap": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
@@ -8570,12 +6648,6 @@
       "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==",
       "dev": true
     },
-    "xmlhttprequest-ssl": {
-      "version": "1.5.5",
-      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
-      "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=",
-      "dev": true
-    },
     "y18n": {
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
@@ -8625,12 +6697,6 @@
       "requires": {
         "fd-slicer": "~1.0.1"
       }
-    },
-    "yeast": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
-      "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=",
-      "dev": true
     }
   }
 }
diff --git a/ui/package.json b/ui/package.json
index dd44b6f..6a0f0f8 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -4,10 +4,6 @@
   "description": "Perfetto UI",
   "repository": "https://android.googlesource.com/platform/external/perfetto",
   "main": "main.js",
-  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1",
-    "dev": "lite-server --baseDir=dist"
-  },
   "author": "Perfetto Team",
   "license": "Apache-2.0",
   "dependencies": {
@@ -37,7 +33,6 @@
     "@types/puppeteer": "^1.12.4",
     "dingusjs": "^0.0.3",
     "jest": "^23.6.0",
-    "lite-server": "^2.4.0",
     "node-sass": "^4.12.0",
     "puppeteer": "^1.16.0",
     "rollup": "^1.17.0",
diff --git a/ui/run-dev-server b/ui/run-dev-server
index e42a940..843d2ac 100755
--- a/ui/run-dev-server
+++ b/ui/run-dev-server
@@ -15,15 +15,7 @@
 
 UI_DIR="$(cd -P ${BASH_SOURCE[0]%/*}; pwd)"
 ROOT_DIR=$(dirname "$UI_DIR")
-NODE=$ROOT_DIR/buildtools/nodejs/bin/node
-LITE="$UI_DIR/node_modules/.bin/lite-server"
 
-if [ ! -f "$LITE" ]; then
-  echo "ERROR: cannot find lite-server. You need to run:"
-  echo "  tools/install-build-deps --ui"
-  echo "  ninja -C out/xxx ui"
-  exit 127
-fi
 if [ -z "$1" ]; then
   echo "ERROR: no output directory specified."
   echo "Usage: $0 out/mac_debug"
@@ -31,16 +23,20 @@
 fi
 OUT_DIR="$1"
 UI_OUT_DIR="$OUT_DIR/ui"
-if [ ! -d $OUT_DIR ]; then
-  echo "ERROR: cannot find the output directory (\"$OUT_DIR\")"
-  echo "Did you run ninja?"
-  exit 127
-fi
 if [ ! -d $UI_OUT_DIR ]; then
   echo "ERROR: cannot find the UI output directory (\"$UI_OUT_DIR\")."
   echo "Did you run ninja ui?"
   exit 127
 fi
 
-OUT_DIR="$OUT_DIR" ROOT_DIR="$ROOT_DIR" $NODE $LITE -c $UI_DIR/bs-config.js
+$ROOT_DIR/tools/dev_server \
+  -p 10000 \
+  -i $ROOT_DIR/.git \
+  -i $ROOT_DIR/src/traced \
+  -i $ROOT_DIR/buildtools \
+  -i $ROOT_DIR/out \
+  -i $ROOT_DIR/ui/node_modules \
+  -s $UI_OUT_DIR \
+  "$ROOT_DIR/tools/ninja -C $OUT_DIR ui"
+