Merge "Chrome trace events: bind_id can be 64bit"
diff --git a/Android.bp b/Android.bp
index 0915ccf..9365d34 100644
--- a/Android.bp
+++ b/Android.bp
@@ -18,15 +18,17 @@
 cc_library_shared {
   name: "libtraced_shared",
   srcs: [
-    ":perfetto_protos_perfetto_common_common_gen",
-    ":perfetto_protos_perfetto_config_config_gen",
-    ":perfetto_protos_perfetto_config_config_zero_gen",
+    ":perfetto_protos_perfetto_common_lite_gen",
+    ":perfetto_protos_perfetto_common_zero_gen",
+    ":perfetto_protos_perfetto_config_lite_gen",
+    ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
     ":perfetto_protos_perfetto_trace_zero_gen",
     ":perfetto_src_ipc_wire_protocol_gen",
@@ -79,6 +81,7 @@
     "src/traced/probes/probes_data_source.cc",
     "src/traced/probes/probes_producer.cc",
     "src/traced/probes/ps/process_stats_data_source.cc",
+    "src/traced/probes/sys_stats/sys_stats_data_source.cc",
     "src/traced/service/service.cc",
     "src/tracing/core/chrome_config.cc",
     "src/tracing/core/commit_data_request.cc",
@@ -93,6 +96,7 @@
     "src/tracing/core/shared_memory_abi.cc",
     "src/tracing/core/shared_memory_arbiter_impl.cc",
     "src/tracing/core/sliced_protobuf_input_stream.cc",
+    "src/tracing/core/sys_stats_config.cc",
     "src/tracing/core/test_config.cc",
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_config.cc",
@@ -110,15 +114,17 @@
     "perfetto_src_tracing_ipc",
   ],
   generated_headers: [
-    "perfetto_protos_perfetto_common_common_gen_headers",
-    "perfetto_protos_perfetto_config_config_gen_headers",
-    "perfetto_protos_perfetto_config_config_zero_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_common_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
+    "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
     "perfetto_protos_perfetto_trace_zero_gen_headers",
     "perfetto_src_ipc_wire_protocol_gen_headers",
@@ -136,15 +142,17 @@
 cc_binary {
   name: "perfetto",
   srcs: [
-    ":perfetto_protos_perfetto_common_common_gen",
-    ":perfetto_protos_perfetto_config_config_gen",
-    ":perfetto_protos_perfetto_config_config_zero_gen",
+    ":perfetto_protos_perfetto_common_lite_gen",
+    ":perfetto_protos_perfetto_common_zero_gen",
+    ":perfetto_protos_perfetto_config_lite_gen",
+    ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
     ":perfetto_protos_perfetto_trace_zero_gen",
     ":perfetto_src_ipc_wire_protocol_gen",
@@ -191,6 +199,7 @@
     "src/tracing/core/shared_memory_abi.cc",
     "src/tracing/core/shared_memory_arbiter_impl.cc",
     "src/tracing/core/sliced_protobuf_input_stream.cc",
+    "src/tracing/core/sys_stats_config.cc",
     "src/tracing/core/test_config.cc",
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_config.cc",
@@ -214,15 +223,17 @@
     "libgtest_prod",
   ],
   generated_headers: [
-    "perfetto_protos_perfetto_common_common_gen_headers",
-    "perfetto_protos_perfetto_config_config_gen_headers",
-    "perfetto_protos_perfetto_config_config_zero_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_common_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
+    "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
     "perfetto_protos_perfetto_trace_zero_gen_headers",
     "perfetto_src_ipc_wire_protocol_gen_headers",
@@ -267,9 +278,10 @@
 cc_test {
   name: "perfetto_integrationtests",
   srcs: [
-    ":perfetto_protos_perfetto_common_common_gen",
-    ":perfetto_protos_perfetto_config_config_gen",
-    ":perfetto_protos_perfetto_config_config_zero_gen",
+    ":perfetto_protos_perfetto_common_lite_gen",
+    ":perfetto_protos_perfetto_common_zero_gen",
+    ":perfetto_protos_perfetto_config_lite_gen",
+    ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
     ":perfetto_protos_perfetto_trace_chrome_lite_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
@@ -281,6 +293,8 @@
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
     ":perfetto_protos_perfetto_trace_zero_gen",
     ":perfetto_src_ipc_wire_protocol_gen",
@@ -339,6 +353,7 @@
     "src/traced/probes/probes_data_source.cc",
     "src/traced/probes/probes_producer.cc",
     "src/traced/probes/ps/process_stats_data_source.cc",
+    "src/traced/probes/sys_stats/sys_stats_data_source.cc",
     "src/tracing/core/chrome_config.cc",
     "src/tracing/core/commit_data_request.cc",
     "src/tracing/core/data_source_config.cc",
@@ -352,6 +367,7 @@
     "src/tracing/core/shared_memory_abi.cc",
     "src/tracing/core/shared_memory_arbiter_impl.cc",
     "src/tracing/core/sliced_protobuf_input_stream.cc",
+    "src/tracing/core/sys_stats_config.cc",
     "src/tracing/core/test_config.cc",
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_config.cc",
@@ -375,9 +391,10 @@
     "perfetto_src_tracing_ipc",
   ],
   generated_headers: [
-    "perfetto_protos_perfetto_common_common_gen_headers",
-    "perfetto_protos_perfetto_config_config_gen_headers",
-    "perfetto_protos_perfetto_config_config_zero_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_common_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
+    "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
@@ -389,6 +406,8 @@
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
     "perfetto_protos_perfetto_trace_zero_gen_headers",
     "perfetto_src_ipc_wire_protocol_gen_headers",
@@ -408,11 +427,12 @@
   },
 }
 
-// GN target: //protos/perfetto/common:common_gen
+// GN target: //protos/perfetto/common:lite_gen
 genrule {
-  name: "perfetto_protos_perfetto_common_common_gen",
+  name: "perfetto_protos_perfetto_common_lite_gen",
   srcs: [
     "protos/perfetto/common/commit_data_request.proto",
+    "protos/perfetto/common/sys_stats_counters.proto",
   ],
   tools: [
     "aprotoc",
@@ -420,14 +440,16 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
   out: [
     "external/perfetto/protos/perfetto/common/commit_data_request.pb.cc",
+    "external/perfetto/protos/perfetto/common/sys_stats_counters.pb.cc",
   ],
 }
 
-// GN target: //protos/perfetto/common:common_gen
+// GN target: //protos/perfetto/common:lite_gen
 genrule {
-  name: "perfetto_protos_perfetto_common_common_gen_headers",
+  name: "perfetto_protos_perfetto_common_lite_gen_headers",
   srcs: [
     "protos/perfetto/common/commit_data_request.proto",
+    "protos/perfetto/common/sys_stats_counters.proto",
   ],
   tools: [
     "aprotoc",
@@ -435,15 +457,55 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
   out: [
     "external/perfetto/protos/perfetto/common/commit_data_request.pb.h",
+    "external/perfetto/protos/perfetto/common/sys_stats_counters.pb.h",
   ],
   export_include_dirs: [
     "protos",
   ],
 }
 
-// GN target: //protos/perfetto/config:config_gen
+// GN target: //protos/perfetto/common:zero_gen
 genrule {
-  name: "perfetto_protos_perfetto_config_config_gen",
+  name: "perfetto_protos_perfetto_common_zero_gen",
+  srcs: [
+    "protos/perfetto/common/commit_data_request.proto",
+    "protos/perfetto/common/sys_stats_counters.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/common/commit_data_request.pbzero.cc",
+    "external/perfetto/protos/perfetto/common/sys_stats_counters.pbzero.cc",
+  ],
+}
+
+// GN target: //protos/perfetto/common:zero_gen
+genrule {
+  name: "perfetto_protos_perfetto_common_zero_gen_headers",
+  srcs: [
+    "protos/perfetto/common/commit_data_request.proto",
+    "protos/perfetto/common/sys_stats_counters.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/common/commit_data_request.pbzero.h",
+    "external/perfetto/protos/perfetto/common/sys_stats_counters.pbzero.h",
+  ],
+  export_include_dirs: [
+    "protos",
+  ],
+}
+
+// GN target: //protos/perfetto/config:lite_gen
+genrule {
+  name: "perfetto_protos_perfetto_config_lite_gen",
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
@@ -451,6 +513,7 @@
     "protos/perfetto/config/ftrace/ftrace_config.proto",
     "protos/perfetto/config/inode_file/inode_file_config.proto",
     "protos/perfetto/config/process_stats/process_stats_config.proto",
+    "protos/perfetto/config/sys_stats/sys_stats_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -465,14 +528,15 @@
     "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.pb.cc",
     "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.pb.cc",
     "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.pb.cc",
+    "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.pb.cc",
     "external/perfetto/protos/perfetto/config/test_config.pb.cc",
     "external/perfetto/protos/perfetto/config/trace_config.pb.cc",
   ],
 }
 
-// GN target: //protos/perfetto/config:config_gen
+// GN target: //protos/perfetto/config:lite_gen
 genrule {
-  name: "perfetto_protos_perfetto_config_config_gen_headers",
+  name: "perfetto_protos_perfetto_config_lite_gen_headers",
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
@@ -480,6 +544,7 @@
     "protos/perfetto/config/ftrace/ftrace_config.proto",
     "protos/perfetto/config/inode_file/inode_file_config.proto",
     "protos/perfetto/config/process_stats/process_stats_config.proto",
+    "protos/perfetto/config/sys_stats/sys_stats_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -494,6 +559,7 @@
     "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.pb.h",
     "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.pb.h",
     "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.pb.h",
+    "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.pb.h",
     "external/perfetto/protos/perfetto/config/test_config.pb.h",
     "external/perfetto/protos/perfetto/config/trace_config.pb.h",
   ],
@@ -502,9 +568,9 @@
   ],
 }
 
-// GN target: //protos/perfetto/config:config_zero_gen
+// GN target: //protos/perfetto/config:zero_gen
 genrule {
-  name: "perfetto_protos_perfetto_config_config_zero_gen",
+  name: "perfetto_protos_perfetto_config_zero_gen",
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
@@ -512,6 +578,7 @@
     "protos/perfetto/config/ftrace/ftrace_config.proto",
     "protos/perfetto/config/inode_file/inode_file_config.proto",
     "protos/perfetto/config/process_stats/process_stats_config.proto",
+    "protos/perfetto/config/sys_stats/sys_stats_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -527,14 +594,15 @@
     "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.pbzero.cc",
     "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.pbzero.cc",
     "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.pbzero.cc",
+    "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.pbzero.cc",
     "external/perfetto/protos/perfetto/config/test_config.pbzero.cc",
     "external/perfetto/protos/perfetto/config/trace_config.pbzero.cc",
   ],
 }
 
-// GN target: //protos/perfetto/config:config_zero_gen
+// GN target: //protos/perfetto/config:zero_gen
 genrule {
-  name: "perfetto_protos_perfetto_config_config_zero_gen_headers",
+  name: "perfetto_protos_perfetto_config_zero_gen_headers",
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
@@ -542,6 +610,7 @@
     "protos/perfetto/config/ftrace/ftrace_config.proto",
     "protos/perfetto/config/inode_file/inode_file_config.proto",
     "protos/perfetto/config/process_stats/process_stats_config.proto",
+    "protos/perfetto/config/sys_stats/sys_stats_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -557,6 +626,7 @@
     "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.pbzero.h",
     "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.pbzero.h",
     "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.pbzero.h",
+    "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.pbzero.h",
     "external/perfetto/protos/perfetto/config/test_config.pbzero.h",
     "external/perfetto/protos/perfetto/config/trace_config.pbzero.h",
   ],
@@ -3062,6 +3132,74 @@
   ],
 }
 
+// GN target: //protos/perfetto/trace/sys_stats:lite_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_sys_stats_lite_gen",
+  srcs: [
+    "protos/perfetto/trace/sys_stats/sys_stats.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.pb.cc",
+  ],
+}
+
+// GN target: //protos/perfetto/trace/sys_stats:lite_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
+  srcs: [
+    "protos/perfetto/trace/sys_stats/sys_stats.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.pb.h",
+  ],
+  export_include_dirs: [
+    "protos",
+  ],
+}
+
+// GN target: //protos/perfetto/trace/sys_stats:zero_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_sys_stats_zero_gen",
+  srcs: [
+    "protos/perfetto/trace/sys_stats/sys_stats.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.pbzero.cc",
+  ],
+}
+
+// GN target: //protos/perfetto/trace/sys_stats:zero_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
+  srcs: [
+    "protos/perfetto/trace/sys_stats/sys_stats.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.pbzero.h",
+  ],
+  export_include_dirs: [
+    "protos",
+  ],
+}
+
 // GN target: //protos/perfetto/trace:trusted_lite_gen
 genrule {
   name: "perfetto_protos_perfetto_trace_trusted_lite_gen",
@@ -3463,15 +3601,17 @@
 cc_library_static {
   name: "perfetto_src_tracing_ipc",
   srcs: [
-    ":perfetto_protos_perfetto_common_common_gen",
-    ":perfetto_protos_perfetto_config_config_gen",
-    ":perfetto_protos_perfetto_config_config_zero_gen",
+    ":perfetto_protos_perfetto_common_lite_gen",
+    ":perfetto_protos_perfetto_common_zero_gen",
+    ":perfetto_protos_perfetto_config_lite_gen",
+    ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
     ":perfetto_protos_perfetto_trace_zero_gen",
     ":perfetto_src_ipc_wire_protocol_gen",
@@ -3513,6 +3653,7 @@
     "src/tracing/core/shared_memory_abi.cc",
     "src/tracing/core/shared_memory_arbiter_impl.cc",
     "src/tracing/core/sliced_protobuf_input_stream.cc",
+    "src/tracing/core/sys_stats_config.cc",
     "src/tracing/core/test_config.cc",
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_config.cc",
@@ -3539,29 +3680,33 @@
     "include",
   ],
   generated_headers: [
-    "perfetto_protos_perfetto_common_common_gen_headers",
-    "perfetto_protos_perfetto_config_config_gen_headers",
-    "perfetto_protos_perfetto_config_config_zero_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_common_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
+    "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
     "perfetto_protos_perfetto_trace_zero_gen_headers",
     "perfetto_src_ipc_wire_protocol_gen_headers",
   ],
   export_generated_headers: [
-    "perfetto_protos_perfetto_common_common_gen_headers",
-    "perfetto_protos_perfetto_config_config_gen_headers",
-    "perfetto_protos_perfetto_config_config_zero_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_common_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
+    "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
     "perfetto_protos_perfetto_trace_zero_gen_headers",
     "perfetto_src_ipc_wire_protocol_gen_headers",
@@ -3579,13 +3724,15 @@
 cc_library_static {
   name: "perfetto_trace_protos",
   srcs: [
-    ":perfetto_protos_perfetto_config_config_gen",
+    ":perfetto_protos_perfetto_common_lite_gen",
+    ":perfetto_protos_perfetto_config_lite_gen",
     ":perfetto_protos_perfetto_trace_chrome_lite_gen",
     ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
     ":perfetto_protos_perfetto_trace_ftrace_lite_gen",
     ":perfetto_protos_perfetto_trace_lite_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_lite_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
   ],
   shared_libs: [
     "liblog",
@@ -3596,22 +3743,26 @@
     "include",
   ],
   generated_headers: [
-    "perfetto_protos_perfetto_config_config_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
   ],
   export_generated_headers: [
-    "perfetto_protos_perfetto_config_config_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
   ],
   defaults: [
     "perfetto_defaults",
@@ -3626,9 +3777,10 @@
 cc_test {
   name: "perfetto_unittests",
   srcs: [
-    ":perfetto_protos_perfetto_common_common_gen",
-    ":perfetto_protos_perfetto_config_config_gen",
-    ":perfetto_protos_perfetto_config_config_zero_gen",
+    ":perfetto_protos_perfetto_common_lite_gen",
+    ":perfetto_protos_perfetto_common_zero_gen",
+    ":perfetto_protos_perfetto_config_lite_gen",
+    ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
     ":perfetto_protos_perfetto_trace_chrome_lite_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
@@ -3640,6 +3792,8 @@
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
     ":perfetto_protos_perfetto_trace_zero_gen",
     ":perfetto_src_ipc_test_messages_gen",
@@ -3759,6 +3913,8 @@
     "src/traced/probes/probes_producer.cc",
     "src/traced/probes/ps/process_stats_data_source.cc",
     "src/traced/probes/ps/process_stats_data_source_unittest.cc",
+    "src/traced/probes/sys_stats/sys_stats_data_source.cc",
+    "src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc",
     "src/tracing/core/chrome_config.cc",
     "src/tracing/core/commit_data_request.cc",
     "src/tracing/core/data_source_config.cc",
@@ -3780,6 +3936,7 @@
     "src/tracing/core/shared_memory_arbiter_impl_unittest.cc",
     "src/tracing/core/sliced_protobuf_input_stream.cc",
     "src/tracing/core/sliced_protobuf_input_stream_unittest.cc",
+    "src/tracing/core/sys_stats_config.cc",
     "src/tracing/core/test_config.cc",
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_buffer_unittest.cc",
@@ -3820,9 +3977,10 @@
     "perfetto_src_tracing_ipc",
   ],
   generated_headers: [
-    "perfetto_protos_perfetto_common_common_gen_headers",
-    "perfetto_protos_perfetto_config_config_gen_headers",
-    "perfetto_protos_perfetto_config_config_zero_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_common_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
+    "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
@@ -3834,6 +3992,8 @@
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
     "perfetto_protos_perfetto_trace_zero_gen_headers",
     "perfetto_src_ipc_test_messages_gen_headers",
@@ -3862,13 +4022,33 @@
 cc_binary_host {
   name: "trace_to_text",
   srcs: [
-    ":perfetto_protos_perfetto_config_config_gen",
+    ":perfetto_protos_perfetto_common_lite_gen",
+    ":perfetto_protos_perfetto_common_zero_gen",
+    ":perfetto_protos_perfetto_config_lite_gen",
     ":perfetto_protos_perfetto_trace_chrome_lite_gen",
     ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
     ":perfetto_protos_perfetto_trace_ftrace_lite_gen",
     ":perfetto_protos_perfetto_trace_lite_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_lite_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
+    "src/base/file_utils.cc",
+    "src/base/metatrace.cc",
+    "src/base/page_allocator.cc",
+    "src/base/string_splitter.cc",
+    "src/base/string_utils.cc",
+    "src/base/temp_file.cc",
+    "src/base/thread_checker.cc",
+    "src/base/time.cc",
+    "src/base/unix_task_runner.cc",
+    "src/base/virtual_destructors.cc",
+    "src/base/watchdog_posix.cc",
+    "src/protozero/message.cc",
+    "src/protozero/message_handle.cc",
+    "src/protozero/proto_decoder.cc",
+    "src/protozero/proto_field_descriptor.cc",
+    "src/protozero/scattered_stream_null_delegate.cc",
+    "src/protozero/scattered_stream_writer.cc",
     "tools/trace_to_text/ftrace_event_formatter.cc",
     "tools/trace_to_text/ftrace_inode_handler.cc",
     "tools/trace_to_text/main.cc",
@@ -3878,14 +4058,20 @@
     "libprotobuf-cpp-full",
     "libprotobuf-cpp-lite",
   ],
+  static_libs: [
+    "libgtest_prod",
+  ],
   generated_headers: [
-    "perfetto_protos_perfetto_config_config_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_common_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
   ],
   defaults: [
     "perfetto_defaults",
diff --git a/buildtools/.gitignore b/buildtools/.gitignore
index 83e0217..1dd47b9 100644
--- a/buildtools/.gitignore
+++ b/buildtools/.gitignore
@@ -1,6 +1,7 @@
 android_sdk/
 aosp-*/
 benchmark/
+bionic/
 clang/
 clang_format/
 emulator/
@@ -10,6 +11,7 @@
 libcxx/
 libcxxabi/
 libunwind/
+linenoise/
 linux/
 linux64/
 mac/
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index 9a16d42..0415b3e 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -597,6 +597,7 @@
     "-DSQLITE_CORE",
     "-DSQLITE_TEMP_STORE=3",
     "-DSQLITE_OMIT_LOAD_EXTENSION",
+    "-DSQLITE_OMIT_RANDOMNESS",
   ]
 }
 
@@ -750,3 +751,22 @@
   configs -= [ "//gn/standalone:extra_warnings" ]
   public_configs = [ ":jsoncpp_config" ]
 }
+
+config("linenoise_config") {
+  cflags = [
+    # Using -isystem instead of include_dirs (-I), so we don't need to suppress
+    # warnings coming from third-party headers. Doing so would mask warnings in
+    # our own code.
+    "-isystem",
+    rebase_path("linenoise", root_build_dir),
+  ]
+}
+
+source_set("linenoise") {
+  sources = [
+    "linenoise/linenoise.c",
+    "linenoise/linenoise.h",
+  ]
+  configs -= [ "//gn/standalone:extra_warnings" ]
+  public_configs = [ ":linenoise_config" ]
+}
diff --git a/gn/standalone/wasm.gni b/gn/standalone/wasm.gni
index 0304007..055af6a 100644
--- a/gn/standalone/wasm.gni
+++ b/gn/standalone/wasm.gni
@@ -64,7 +64,9 @@
       "-s",
       "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap', 'addFunction']",
     ]
-    if (!is_debug) {
+    if (is_debug) {
+      _target_ldflags += [ "-g4" ]
+    } else {
       _target_ldflags += [ "-O3" ]
     }
 
@@ -106,6 +108,9 @@
       outputs = [
         "$root_out_dir/$_lib_name.wasm",
       ]
+      if (is_debug) {
+        outputs += [ "$root_out_dir/$_lib_name.wasm.map" ]
+      }
       args = [ "--noop" ]
       script = "//gn/standalone/build_tool_wrapper.py"
     }
diff --git a/include/perfetto/base/build_config.h b/include/perfetto/base/build_config.h
index e083415..ef00b16 100644
--- a/include/perfetto/base/build_config.h
+++ b/include/perfetto/base/build_config.h
@@ -70,6 +70,13 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_CHROMIUM_BUILD() 0
 #endif
 
+#if !defined(PERFETTO_BUILD_WITH_CHROMIUM) && \
+    !defined(PERFETTO_BUILD_WITH_ANDROID)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_STANDALONE_BUILD() 1
+#else
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_STANDALONE_BUILD() 0
+#endif
+
 #if defined(PERFETTO_START_DAEMONS_FOR_TESTING)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_START_DAEMONS() 1
 #else
diff --git a/include/perfetto/base/time.h b/include/perfetto/base/time.h
index d1895c1..760d494 100644
--- a/include/perfetto/base/time.h
+++ b/include/perfetto/base/time.h
@@ -53,6 +53,11 @@
 TimeNanos GetWallTimeNs();
 TimeNanos GetThreadCPUTimeNs();
 
+// TODO: Clock that counts time during suspend is not implemented on Windows.
+inline TimeNanos GetBootTimeNs() {
+  return GetWallTimeNs();
+}
+
 #elif PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
 
 inline TimeNanos GetWallTimeNs() {
@@ -66,6 +71,11 @@
   return TimeNanos(mach_absolute_time() * monotonic_timebase_factor);
 }
 
+// TODO: Clock that counts time during suspend is not implemented on Mac.
+inline TimeNanos GetBootTimeNs() {
+  return GetWallTimeNs();
+}
+
 inline TimeNanos GetThreadCPUTimeNs() {
   mach_port_t this_thread = mach_thread_self();
   mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
@@ -95,6 +105,11 @@
   return TimeNanos(0);
 }
 
+// TODO: Clock that counts time during suspend is not implemented on WASM.
+inline TimeNanos GetBootTimeNs() {
+  return GetWallTimeNs();
+}
+
 #else
 
 constexpr clockid_t kWallTimeClockSource = CLOCK_MONOTONIC;
@@ -105,6 +120,18 @@
   return FromPosixTimespec(ts);
 }
 
+// Return ns from boot. Conversely to GetWallTimeNs, this clock counts also time
+// during suspend (when supported).
+inline TimeNanos GetBootTimeNs() {
+  // Determine if CLOCK_BOOTTIME is available on the first call.
+  static const clockid_t kBootTimeClockSource = [] {
+    struct timespec ts = {};
+    int res = clock_gettime(CLOCK_BOOTTIME, &ts);
+    return res == 0 ? CLOCK_BOOTTIME : kWallTimeClockSource;
+  }();
+  return GetTimeInternalNs(kBootTimeClockSource);
+}
+
 inline TimeNanos GetWallTimeNs() {
   return GetTimeInternalNs(kWallTimeClockSource);
 }
diff --git a/include/perfetto/traced/BUILD.gn b/include/perfetto/traced/BUILD.gn
index fefc5fa..f7dc66f 100644
--- a/include/perfetto/traced/BUILD.gn
+++ b/include/perfetto/traced/BUILD.gn
@@ -18,3 +18,16 @@
     "traced.h",
   ]
 }
+
+source_set("sys_stats_counters") {
+  deps = [
+    "../../../gn:default_deps",
+  ]
+  public_deps = [
+    "../../../protos/perfetto/common:zero",
+    "../base",
+  ]
+  sources = [
+    "sys_stats_counters.h",
+  ]
+}
diff --git a/include/perfetto/traced/sys_stats_counters.h b/include/perfetto/traced/sys_stats_counters.h
new file mode 100644
index 0000000..5bb0974
--- /dev/null
+++ b/include/perfetto/traced/sys_stats_counters.h
@@ -0,0 +1,233 @@
+/*
+ * 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 INCLUDE_PERFETTO_TRACED_SYS_STATS_COUNTERS_H_
+#define INCLUDE_PERFETTO_TRACED_SYS_STATS_COUNTERS_H_
+
+#include "perfetto/base/utils.h"
+#include "perfetto/common/sys_stats_counters.pbzero.h"
+
+#include <vector>
+
+namespace perfetto {
+
+struct KeyAndId {
+  const char* str;
+  int id;
+};
+
+constexpr KeyAndId kMeminfoKeys[] = {
+    {"MemTotal", protos::pbzero::MeminfoCounters::MEMINFO_MEM_TOTAL},
+    {"MemFree", protos::pbzero::MeminfoCounters::MEMINFO_MEM_FREE},
+    {"MemAvailable", protos::pbzero::MeminfoCounters::MEMINFO_MEM_AVAILABLE},
+    {"Buffers", protos::pbzero::MeminfoCounters::MEMINFO_BUFFERS},
+    {"Cached", protos::pbzero::MeminfoCounters::MEMINFO_CACHED},
+    {"SwapCached", protos::pbzero::MeminfoCounters::MEMINFO_SWAP_CACHED},
+    {"Active", protos::pbzero::MeminfoCounters::MEMINFO_ACTIVE},
+    {"Inactive", protos::pbzero::MeminfoCounters::MEMINFO_INACTIVE},
+    {"Active(anon)", protos::pbzero::MeminfoCounters::MEMINFO_ACTIVE_ANON},
+    {"Inactive(anon)", protos::pbzero::MeminfoCounters::MEMINFO_INACTIVE_ANON},
+    {"Active(file)", protos::pbzero::MeminfoCounters::MEMINFO_ACTIVE_FILE},
+    {"Inactive(file)", protos::pbzero::MeminfoCounters::MEMINFO_INACTIVE_FILE},
+    {"Unevictable", protos::pbzero::MeminfoCounters::MEMINFO_UNEVICTABLE},
+    {"Mlocked", protos::pbzero::MeminfoCounters::MEMINFO_MLOCKED},
+    {"SwapTotal", protos::pbzero::MeminfoCounters::MEMINFO_SWAP_TOTAL},
+    {"SwapFree", protos::pbzero::MeminfoCounters::MEMINFO_SWAP_FREE},
+    {"Dirty", protos::pbzero::MeminfoCounters::MEMINFO_DIRTY},
+    {"Writeback", protos::pbzero::MeminfoCounters::MEMINFO_WRITEBACK},
+    {"AnonPages", protos::pbzero::MeminfoCounters::MEMINFO_ANON_PAGES},
+    {"Mapped", protos::pbzero::MeminfoCounters::MEMINFO_MAPPED},
+    {"Shmem", protos::pbzero::MeminfoCounters::MEMINFO_SHMEM},
+    {"Slab", protos::pbzero::MeminfoCounters::MEMINFO_SLAB},
+    {"SReclaimable", protos::pbzero::MeminfoCounters::MEMINFO_SLAB_RECLAIMABLE},
+    {"SUnreclaim", protos::pbzero::MeminfoCounters::MEMINFO_SLAB_UNRECLAIMABLE},
+    {"KernelStack", protos::pbzero::MeminfoCounters::MEMINFO_KERNEL_STACK},
+    {"PageTables", protos::pbzero::MeminfoCounters::MEMINFO_PAGE_TABLES},
+    {"CommitLimit", protos::pbzero::MeminfoCounters::MEMINFO_COMMIT_LIMIT},
+    {"Committed_AS", protos::pbzero::MeminfoCounters::MEMINFO_COMMITED_AS},
+    {"VmallocTotal", protos::pbzero::MeminfoCounters::MEMINFO_VMALLOC_TOTAL},
+    {"VmallocUsed", protos::pbzero::MeminfoCounters::MEMINFO_VMALLOC_USED},
+    {"VmallocChunk", protos::pbzero::MeminfoCounters::MEMINFO_VMALLOC_CHUNK},
+    {"CmaTotal", protos::pbzero::MeminfoCounters::MEMINFO_CMA_TOTAL},
+    {"CmaFree", protos::pbzero::MeminfoCounters::MEMINFO_CMA_FREE},
+};
+
+const KeyAndId kVmstatKeys[] = {
+    {"nr_free_pages", protos::pbzero::VmstatCounters::VMSTAT_NR_FREE_PAGES},
+    {"nr_alloc_batch", protos::pbzero::VmstatCounters::VMSTAT_NR_ALLOC_BATCH},
+    {"nr_inactive_anon",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_INACTIVE_ANON},
+    {"nr_active_anon", protos::pbzero::VmstatCounters::VMSTAT_NR_ACTIVE_ANON},
+    {"nr_inactive_file",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_INACTIVE_FILE},
+    {"nr_active_file", protos::pbzero::VmstatCounters::VMSTAT_NR_ACTIVE_FILE},
+    {"nr_unevictable", protos::pbzero::VmstatCounters::VMSTAT_NR_UNEVICTABLE},
+    {"nr_mlock", protos::pbzero::VmstatCounters::VMSTAT_NR_MLOCK},
+    {"nr_anon_pages", protos::pbzero::VmstatCounters::VMSTAT_NR_ANON_PAGES},
+    {"nr_mapped", protos::pbzero::VmstatCounters::VMSTAT_NR_MAPPED},
+    {"nr_file_pages", protos::pbzero::VmstatCounters::VMSTAT_NR_FILE_PAGES},
+    {"nr_dirty", protos::pbzero::VmstatCounters::VMSTAT_NR_DIRTY},
+    {"nr_writeback", protos::pbzero::VmstatCounters::VMSTAT_NR_WRITEBACK},
+    {"nr_slab_reclaimable",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_SLAB_RECLAIMABLE},
+    {"nr_slab_unreclaimable",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_SLAB_UNRECLAIMABLE},
+    {"nr_page_table_pages",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_PAGE_TABLE_PAGES},
+    {"nr_kernel_stack", protos::pbzero::VmstatCounters::VMSTAT_NR_KERNEL_STACK},
+    {"nr_overhead", protos::pbzero::VmstatCounters::VMSTAT_NR_OVERHEAD},
+    {"nr_unstable", protos::pbzero::VmstatCounters::VMSTAT_NR_UNSTABLE},
+    {"nr_bounce", protos::pbzero::VmstatCounters::VMSTAT_NR_BOUNCE},
+    {"nr_vmscan_write", protos::pbzero::VmstatCounters::VMSTAT_NR_VMSCAN_WRITE},
+    {"nr_vmscan_immediate_reclaim",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_VMSCAN_IMMEDIATE_RECLAIM},
+    {"nr_writeback_temp",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_WRITEBACK_TEMP},
+    {"nr_isolated_anon",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ISOLATED_ANON},
+    {"nr_isolated_file",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ISOLATED_FILE},
+    {"nr_shmem", protos::pbzero::VmstatCounters::VMSTAT_NR_SHMEM},
+    {"nr_dirtied", protos::pbzero::VmstatCounters::VMSTAT_NR_DIRTIED},
+    {"nr_written", protos::pbzero::VmstatCounters::VMSTAT_NR_WRITTEN},
+    {"nr_pages_scanned",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_PAGES_SCANNED},
+    {"workingset_refault",
+     protos::pbzero::VmstatCounters::VMSTAT_WORKINGSET_REFAULT},
+    {"workingset_activate",
+     protos::pbzero::VmstatCounters::VMSTAT_WORKINGSET_ACTIVATE},
+    {"workingset_nodereclaim",
+     protos::pbzero::VmstatCounters::VMSTAT_WORKINGSET_NODERECLAIM},
+    {"nr_anon_transparent_hugepages",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ANON_TRANSPARENT_HUGEPAGES},
+    {"nr_free_cma", protos::pbzero::VmstatCounters::VMSTAT_NR_FREE_CMA},
+    {"nr_swapcache", protos::pbzero::VmstatCounters::VMSTAT_NR_SWAPCACHE},
+    {"nr_dirty_threshold",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_DIRTY_THRESHOLD},
+    {"nr_dirty_background_threshold",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_DIRTY_BACKGROUND_THRESHOLD},
+    {"pgpgin", protos::pbzero::VmstatCounters::VMSTAT_PGPGIN},
+    {"pgpgout", protos::pbzero::VmstatCounters::VMSTAT_PGPGOUT},
+    {"pgpgoutclean", protos::pbzero::VmstatCounters::VMSTAT_PGPGOUTCLEAN},
+    {"pswpin", protos::pbzero::VmstatCounters::VMSTAT_PSWPIN},
+    {"pswpout", protos::pbzero::VmstatCounters::VMSTAT_PSWPOUT},
+    {"pgalloc_dma", protos::pbzero::VmstatCounters::VMSTAT_PGALLOC_DMA},
+    {"pgalloc_normal", protos::pbzero::VmstatCounters::VMSTAT_PGALLOC_NORMAL},
+    {"pgalloc_movable", protos::pbzero::VmstatCounters::VMSTAT_PGALLOC_MOVABLE},
+    {"pgfree", protos::pbzero::VmstatCounters::VMSTAT_PGFREE},
+    {"pgactivate", protos::pbzero::VmstatCounters::VMSTAT_PGACTIVATE},
+    {"pgdeactivate", protos::pbzero::VmstatCounters::VMSTAT_PGDEACTIVATE},
+    {"pgfault", protos::pbzero::VmstatCounters::VMSTAT_PGFAULT},
+    {"pgmajfault", protos::pbzero::VmstatCounters::VMSTAT_PGMAJFAULT},
+    {"pgrefill_dma", protos::pbzero::VmstatCounters::VMSTAT_PGREFILL_DMA},
+    {"pgrefill_normal", protos::pbzero::VmstatCounters::VMSTAT_PGREFILL_NORMAL},
+    {"pgrefill_movable",
+     protos::pbzero::VmstatCounters::VMSTAT_PGREFILL_MOVABLE},
+    {"pgsteal_kswapd_dma",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSTEAL_KSWAPD_DMA},
+    {"pgsteal_kswapd_normal",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSTEAL_KSWAPD_NORMAL},
+    {"pgsteal_kswapd_movable",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSTEAL_KSWAPD_MOVABLE},
+    {"pgsteal_direct_dma",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSTEAL_DIRECT_DMA},
+    {"pgsteal_direct_normal",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSTEAL_DIRECT_NORMAL},
+    {"pgsteal_direct_movable",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSTEAL_DIRECT_MOVABLE},
+    {"pgscan_kswapd_dma",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSCAN_KSWAPD_DMA},
+    {"pgscan_kswapd_normal",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSCAN_KSWAPD_NORMAL},
+    {"pgscan_kswapd_movable",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSCAN_KSWAPD_MOVABLE},
+    {"pgscan_direct_dma",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSCAN_DIRECT_DMA},
+    {"pgscan_direct_normal",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSCAN_DIRECT_NORMAL},
+    {"pgscan_direct_movable",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSCAN_DIRECT_MOVABLE},
+    {"pgscan_direct_throttle",
+     protos::pbzero::VmstatCounters::VMSTAT_PGSCAN_DIRECT_THROTTLE},
+    {"pginodesteal", protos::pbzero::VmstatCounters::VMSTAT_PGINODESTEAL},
+    {"slabs_scanned", protos::pbzero::VmstatCounters::VMSTAT_SLABS_SCANNED},
+    {"kswapd_inodesteal",
+     protos::pbzero::VmstatCounters::VMSTAT_KSWAPD_INODESTEAL},
+    {"kswapd_low_wmark_hit_quickly",
+     protos::pbzero::VmstatCounters::VMSTAT_KSWAPD_LOW_WMARK_HIT_QUICKLY},
+    {"kswapd_high_wmark_hit_quickly",
+     protos::pbzero::VmstatCounters::VMSTAT_KSWAPD_HIGH_WMARK_HIT_QUICKLY},
+    {"pageoutrun", protos::pbzero::VmstatCounters::VMSTAT_PAGEOUTRUN},
+    {"allocstall", protos::pbzero::VmstatCounters::VMSTAT_ALLOCSTALL},
+    {"pgrotated", protos::pbzero::VmstatCounters::VMSTAT_PGROTATED},
+    {"drop_pagecache", protos::pbzero::VmstatCounters::VMSTAT_DROP_PAGECACHE},
+    {"drop_slab", protos::pbzero::VmstatCounters::VMSTAT_DROP_SLAB},
+    {"pgmigrate_success",
+     protos::pbzero::VmstatCounters::VMSTAT_PGMIGRATE_SUCCESS},
+    {"pgmigrate_fail", protos::pbzero::VmstatCounters::VMSTAT_PGMIGRATE_FAIL},
+    {"compact_migrate_scanned",
+     protos::pbzero::VmstatCounters::VMSTAT_COMPACT_MIGRATE_SCANNED},
+    {"compact_free_scanned",
+     protos::pbzero::VmstatCounters::VMSTAT_COMPACT_FREE_SCANNED},
+    {"compact_isolated",
+     protos::pbzero::VmstatCounters::VMSTAT_COMPACT_ISOLATED},
+    {"compact_stall", protos::pbzero::VmstatCounters::VMSTAT_COMPACT_STALL},
+    {"compact_fail", protos::pbzero::VmstatCounters::VMSTAT_COMPACT_FAIL},
+    {"compact_success", protos::pbzero::VmstatCounters::VMSTAT_COMPACT_SUCCESS},
+    {"compact_daemon_wake",
+     protos::pbzero::VmstatCounters::VMSTAT_COMPACT_DAEMON_WAKE},
+    {"unevictable_pgs_culled",
+     protos::pbzero::VmstatCounters::VMSTAT_UNEVICTABLE_PGS_CULLED},
+    {"unevictable_pgs_scanned",
+     protos::pbzero::VmstatCounters::VMSTAT_UNEVICTABLE_PGS_SCANNED},
+    {"unevictable_pgs_rescued",
+     protos::pbzero::VmstatCounters::VMSTAT_UNEVICTABLE_PGS_RESCUED},
+    {"unevictable_pgs_mlocked",
+     protos::pbzero::VmstatCounters::VMSTAT_UNEVICTABLE_PGS_MLOCKED},
+    {"unevictable_pgs_munlocked",
+     protos::pbzero::VmstatCounters::VMSTAT_UNEVICTABLE_PGS_MUNLOCKED},
+    {"unevictable_pgs_cleared",
+     protos::pbzero::VmstatCounters::VMSTAT_UNEVICTABLE_PGS_CLEARED},
+    {"unevictable_pgs_stranded",
+     protos::pbzero::VmstatCounters::VMSTAT_UNEVICTABLE_PGS_STRANDED},
+};
+
+// Returns a lookup table of meminfo counter names addressable by counter id.
+inline std::vector<const char*> BuildMeminfoCounterNames() {
+  int max_id = 0;
+  for (size_t i = 0; i < base::ArraySize(kMeminfoKeys); i++)
+    max_id = std::max(max_id, kMeminfoKeys[i].id);
+  std::vector<const char*> v;
+  v.resize(static_cast<size_t>(max_id) + 1);
+  for (size_t i = 0; i < base::ArraySize(kMeminfoKeys); i++)
+    v[static_cast<size_t>(kMeminfoKeys[i].id)] = kMeminfoKeys[i].str;
+  return v;
+}
+
+inline std::vector<const char*> BuildVmstatCounterNames() {
+  int max_id = 0;
+  for (size_t i = 0; i < base::ArraySize(kVmstatKeys); i++)
+    max_id = std::max(max_id, kVmstatKeys[i].id);
+  std::vector<const char*> v;
+  v.resize(static_cast<size_t>(max_id) + 1);
+  for (size_t i = 0; i < base::ArraySize(kVmstatKeys); i++)
+    v[static_cast<size_t>(kVmstatKeys[i].id)] = kVmstatKeys[i].str;
+  return v;
+}
+
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACED_SYS_STATS_COUNTERS_H_
diff --git a/include/perfetto/tracing/core/chrome_config.h b/include/perfetto/tracing/core/chrome_config.h
index ceac1a6..693546d 100644
--- a/include/perfetto/tracing/core/chrome_config.h
+++ b/include/perfetto/tracing/core/chrome_config.h
@@ -69,4 +69,5 @@
 };
 
 }  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_CHROME_CONFIG_H_
diff --git a/include/perfetto/tracing/core/commit_data_request.h b/include/perfetto/tracing/core/commit_data_request.h
index 3f0042f..d112d01 100644
--- a/include/perfetto/tracing/core/commit_data_request.h
+++ b/include/perfetto/tracing/core/commit_data_request.h
@@ -205,4 +205,5 @@
 };
 
 }  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_COMMIT_DATA_REQUEST_H_
diff --git a/include/perfetto/tracing/core/data_source_config.h b/include/perfetto/tracing/core/data_source_config.h
index fda2fb0..e816948 100644
--- a/include/perfetto/tracing/core/data_source_config.h
+++ b/include/perfetto/tracing/core/data_source_config.h
@@ -39,6 +39,7 @@
 #include "perfetto/tracing/core/ftrace_config.h"
 #include "perfetto/tracing/core/inode_file_config.h"
 #include "perfetto/tracing/core/process_stats_config.h"
+#include "perfetto/tracing/core/sys_stats_config.h"
 #include "perfetto/tracing/core/test_config.h"
 
 // Forward declarations for protobuf types.
@@ -50,6 +51,7 @@
 class InodeFileConfig;
 class InodeFileConfig_MountPointMappingEntry;
 class ProcessStatsConfig;
+class SysStatsConfig;
 class TestConfig;
 }  // namespace protos
 }  // namespace perfetto
@@ -99,6 +101,9 @@
     return &process_stats_config_;
   }
 
+  const SysStatsConfig& sys_stats_config() const { return sys_stats_config_; }
+  SysStatsConfig* mutable_sys_stats_config() { return &sys_stats_config_; }
+
   const std::string& legacy_config() const { return legacy_config_; }
   void set_legacy_config(const std::string& value) { legacy_config_ = value; }
 
@@ -114,6 +119,7 @@
   ChromeConfig chrome_config_ = {};
   InodeFileConfig inode_file_config_ = {};
   ProcessStatsConfig process_stats_config_ = {};
+  SysStatsConfig sys_stats_config_ = {};
   std::string legacy_config_ = {};
   TestConfig for_testing_ = {};
 
@@ -123,4 +129,5 @@
 };
 
 }  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_DATA_SOURCE_CONFIG_H_
diff --git a/include/perfetto/tracing/core/data_source_descriptor.h b/include/perfetto/tracing/core/data_source_descriptor.h
index 1678e79..db4e0de 100644
--- a/include/perfetto/tracing/core/data_source_descriptor.h
+++ b/include/perfetto/tracing/core/data_source_descriptor.h
@@ -73,4 +73,5 @@
 };
 
 }  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_DATA_SOURCE_DESCRIPTOR_H_
diff --git a/include/perfetto/tracing/core/ftrace_config.h b/include/perfetto/tracing/core/ftrace_config.h
index 5ed5715..794314a 100644
--- a/include/perfetto/tracing/core/ftrace_config.h
+++ b/include/perfetto/tracing/core/ftrace_config.h
@@ -105,4 +105,5 @@
 };
 
 }  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_FTRACE_CONFIG_H_
diff --git a/include/perfetto/tracing/core/inode_file_config.h b/include/perfetto/tracing/core/inode_file_config.h
index e376624..34f3e0a 100644
--- a/include/perfetto/tracing/core/inode_file_config.h
+++ b/include/perfetto/tracing/core/inode_file_config.h
@@ -140,4 +140,5 @@
 };
 
 }  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_INODE_FILE_CONFIG_H_
diff --git a/include/perfetto/tracing/core/process_stats_config.h b/include/perfetto/tracing/core/process_stats_config.h
index c89ee86..7adf410 100644
--- a/include/perfetto/tracing/core/process_stats_config.h
+++ b/include/perfetto/tracing/core/process_stats_config.h
@@ -90,4 +90,5 @@
 };
 
 }  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_PROCESS_STATS_CONFIG_H_
diff --git a/include/perfetto/tracing/core/sys_stats_config.h b/include/perfetto/tracing/core/sys_stats_config.h
new file mode 100644
index 0000000..a5deb9b
--- /dev/null
+++ b/include/perfetto/tracing/core/sys_stats_config.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/*******************************************************************************
+ * AUTOGENERATED - DO NOT EDIT
+ *******************************************************************************
+ * This file has been generated from the protobuf message
+ * perfetto/config/sys_stats/sys_stats_config.proto
+ * by
+ * ../../tools/proto_to_cpp/proto_to_cpp.cc.
+ * If you need to make changes here, change the .proto file and then run
+ * ./tools/gen_tracing_cpp_headers_from_protos.py
+ */
+
+#ifndef INCLUDE_PERFETTO_TRACING_CORE_SYS_STATS_CONFIG_H_
+#define INCLUDE_PERFETTO_TRACING_CORE_SYS_STATS_CONFIG_H_
+
+#include <stdint.h>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "perfetto/base/export.h"
+
+#include "perfetto/tracing/core/sys_stats_counters.h"
+
+// Forward declarations for protobuf types.
+namespace perfetto {
+namespace protos {
+class SysStatsConfig;
+}
+}  // namespace perfetto
+
+namespace perfetto {
+
+class PERFETTO_EXPORT SysStatsConfig {
+ public:
+  enum MeminfoCounters {
+    MEMINFO_UNSPECIFIED = 0,
+    MEMINFO_MEM_TOTAL = 1,
+    MEMINFO_MEM_FREE = 2,
+    MEMINFO_MEM_AVAILABLE = 3,
+    MEMINFO_BUFFERS = 4,
+    MEMINFO_CACHED = 5,
+    MEMINFO_SWAP_CACHED = 6,
+    MEMINFO_ACTIVE = 7,
+    MEMINFO_INACTIVE = 8,
+    MEMINFO_ACTIVE_ANON = 9,
+    MEMINFO_INACTIVE_ANON = 10,
+    MEMINFO_ACTIVE_FILE = 11,
+    MEMINFO_INACTIVE_FILE = 12,
+    MEMINFO_UNEVICTABLE = 13,
+    MEMINFO_MLOCKED = 14,
+    MEMINFO_SWAP_TOTAL = 15,
+    MEMINFO_SWAP_FREE = 16,
+    MEMINFO_DIRTY = 17,
+    MEMINFO_WRITEBACK = 18,
+    MEMINFO_ANON_PAGES = 19,
+    MEMINFO_MAPPED = 20,
+    MEMINFO_SHMEM = 21,
+    MEMINFO_SLAB = 22,
+    MEMINFO_SLAB_RECLAIMABLE = 23,
+    MEMINFO_SLAB_UNRECLAIMABLE = 24,
+    MEMINFO_KERNEL_STACK = 25,
+    MEMINFO_PAGE_TABLES = 26,
+    MEMINFO_COMMIT_LIMIT = 27,
+    MEMINFO_COMMITED_AS = 28,
+    MEMINFO_VMALLOC_TOTAL = 29,
+    MEMINFO_VMALLOC_USED = 30,
+    MEMINFO_VMALLOC_CHUNK = 31,
+    MEMINFO_CMA_TOTAL = 32,
+    MEMINFO_CMA_FREE = 33,
+  };
+  enum VmstatCounters {
+    VMSTAT_UNSPECIFIED = 0,
+    VMSTAT_NR_FREE_PAGES = 1,
+    VMSTAT_NR_ALLOC_BATCH = 2,
+    VMSTAT_NR_INACTIVE_ANON = 3,
+    VMSTAT_NR_ACTIVE_ANON = 4,
+    VMSTAT_NR_INACTIVE_FILE = 5,
+    VMSTAT_NR_ACTIVE_FILE = 6,
+    VMSTAT_NR_UNEVICTABLE = 7,
+    VMSTAT_NR_MLOCK = 8,
+    VMSTAT_NR_ANON_PAGES = 9,
+    VMSTAT_NR_MAPPED = 10,
+    VMSTAT_NR_FILE_PAGES = 11,
+    VMSTAT_NR_DIRTY = 12,
+    VMSTAT_NR_WRITEBACK = 13,
+    VMSTAT_NR_SLAB_RECLAIMABLE = 14,
+    VMSTAT_NR_SLAB_UNRECLAIMABLE = 15,
+    VMSTAT_NR_PAGE_TABLE_PAGES = 16,
+    VMSTAT_NR_KERNEL_STACK = 17,
+    VMSTAT_NR_OVERHEAD = 18,
+    VMSTAT_NR_UNSTABLE = 19,
+    VMSTAT_NR_BOUNCE = 20,
+    VMSTAT_NR_VMSCAN_WRITE = 21,
+    VMSTAT_NR_VMSCAN_IMMEDIATE_RECLAIM = 22,
+    VMSTAT_NR_WRITEBACK_TEMP = 23,
+    VMSTAT_NR_ISOLATED_ANON = 24,
+    VMSTAT_NR_ISOLATED_FILE = 25,
+    VMSTAT_NR_SHMEM = 26,
+    VMSTAT_NR_DIRTIED = 27,
+    VMSTAT_NR_WRITTEN = 28,
+    VMSTAT_NR_PAGES_SCANNED = 29,
+    VMSTAT_WORKINGSET_REFAULT = 30,
+    VMSTAT_WORKINGSET_ACTIVATE = 31,
+    VMSTAT_WORKINGSET_NODERECLAIM = 32,
+    VMSTAT_NR_ANON_TRANSPARENT_HUGEPAGES = 33,
+    VMSTAT_NR_FREE_CMA = 34,
+    VMSTAT_NR_SWAPCACHE = 35,
+    VMSTAT_NR_DIRTY_THRESHOLD = 36,
+    VMSTAT_NR_DIRTY_BACKGROUND_THRESHOLD = 37,
+    VMSTAT_PGPGIN = 38,
+    VMSTAT_PGPGOUT = 39,
+    VMSTAT_PGPGOUTCLEAN = 40,
+    VMSTAT_PSWPIN = 41,
+    VMSTAT_PSWPOUT = 42,
+    VMSTAT_PGALLOC_DMA = 43,
+    VMSTAT_PGALLOC_NORMAL = 44,
+    VMSTAT_PGALLOC_MOVABLE = 45,
+    VMSTAT_PGFREE = 46,
+    VMSTAT_PGACTIVATE = 47,
+    VMSTAT_PGDEACTIVATE = 48,
+    VMSTAT_PGFAULT = 49,
+    VMSTAT_PGMAJFAULT = 50,
+    VMSTAT_PGREFILL_DMA = 51,
+    VMSTAT_PGREFILL_NORMAL = 52,
+    VMSTAT_PGREFILL_MOVABLE = 53,
+    VMSTAT_PGSTEAL_KSWAPD_DMA = 54,
+    VMSTAT_PGSTEAL_KSWAPD_NORMAL = 55,
+    VMSTAT_PGSTEAL_KSWAPD_MOVABLE = 56,
+    VMSTAT_PGSTEAL_DIRECT_DMA = 57,
+    VMSTAT_PGSTEAL_DIRECT_NORMAL = 58,
+    VMSTAT_PGSTEAL_DIRECT_MOVABLE = 59,
+    VMSTAT_PGSCAN_KSWAPD_DMA = 60,
+    VMSTAT_PGSCAN_KSWAPD_NORMAL = 61,
+    VMSTAT_PGSCAN_KSWAPD_MOVABLE = 62,
+    VMSTAT_PGSCAN_DIRECT_DMA = 63,
+    VMSTAT_PGSCAN_DIRECT_NORMAL = 64,
+    VMSTAT_PGSCAN_DIRECT_MOVABLE = 65,
+    VMSTAT_PGSCAN_DIRECT_THROTTLE = 66,
+    VMSTAT_PGINODESTEAL = 67,
+    VMSTAT_SLABS_SCANNED = 68,
+    VMSTAT_KSWAPD_INODESTEAL = 69,
+    VMSTAT_KSWAPD_LOW_WMARK_HIT_QUICKLY = 70,
+    VMSTAT_KSWAPD_HIGH_WMARK_HIT_QUICKLY = 71,
+    VMSTAT_PAGEOUTRUN = 72,
+    VMSTAT_ALLOCSTALL = 73,
+    VMSTAT_PGROTATED = 74,
+    VMSTAT_DROP_PAGECACHE = 75,
+    VMSTAT_DROP_SLAB = 76,
+    VMSTAT_PGMIGRATE_SUCCESS = 77,
+    VMSTAT_PGMIGRATE_FAIL = 78,
+    VMSTAT_COMPACT_MIGRATE_SCANNED = 79,
+    VMSTAT_COMPACT_FREE_SCANNED = 80,
+    VMSTAT_COMPACT_ISOLATED = 81,
+    VMSTAT_COMPACT_STALL = 82,
+    VMSTAT_COMPACT_FAIL = 83,
+    VMSTAT_COMPACT_SUCCESS = 84,
+    VMSTAT_COMPACT_DAEMON_WAKE = 85,
+    VMSTAT_UNEVICTABLE_PGS_CULLED = 86,
+    VMSTAT_UNEVICTABLE_PGS_SCANNED = 87,
+    VMSTAT_UNEVICTABLE_PGS_RESCUED = 88,
+    VMSTAT_UNEVICTABLE_PGS_MLOCKED = 89,
+    VMSTAT_UNEVICTABLE_PGS_MUNLOCKED = 90,
+    VMSTAT_UNEVICTABLE_PGS_CLEARED = 91,
+    VMSTAT_UNEVICTABLE_PGS_STRANDED = 92,
+  };
+  enum StatCounters {
+    STAT_UNSPECIFIED = 0,
+    STAT_CPU_TIMES = 1,
+    STAT_IRQ_COUNTS = 2,
+    STAT_SOFTIRQ_COUNTS = 3,
+    STAT_FORK_COUNT = 4,
+  };
+  SysStatsConfig();
+  ~SysStatsConfig();
+  SysStatsConfig(SysStatsConfig&&) noexcept;
+  SysStatsConfig& operator=(SysStatsConfig&&);
+  SysStatsConfig(const SysStatsConfig&);
+  SysStatsConfig& operator=(const SysStatsConfig&);
+
+  // Conversion methods from/to the corresponding protobuf types.
+  void FromProto(const perfetto::protos::SysStatsConfig&);
+  void ToProto(perfetto::protos::SysStatsConfig*) const;
+
+  uint32_t meminfo_period_ms() const { return meminfo_period_ms_; }
+  void set_meminfo_period_ms(uint32_t value) { meminfo_period_ms_ = value; }
+
+  int meminfo_counters_size() const {
+    return static_cast<int>(meminfo_counters_.size());
+  }
+  const std::vector<MeminfoCounters>& meminfo_counters() const {
+    return meminfo_counters_;
+  }
+  MeminfoCounters* add_meminfo_counters() {
+    meminfo_counters_.emplace_back();
+    return &meminfo_counters_.back();
+  }
+
+  uint32_t vmstat_period_ms() const { return vmstat_period_ms_; }
+  void set_vmstat_period_ms(uint32_t value) { vmstat_period_ms_ = value; }
+
+  int vmstat_counters_size() const {
+    return static_cast<int>(vmstat_counters_.size());
+  }
+  const std::vector<VmstatCounters>& vmstat_counters() const {
+    return vmstat_counters_;
+  }
+  VmstatCounters* add_vmstat_counters() {
+    vmstat_counters_.emplace_back();
+    return &vmstat_counters_.back();
+  }
+
+  uint32_t stat_period_ms() const { return stat_period_ms_; }
+  void set_stat_period_ms(uint32_t value) { stat_period_ms_ = value; }
+
+  int stat_counters_size() const {
+    return static_cast<int>(stat_counters_.size());
+  }
+  const std::vector<StatCounters>& stat_counters() const {
+    return stat_counters_;
+  }
+  StatCounters* add_stat_counters() {
+    stat_counters_.emplace_back();
+    return &stat_counters_.back();
+  }
+
+ private:
+  uint32_t meminfo_period_ms_ = {};
+  std::vector<MeminfoCounters> meminfo_counters_;
+  uint32_t vmstat_period_ms_ = {};
+  std::vector<VmstatCounters> vmstat_counters_;
+  uint32_t stat_period_ms_ = {};
+  std::vector<StatCounters> stat_counters_;
+
+  // Allows to preserve unknown protobuf fields for compatibility
+  // with future versions of .proto files.
+  std::string unknown_fields_;
+};
+
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_CORE_SYS_STATS_CONFIG_H_
diff --git a/include/perfetto/tracing/core/sys_stats_counters.h b/include/perfetto/tracing/core/sys_stats_counters.h
new file mode 100644
index 0000000..e27b5f2
--- /dev/null
+++ b/include/perfetto/tracing/core/sys_stats_counters.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/*******************************************************************************
+ * AUTOGENERATED - DO NOT EDIT
+ *******************************************************************************
+ * This file has been generated from the protobuf message
+ * perfetto/common/sys_stats_counters.proto
+ * by
+ * ../../tools/proto_to_cpp/proto_to_cpp.cc.
+ * If you need to make changes here, change the .proto file and then run
+ * ./tools/gen_tracing_cpp_headers_from_protos.py
+ */
+
+#ifndef INCLUDE_PERFETTO_TRACING_CORE_SYS_STATS_COUNTERS_H_
+#define INCLUDE_PERFETTO_TRACING_CORE_SYS_STATS_COUNTERS_H_
+
+#include <stdint.h>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "perfetto/base/export.h"
+
+// Forward declarations for protobuf types.
+namespace perfetto {
+namespace protos {}
+}  // namespace perfetto
+
+namespace perfetto {}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_CORE_SYS_STATS_COUNTERS_H_
diff --git a/include/perfetto/tracing/core/test_config.h b/include/perfetto/tracing/core/test_config.h
index 4fdfe8e..24e272d 100644
--- a/include/perfetto/tracing/core/test_config.h
+++ b/include/perfetto/tracing/core/test_config.h
@@ -89,4 +89,5 @@
 };
 
 }  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_TEST_CONFIG_H_
diff --git a/include/perfetto/tracing/core/trace_config.h b/include/perfetto/tracing/core/trace_config.h
index d94b72a..df0a89e 100644
--- a/include/perfetto/tracing/core/trace_config.h
+++ b/include/perfetto/tracing/core/trace_config.h
@@ -49,6 +49,7 @@
 class InodeFileConfig;
 class InodeFileConfig_MountPointMappingEntry;
 class ProcessStatsConfig;
+class SysStatsConfig;
 class TestConfig;
 class TraceConfig_ProducerConfig;
 class TraceConfig_StatsdMetadata;
@@ -319,4 +320,5 @@
 };
 
 }  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_TRACE_CONFIG_H_
diff --git a/protos/perfetto/common/BUILD.gn b/protos/perfetto/common/BUILD.gn
index 9a07e5a..38603fd 100644
--- a/protos/perfetto/common/BUILD.gn
+++ b/protos/perfetto/common/BUILD.gn
@@ -14,14 +14,25 @@
 
 import("../../../gn/perfetto.gni")
 import("../../../gn/proto_library.gni")
+import("../../../src/protozero/protozero_library.gni")
+
+common_sources = [
+  "commit_data_request.proto",
+  "sys_stats_counters.proto",
+]
 
 # Proto messages that are required by the IPC service definitions but have also
 # a C++ counterpart in tracing/core (i.e. are used also for the non-IPC cases).
-proto_library("common") {
+proto_library("lite") {
   generate_python = false
   proto_in_dir = "$perfetto_root_path/protos"
   proto_out_dir = "$perfetto_root_path/protos"
-  sources = [
-    "commit_data_request.proto",
-  ]
+  sources = common_sources
+}
+
+protozero_library("zero") {
+  proto_in_dir = "$perfetto_root_path/protos"
+  proto_out_dir = "$perfetto_root_path/protos"
+  sources = common_sources
+  generator_plugin_options = "wrapper_namespace=pbzero"
 }
diff --git a/protos/perfetto/common/sys_stats_counters.proto b/protos/perfetto/common/sys_stats_counters.proto
new file mode 100644
index 0000000..2e482e7
--- /dev/null
+++ b/protos/perfetto/common/sys_stats_counters.proto
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package perfetto.protos;
+
+// When editing entries here remember also to update "sys_stats_counters.h" with
+// the corresponding string definitions for the actual /proc files parser.
+
+// Counter definitions for Linux's /proc/meminfo.
+enum MeminfoCounters {
+  MEMINFO_UNSPECIFIED = 0;
+  MEMINFO_MEM_TOTAL = 1;
+  MEMINFO_MEM_FREE = 2;
+  MEMINFO_MEM_AVAILABLE = 3;
+  MEMINFO_BUFFERS = 4;
+  MEMINFO_CACHED = 5;
+  MEMINFO_SWAP_CACHED = 6;
+  MEMINFO_ACTIVE = 7;
+  MEMINFO_INACTIVE = 8;
+  MEMINFO_ACTIVE_ANON = 9;
+  MEMINFO_INACTIVE_ANON = 10;
+  MEMINFO_ACTIVE_FILE = 11;
+  MEMINFO_INACTIVE_FILE = 12;
+  MEMINFO_UNEVICTABLE = 13;
+  MEMINFO_MLOCKED = 14;
+  MEMINFO_SWAP_TOTAL = 15;
+  MEMINFO_SWAP_FREE = 16;
+  MEMINFO_DIRTY = 17;
+  MEMINFO_WRITEBACK = 18;
+  MEMINFO_ANON_PAGES = 19;
+  MEMINFO_MAPPED = 20;
+  MEMINFO_SHMEM = 21;
+  MEMINFO_SLAB = 22;
+  MEMINFO_SLAB_RECLAIMABLE = 23;
+  MEMINFO_SLAB_UNRECLAIMABLE = 24;
+  MEMINFO_KERNEL_STACK = 25;
+  MEMINFO_PAGE_TABLES = 26;
+  MEMINFO_COMMIT_LIMIT = 27;
+  MEMINFO_COMMITED_AS = 28;
+  MEMINFO_VMALLOC_TOTAL = 29;
+  MEMINFO_VMALLOC_USED = 30;
+  MEMINFO_VMALLOC_CHUNK = 31;
+  MEMINFO_CMA_TOTAL = 32;
+  MEMINFO_CMA_FREE = 33;
+}
+
+// Counter definitions for Linux's /proc/vmstat.
+enum VmstatCounters {
+  VMSTAT_UNSPECIFIED = 0;
+  VMSTAT_NR_FREE_PAGES = 1;
+  VMSTAT_NR_ALLOC_BATCH = 2;
+  VMSTAT_NR_INACTIVE_ANON = 3;
+  VMSTAT_NR_ACTIVE_ANON = 4;
+  VMSTAT_NR_INACTIVE_FILE = 5;
+  VMSTAT_NR_ACTIVE_FILE = 6;
+  VMSTAT_NR_UNEVICTABLE = 7;
+  VMSTAT_NR_MLOCK = 8;
+  VMSTAT_NR_ANON_PAGES = 9;
+  VMSTAT_NR_MAPPED = 10;
+  VMSTAT_NR_FILE_PAGES = 11;
+  VMSTAT_NR_DIRTY = 12;
+  VMSTAT_NR_WRITEBACK = 13;
+  VMSTAT_NR_SLAB_RECLAIMABLE = 14;
+  VMSTAT_NR_SLAB_UNRECLAIMABLE = 15;
+  VMSTAT_NR_PAGE_TABLE_PAGES = 16;
+  VMSTAT_NR_KERNEL_STACK = 17;
+  VMSTAT_NR_OVERHEAD = 18;
+  VMSTAT_NR_UNSTABLE = 19;
+  VMSTAT_NR_BOUNCE = 20;
+  VMSTAT_NR_VMSCAN_WRITE = 21;
+  VMSTAT_NR_VMSCAN_IMMEDIATE_RECLAIM = 22;
+  VMSTAT_NR_WRITEBACK_TEMP = 23;
+  VMSTAT_NR_ISOLATED_ANON = 24;
+  VMSTAT_NR_ISOLATED_FILE = 25;
+  VMSTAT_NR_SHMEM = 26;
+  VMSTAT_NR_DIRTIED = 27;
+  VMSTAT_NR_WRITTEN = 28;
+  VMSTAT_NR_PAGES_SCANNED = 29;
+  VMSTAT_WORKINGSET_REFAULT = 30;
+  VMSTAT_WORKINGSET_ACTIVATE = 31;
+  VMSTAT_WORKINGSET_NODERECLAIM = 32;
+  VMSTAT_NR_ANON_TRANSPARENT_HUGEPAGES = 33;
+  VMSTAT_NR_FREE_CMA = 34;
+  VMSTAT_NR_SWAPCACHE = 35;
+  VMSTAT_NR_DIRTY_THRESHOLD = 36;
+  VMSTAT_NR_DIRTY_BACKGROUND_THRESHOLD = 37;
+  VMSTAT_PGPGIN = 38;
+  VMSTAT_PGPGOUT = 39;
+  VMSTAT_PGPGOUTCLEAN = 40;
+  VMSTAT_PSWPIN = 41;
+  VMSTAT_PSWPOUT = 42;
+  VMSTAT_PGALLOC_DMA = 43;
+  VMSTAT_PGALLOC_NORMAL = 44;
+  VMSTAT_PGALLOC_MOVABLE = 45;
+  VMSTAT_PGFREE = 46;
+  VMSTAT_PGACTIVATE = 47;
+  VMSTAT_PGDEACTIVATE = 48;
+  VMSTAT_PGFAULT = 49;
+  VMSTAT_PGMAJFAULT = 50;
+  VMSTAT_PGREFILL_DMA = 51;
+  VMSTAT_PGREFILL_NORMAL = 52;
+  VMSTAT_PGREFILL_MOVABLE = 53;
+  VMSTAT_PGSTEAL_KSWAPD_DMA = 54;
+  VMSTAT_PGSTEAL_KSWAPD_NORMAL = 55;
+  VMSTAT_PGSTEAL_KSWAPD_MOVABLE = 56;
+  VMSTAT_PGSTEAL_DIRECT_DMA = 57;
+  VMSTAT_PGSTEAL_DIRECT_NORMAL = 58;
+  VMSTAT_PGSTEAL_DIRECT_MOVABLE = 59;
+  VMSTAT_PGSCAN_KSWAPD_DMA = 60;
+  VMSTAT_PGSCAN_KSWAPD_NORMAL = 61;
+  VMSTAT_PGSCAN_KSWAPD_MOVABLE = 62;
+  VMSTAT_PGSCAN_DIRECT_DMA = 63;
+  VMSTAT_PGSCAN_DIRECT_NORMAL = 64;
+  VMSTAT_PGSCAN_DIRECT_MOVABLE = 65;
+  VMSTAT_PGSCAN_DIRECT_THROTTLE = 66;
+  VMSTAT_PGINODESTEAL = 67;
+  VMSTAT_SLABS_SCANNED = 68;
+  VMSTAT_KSWAPD_INODESTEAL = 69;
+  VMSTAT_KSWAPD_LOW_WMARK_HIT_QUICKLY = 70;
+  VMSTAT_KSWAPD_HIGH_WMARK_HIT_QUICKLY = 71;
+  VMSTAT_PAGEOUTRUN = 72;
+  VMSTAT_ALLOCSTALL = 73;
+  VMSTAT_PGROTATED = 74;
+  VMSTAT_DROP_PAGECACHE = 75;
+  VMSTAT_DROP_SLAB = 76;
+  VMSTAT_PGMIGRATE_SUCCESS = 77;
+  VMSTAT_PGMIGRATE_FAIL = 78;
+  VMSTAT_COMPACT_MIGRATE_SCANNED = 79;
+  VMSTAT_COMPACT_FREE_SCANNED = 80;
+  VMSTAT_COMPACT_ISOLATED = 81;
+  VMSTAT_COMPACT_STALL = 82;
+  VMSTAT_COMPACT_FAIL = 83;
+  VMSTAT_COMPACT_SUCCESS = 84;
+  VMSTAT_COMPACT_DAEMON_WAKE = 85;
+  VMSTAT_UNEVICTABLE_PGS_CULLED = 86;
+  VMSTAT_UNEVICTABLE_PGS_SCANNED = 87;
+  VMSTAT_UNEVICTABLE_PGS_RESCUED = 88;
+  VMSTAT_UNEVICTABLE_PGS_MLOCKED = 89;
+  VMSTAT_UNEVICTABLE_PGS_MUNLOCKED = 90;
+  VMSTAT_UNEVICTABLE_PGS_CLEARED = 91;
+  VMSTAT_UNEVICTABLE_PGS_STRANDED = 92;
+}
\ No newline at end of file
diff --git a/protos/perfetto/config/BUILD.gn b/protos/perfetto/config/BUILD.gn
index 74fa6c1..db89822 100644
--- a/protos/perfetto/config/BUILD.gn
+++ b/protos/perfetto/config/BUILD.gn
@@ -16,10 +16,13 @@
 import("../../../gn/proto_library.gni")
 import("../../../src/protozero/protozero_library.gni")
 
-proto_library("config") {
+proto_library("lite") {
   generate_python = false
   proto_in_dir = "$perfetto_root_path/protos"
   proto_out_dir = "$perfetto_root_path/protos"
+  deps = [
+    "../common:lite",
+  ]
   sources = [
     "chrome/chrome_config.proto",
     "data_source_config.proto",
@@ -27,14 +30,18 @@
     "ftrace/ftrace_config.proto",
     "inode_file/inode_file_config.proto",
     "process_stats/process_stats_config.proto",
+    "sys_stats/sys_stats_config.proto",
     "test_config.proto",
     "trace_config.proto",
   ]
 }
 
-protozero_library("config_zero") {
+protozero_library("zero") {
   proto_in_dir = "$perfetto_root_path/protos"
   proto_out_dir = "$perfetto_root_path/protos"
+  deps = [
+    "../common:zero",
+  ]
   sources = [
     "chrome/chrome_config.proto",
     "data_source_config.proto",
@@ -42,6 +49,7 @@
     "ftrace/ftrace_config.proto",
     "inode_file/inode_file_config.proto",
     "process_stats/process_stats_config.proto",
+    "sys_stats/sys_stats_config.proto",
     "test_config.proto",
     "trace_config.proto",
   ]
diff --git a/protos/perfetto/config/data_source_config.proto b/protos/perfetto/config/data_source_config.proto
index 35b8fe1..1688179 100644
--- a/protos/perfetto/config/data_source_config.proto
+++ b/protos/perfetto/config/data_source_config.proto
@@ -23,7 +23,9 @@
 import "perfetto/config/ftrace/ftrace_config.proto";
 import "perfetto/config/inode_file/inode_file_config.proto";
 import "perfetto/config/process_stats/process_stats_config.proto";
+import "perfetto/config/sys_stats/sys_stats_config.proto";
 import "perfetto/config/test_config.proto";
+
 // When editing this file run ./tools/gen_tracing_cpp_headers_from_protos.py
 // to reflect changes in the corresponding C++ headers.
 
@@ -60,6 +62,7 @@
   optional ChromeConfig chrome_config = 101;
   optional InodeFileConfig inode_file_config = 102;
   optional ProcessStatsConfig process_stats_config = 103;
+  optional SysStatsConfig sys_stats_config = 104;
 
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index e99a626..91f4aad 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -11,6 +11,147 @@
 
 package perfetto.protos;
 
+// Begin of protos/perfetto/common/sys_stats_counters.proto
+
+// When editing entries here remember also to update "sys_stats_counters.h" with
+// the corresponding string definitions for the actual /proc files parser.
+
+// Counter definitions for Linux's /proc/meminfo.
+enum MeminfoCounters {
+  MEMINFO_UNSPECIFIED = 0;
+  MEMINFO_MEM_TOTAL = 1;
+  MEMINFO_MEM_FREE = 2;
+  MEMINFO_MEM_AVAILABLE = 3;
+  MEMINFO_BUFFERS = 4;
+  MEMINFO_CACHED = 5;
+  MEMINFO_SWAP_CACHED = 6;
+  MEMINFO_ACTIVE = 7;
+  MEMINFO_INACTIVE = 8;
+  MEMINFO_ACTIVE_ANON = 9;
+  MEMINFO_INACTIVE_ANON = 10;
+  MEMINFO_ACTIVE_FILE = 11;
+  MEMINFO_INACTIVE_FILE = 12;
+  MEMINFO_UNEVICTABLE = 13;
+  MEMINFO_MLOCKED = 14;
+  MEMINFO_SWAP_TOTAL = 15;
+  MEMINFO_SWAP_FREE = 16;
+  MEMINFO_DIRTY = 17;
+  MEMINFO_WRITEBACK = 18;
+  MEMINFO_ANON_PAGES = 19;
+  MEMINFO_MAPPED = 20;
+  MEMINFO_SHMEM = 21;
+  MEMINFO_SLAB = 22;
+  MEMINFO_SLAB_RECLAIMABLE = 23;
+  MEMINFO_SLAB_UNRECLAIMABLE = 24;
+  MEMINFO_KERNEL_STACK = 25;
+  MEMINFO_PAGE_TABLES = 26;
+  MEMINFO_COMMIT_LIMIT = 27;
+  MEMINFO_COMMITED_AS = 28;
+  MEMINFO_VMALLOC_TOTAL = 29;
+  MEMINFO_VMALLOC_USED = 30;
+  MEMINFO_VMALLOC_CHUNK = 31;
+  MEMINFO_CMA_TOTAL = 32;
+  MEMINFO_CMA_FREE = 33;
+}
+
+// Counter definitions for Linux's /proc/vmstat.
+enum VmstatCounters {
+  VMSTAT_UNSPECIFIED = 0;
+  VMSTAT_NR_FREE_PAGES = 1;
+  VMSTAT_NR_ALLOC_BATCH = 2;
+  VMSTAT_NR_INACTIVE_ANON = 3;
+  VMSTAT_NR_ACTIVE_ANON = 4;
+  VMSTAT_NR_INACTIVE_FILE = 5;
+  VMSTAT_NR_ACTIVE_FILE = 6;
+  VMSTAT_NR_UNEVICTABLE = 7;
+  VMSTAT_NR_MLOCK = 8;
+  VMSTAT_NR_ANON_PAGES = 9;
+  VMSTAT_NR_MAPPED = 10;
+  VMSTAT_NR_FILE_PAGES = 11;
+  VMSTAT_NR_DIRTY = 12;
+  VMSTAT_NR_WRITEBACK = 13;
+  VMSTAT_NR_SLAB_RECLAIMABLE = 14;
+  VMSTAT_NR_SLAB_UNRECLAIMABLE = 15;
+  VMSTAT_NR_PAGE_TABLE_PAGES = 16;
+  VMSTAT_NR_KERNEL_STACK = 17;
+  VMSTAT_NR_OVERHEAD = 18;
+  VMSTAT_NR_UNSTABLE = 19;
+  VMSTAT_NR_BOUNCE = 20;
+  VMSTAT_NR_VMSCAN_WRITE = 21;
+  VMSTAT_NR_VMSCAN_IMMEDIATE_RECLAIM = 22;
+  VMSTAT_NR_WRITEBACK_TEMP = 23;
+  VMSTAT_NR_ISOLATED_ANON = 24;
+  VMSTAT_NR_ISOLATED_FILE = 25;
+  VMSTAT_NR_SHMEM = 26;
+  VMSTAT_NR_DIRTIED = 27;
+  VMSTAT_NR_WRITTEN = 28;
+  VMSTAT_NR_PAGES_SCANNED = 29;
+  VMSTAT_WORKINGSET_REFAULT = 30;
+  VMSTAT_WORKINGSET_ACTIVATE = 31;
+  VMSTAT_WORKINGSET_NODERECLAIM = 32;
+  VMSTAT_NR_ANON_TRANSPARENT_HUGEPAGES = 33;
+  VMSTAT_NR_FREE_CMA = 34;
+  VMSTAT_NR_SWAPCACHE = 35;
+  VMSTAT_NR_DIRTY_THRESHOLD = 36;
+  VMSTAT_NR_DIRTY_BACKGROUND_THRESHOLD = 37;
+  VMSTAT_PGPGIN = 38;
+  VMSTAT_PGPGOUT = 39;
+  VMSTAT_PGPGOUTCLEAN = 40;
+  VMSTAT_PSWPIN = 41;
+  VMSTAT_PSWPOUT = 42;
+  VMSTAT_PGALLOC_DMA = 43;
+  VMSTAT_PGALLOC_NORMAL = 44;
+  VMSTAT_PGALLOC_MOVABLE = 45;
+  VMSTAT_PGFREE = 46;
+  VMSTAT_PGACTIVATE = 47;
+  VMSTAT_PGDEACTIVATE = 48;
+  VMSTAT_PGFAULT = 49;
+  VMSTAT_PGMAJFAULT = 50;
+  VMSTAT_PGREFILL_DMA = 51;
+  VMSTAT_PGREFILL_NORMAL = 52;
+  VMSTAT_PGREFILL_MOVABLE = 53;
+  VMSTAT_PGSTEAL_KSWAPD_DMA = 54;
+  VMSTAT_PGSTEAL_KSWAPD_NORMAL = 55;
+  VMSTAT_PGSTEAL_KSWAPD_MOVABLE = 56;
+  VMSTAT_PGSTEAL_DIRECT_DMA = 57;
+  VMSTAT_PGSTEAL_DIRECT_NORMAL = 58;
+  VMSTAT_PGSTEAL_DIRECT_MOVABLE = 59;
+  VMSTAT_PGSCAN_KSWAPD_DMA = 60;
+  VMSTAT_PGSCAN_KSWAPD_NORMAL = 61;
+  VMSTAT_PGSCAN_KSWAPD_MOVABLE = 62;
+  VMSTAT_PGSCAN_DIRECT_DMA = 63;
+  VMSTAT_PGSCAN_DIRECT_NORMAL = 64;
+  VMSTAT_PGSCAN_DIRECT_MOVABLE = 65;
+  VMSTAT_PGSCAN_DIRECT_THROTTLE = 66;
+  VMSTAT_PGINODESTEAL = 67;
+  VMSTAT_SLABS_SCANNED = 68;
+  VMSTAT_KSWAPD_INODESTEAL = 69;
+  VMSTAT_KSWAPD_LOW_WMARK_HIT_QUICKLY = 70;
+  VMSTAT_KSWAPD_HIGH_WMARK_HIT_QUICKLY = 71;
+  VMSTAT_PAGEOUTRUN = 72;
+  VMSTAT_ALLOCSTALL = 73;
+  VMSTAT_PGROTATED = 74;
+  VMSTAT_DROP_PAGECACHE = 75;
+  VMSTAT_DROP_SLAB = 76;
+  VMSTAT_PGMIGRATE_SUCCESS = 77;
+  VMSTAT_PGMIGRATE_FAIL = 78;
+  VMSTAT_COMPACT_MIGRATE_SCANNED = 79;
+  VMSTAT_COMPACT_FREE_SCANNED = 80;
+  VMSTAT_COMPACT_ISOLATED = 81;
+  VMSTAT_COMPACT_STALL = 82;
+  VMSTAT_COMPACT_FAIL = 83;
+  VMSTAT_COMPACT_SUCCESS = 84;
+  VMSTAT_COMPACT_DAEMON_WAKE = 85;
+  VMSTAT_UNEVICTABLE_PGS_CULLED = 86;
+  VMSTAT_UNEVICTABLE_PGS_SCANNED = 87;
+  VMSTAT_UNEVICTABLE_PGS_RESCUED = 88;
+  VMSTAT_UNEVICTABLE_PGS_MLOCKED = 89;
+  VMSTAT_UNEVICTABLE_PGS_MUNLOCKED = 90;
+  VMSTAT_UNEVICTABLE_PGS_CLEARED = 91;
+  VMSTAT_UNEVICTABLE_PGS_STRANDED = 92;
+}
+// End of protos/perfetto/common/sys_stats_counters.proto
+
 // Begin of protos/perfetto/config/chrome/chrome_config.proto
 
 // When editing this file run ./tools/gen_tracing_cpp_headers_from_protos.py
@@ -22,6 +163,79 @@
 
 // End of protos/perfetto/config/chrome/chrome_config.proto
 
+// Begin of protos/perfetto/config/data_source_config.proto
+
+
+// When editing this file run ./tools/gen_tracing_cpp_headers_from_protos.py
+// to reflect changes in the corresponding C++ headers.
+
+// The configuration that is passed to each data source when starting tracing.
+message DataSourceConfig {
+  // Data source unique name, e.g., "linux.ftrace". This must match
+  // the name passed by the data source when it registers (see
+  // RegisterDataSource()).
+  optional string name = 1;
+
+  // The index of the logging buffer where TracePacket(s) will be stored.
+  // This field doesn't make a major difference for the Producer(s). The final
+  // logging buffers, in fact, are completely owned by the Service. We just ask
+  // the Producer to copy this number into the chunk headers it emits, so that
+  // the Service can quickly identify the buffer where to move the chunks into
+  // without expensive lookups on its fastpath.
+  optional uint32 target_buffer = 2;
+
+  // Set by the service to indicate the duration of the trace.
+  // DO NOT SET in consumer as this will be overridden by the service.
+  optional uint32 trace_duration_ms = 3;
+
+  // Set by the service to indicate which tracing session the data source
+  // belongs to. The intended use case for this is checking if two data sources,
+  // one of which produces metadata for the other one, belong to the same trace
+  // session and hence should be linked together.
+  // This field was introduced in Aug 2018 after Android P.
+  optional uint64 tracing_session_id = 4;
+
+  // Keeep the lower IDs (up to 99) for fields that are *not* specific to
+  // data-sources and needs to be processed by the traced daemon.
+
+  optional FtraceConfig ftrace_config = 100;
+  optional ChromeConfig chrome_config = 101;
+  optional InodeFileConfig inode_file_config = 102;
+  optional ProcessStatsConfig process_stats_config = 103;
+  optional SysStatsConfig sys_stats_config = 104;
+
+  // This is a fallback mechanism to send a free-form text config to the
+  // producer. In theory this should never be needed. All the code that
+  // is part of the platform (i.e. traced service) is supposed to *not* truncate
+  // the trace config proto and propagate unknown fields. However, if anything
+  // in the pipeline (client or backend) ends up breaking this forward compat
+  // plan, this field will become the escape hatch to allow future data sources
+  // to get some meaningful configuration.
+  optional string legacy_config = 1000;
+
+  // This field is only used for testing.
+  optional TestConfig for_testing =
+      536870911;  // 2^29 - 1, max field id for protos.
+}
+
+// End of protos/perfetto/config/data_source_config.proto
+
+// Begin of protos/perfetto/config/ftrace/ftrace_config.proto
+
+// When editing this file run ./tools/gen_tracing_cpp_headers_from_protos.py
+// to reflect changes in the corresponding C++ headers.
+
+message FtraceConfig {
+  repeated string ftrace_events = 1;
+  repeated string atrace_categories = 2;
+  repeated string atrace_apps = 3;
+  // *Per-CPU* buffer size.
+  optional uint32 buffer_size_kb = 10;
+  optional uint32 drain_period_ms = 11;
+}
+
+// End of protos/perfetto/config/ftrace/ftrace_config.proto
+
 // Begin of protos/perfetto/config/inode_file/inode_file_config.proto
 
 // When editing this file run ./tools/gen_tracing_cpp_headers_from_protos.py
@@ -85,76 +299,45 @@
 
 // End of protos/perfetto/config/process_stats/process_stats_config.proto
 
-// Begin of protos/perfetto/config/data_source_config.proto
+// Begin of protos/perfetto/config/sys_stats/sys_stats_config.proto
+
 
 // When editing this file run ./tools/gen_tracing_cpp_headers_from_protos.py
 // to reflect changes in the corresponding C++ headers.
 
-// The configuration that is passed to each data source when starting tracing.
-message DataSourceConfig {
-  // Data source unique name, e.g., "linux.ftrace". This must match
-  // the name passed by the data source when it registers (see
-  // RegisterDataSource()).
-  optional string name = 1;
+// This file defines the configuration for the Linux /proc poller data source,
+// which injects counters in the trace.
+// Counters that are needed in the trace must be explicitly listed in the
+// *_counters fields. This is to avoid spamming the trace with all counters
+// at all times.
+// The sampling rate is configurable. All polling rates (*_period_ms) need
+// to be integer multiples of each other.
+// OK:     [10ms, 10ms, 10ms],  [10ms, 20ms, 10ms],  [10ms, 20ms, 60ms]
+// Not OK: [10ms, 10ms, 11ms],  [10ms, 15ms, 20ms]
+message SysStatsConfig {
+  // Polls /proc/meminfo every X ms, if non-zero.
+  optional uint32 meminfo_period_ms = 1;
 
-  // The index of the logging buffer where TracePacket(s) will be stored.
-  // This field doesn't make a major difference for the Producer(s). The final
-  // logging buffers, in fact, are completely owned by the Service. We just ask
-  // the Producer to copy this number into the chunk headers it emits, so that
-  // the Service can quickly identify the buffer where to move the chunks into
-  // without expensive lookups on its fastpath.
-  optional uint32 target_buffer = 2;
+  // Only the counters specified below are reported.
+  repeated MeminfoCounters meminfo_counters = 2;
 
-  // Set by the service to indicate the duration of the trace.
-  // DO NOT SET in consumer as this will be overridden by the service.
-  optional uint32 trace_duration_ms = 3;
+  // Polls /proc/vmstat every X ms, if non-zero.
+  optional uint32 vmstat_period_ms = 3;
+  repeated VmstatCounters vmstat_counters = 4;
 
-  // Set by the service to indicate which tracing session the data source
-  // belongs to. The intended use case for this is checking if two data sources,
-  // one of which produces metadata for the other one, belong to the same trace
-  // session and hence should be linked together.
-  // This field was introduced in Aug 2018 after Android P.
-  optional uint64 tracing_session_id = 4;
-
-  // Keeep the lower IDs (up to 99) for fields that are *not* specific to
-  // data-sources and needs to be processed by the traced daemon.
-
-  optional FtraceConfig ftrace_config = 100;
-  optional ChromeConfig chrome_config = 101;
-  optional InodeFileConfig inode_file_config = 102;
-  optional ProcessStatsConfig process_stats_config = 103;
-
-  // This is a fallback mechanism to send a free-form text config to the
-  // producer. In theory this should never be needed. All the code that
-  // is part of the platform (i.e. traced service) is supposed to *not* truncate
-  // the trace config proto and propagate unknown fields. However, if anything
-  // in the pipeline (client or backend) ends up breaking this forward compat
-  // plan, this field will become the escape hatch to allow future data sources
-  // to get some meaningful configuration.
-  optional string legacy_config = 1000;
-
-  // This field is only used for testing.
-  optional TestConfig for_testing =
-      536870911;  // 2^29 - 1, max field id for protos.
+  // Pols /proc/stat every X ms, if non-zero.
+  optional uint32 stat_period_ms = 5;
+  enum StatCounters {
+    STAT_UNSPECIFIED = 0;
+    STAT_CPU_TIMES = 1;
+    STAT_IRQ_COUNTS = 2;
+    STAT_SOFTIRQ_COUNTS = 3;
+    STAT_FORK_COUNT = 4;
+  }
+  repeated StatCounters stat_counters = 6;
 }
 
-// End of protos/perfetto/config/data_source_config.proto
-
-// Begin of protos/perfetto/config/ftrace/ftrace_config.proto
-
-// When editing this file run ./tools/gen_tracing_cpp_headers_from_protos.py
-// to reflect changes in the corresponding C++ headers.
-
-message FtraceConfig {
-  repeated string ftrace_events = 1;
-  repeated string atrace_categories = 2;
-  repeated string atrace_apps = 3;
-  // *Per-CPU* buffer size.
-  optional uint32 buffer_size_kb = 10;
-  optional uint32 drain_period_ms = 11;
-}
-
-// End of protos/perfetto/config/ftrace/ftrace_config.proto
+// End of protos/perfetto/config/sys_stats/sys_stats_config.proto
 
 // Begin of protos/perfetto/config/test_config.proto
 
diff --git a/protos/perfetto/config/sys_stats/sys_stats_config.proto b/protos/perfetto/config/sys_stats/sys_stats_config.proto
new file mode 100644
index 0000000..ef958b3
--- /dev/null
+++ b/protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package perfetto.protos;
+
+import "perfetto/common/sys_stats_counters.proto";
+
+// When editing this file run ./tools/gen_tracing_cpp_headers_from_protos.py
+// to reflect changes in the corresponding C++ headers.
+
+// This file defines the configuration for the Linux /proc poller data source,
+// which injects counters in the trace.
+// Counters that are needed in the trace must be explicitly listed in the
+// *_counters fields. This is to avoid spamming the trace with all counters
+// at all times.
+// The sampling rate is configurable. All polling rates (*_period_ms) need
+// to be integer multiples of each other.
+// OK:     [10ms, 10ms, 10ms],  [10ms, 20ms, 10ms],  [10ms, 20ms, 60ms]
+// Not OK: [10ms, 10ms, 11ms],  [10ms, 15ms, 20ms]
+message SysStatsConfig {
+  // Polls /proc/meminfo every X ms, if non-zero.
+  optional uint32 meminfo_period_ms = 1;
+
+  // Only the counters specified below are reported.
+  repeated MeminfoCounters meminfo_counters = 2;
+
+  // Polls /proc/vmstat every X ms, if non-zero.
+  optional uint32 vmstat_period_ms = 3;
+  repeated VmstatCounters vmstat_counters = 4;
+
+  // Pols /proc/stat every X ms, if non-zero.
+  optional uint32 stat_period_ms = 5;
+  enum StatCounters {
+    STAT_UNSPECIFIED = 0;
+    STAT_CPU_TIMES = 1;
+    STAT_IRQ_COUNTS = 2;
+    STAT_SOFTIRQ_COUNTS = 3;
+    STAT_FORK_COUNT = 4;
+  }
+  repeated StatCounters stat_counters = 6;
+}
diff --git a/protos/perfetto/ipc/BUILD.gn b/protos/perfetto/ipc/BUILD.gn
index e9055be..cd78bc1 100644
--- a/protos/perfetto/ipc/BUILD.gn
+++ b/protos/perfetto/ipc/BUILD.gn
@@ -18,8 +18,8 @@
 # IPC service definitions.
 ipc_library("ipc") {
   deps = [
-    "../common",
-    "../config",
+    "../common:lite",
+    "../config:lite",
   ]
   proto_in_dir = "$perfetto_root_path/protos"
   proto_out_dir = "$perfetto_root_path/protos"
diff --git a/protos/perfetto/trace/BUILD.gn b/protos/perfetto/trace/BUILD.gn
index 60d8259..aa1fe9a 100644
--- a/protos/perfetto/trace/BUILD.gn
+++ b/protos/perfetto/trace/BUILD.gn
@@ -34,11 +34,12 @@
 # Protozero generated stubs, for writers.
 protozero_library("zero") {
   deps = [
-    "../config:config_zero",
+    "../config:zero",
     "chrome:zero",
     "filesystem:zero",
     "ftrace:zero",
     "ps:zero",
+    "sys_stats:zero",
   ]
   sources = proto_sources_minimal + proto_sources
   proto_in_dir = "$perfetto_root_path/protos"
@@ -51,11 +52,12 @@
   generate_python = false
   deps = [
     ":minimal_lite",
-    "../config:config",
+    "../config:lite",
     "chrome:lite",
     "filesystem:lite",
     "ftrace:lite",
     "ps:lite",
+    "sys_stats:lite",
   ]
   if (!build_with_chromium) {
     generate_descriptor = "$perfetto_root_path/protos/trace/trace.descriptor"
@@ -69,7 +71,7 @@
 proto_library("minimal_lite") {
   generate_python = false
   deps = [
-    "../config:config",
+    "../config:lite",
   ]
   sources = proto_sources_minimal
   proto_in_dir = "$perfetto_root_path/protos"
@@ -81,7 +83,7 @@
   generate_python = false
   deps = [
     ":minimal_lite",
-    "../config:config",
+    "../config:lite",
   ]
   sources = proto_sources_trusted
   proto_in_dir = "$perfetto_root_path/protos"
diff --git a/protos/perfetto/trace/chrome/BUILD.gn b/protos/perfetto/trace/chrome/BUILD.gn
index 4a6735f..b5191a6 100644
--- a/protos/perfetto/trace/chrome/BUILD.gn
+++ b/protos/perfetto/trace/chrome/BUILD.gn
@@ -37,7 +37,7 @@
   deps = [
     ":lite",
     "../:minimal_lite",
-    "../../config:config",
+    "../../config:lite",
   ]
   sources = minimal_chrome_proto_names
   proto_in_dir = "$perfetto_root_path/protos"
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index b83ecf0..7fb6490 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -11,6 +11,147 @@
 
 package perfetto.protos;
 
+// Begin of protos/perfetto/common/sys_stats_counters.proto
+
+// When editing entries here remember also to update "sys_stats_counters.h" with
+// the corresponding string definitions for the actual /proc files parser.
+
+// Counter definitions for Linux's /proc/meminfo.
+enum MeminfoCounters {
+  MEMINFO_UNSPECIFIED = 0;
+  MEMINFO_MEM_TOTAL = 1;
+  MEMINFO_MEM_FREE = 2;
+  MEMINFO_MEM_AVAILABLE = 3;
+  MEMINFO_BUFFERS = 4;
+  MEMINFO_CACHED = 5;
+  MEMINFO_SWAP_CACHED = 6;
+  MEMINFO_ACTIVE = 7;
+  MEMINFO_INACTIVE = 8;
+  MEMINFO_ACTIVE_ANON = 9;
+  MEMINFO_INACTIVE_ANON = 10;
+  MEMINFO_ACTIVE_FILE = 11;
+  MEMINFO_INACTIVE_FILE = 12;
+  MEMINFO_UNEVICTABLE = 13;
+  MEMINFO_MLOCKED = 14;
+  MEMINFO_SWAP_TOTAL = 15;
+  MEMINFO_SWAP_FREE = 16;
+  MEMINFO_DIRTY = 17;
+  MEMINFO_WRITEBACK = 18;
+  MEMINFO_ANON_PAGES = 19;
+  MEMINFO_MAPPED = 20;
+  MEMINFO_SHMEM = 21;
+  MEMINFO_SLAB = 22;
+  MEMINFO_SLAB_RECLAIMABLE = 23;
+  MEMINFO_SLAB_UNRECLAIMABLE = 24;
+  MEMINFO_KERNEL_STACK = 25;
+  MEMINFO_PAGE_TABLES = 26;
+  MEMINFO_COMMIT_LIMIT = 27;
+  MEMINFO_COMMITED_AS = 28;
+  MEMINFO_VMALLOC_TOTAL = 29;
+  MEMINFO_VMALLOC_USED = 30;
+  MEMINFO_VMALLOC_CHUNK = 31;
+  MEMINFO_CMA_TOTAL = 32;
+  MEMINFO_CMA_FREE = 33;
+}
+
+// Counter definitions for Linux's /proc/vmstat.
+enum VmstatCounters {
+  VMSTAT_UNSPECIFIED = 0;
+  VMSTAT_NR_FREE_PAGES = 1;
+  VMSTAT_NR_ALLOC_BATCH = 2;
+  VMSTAT_NR_INACTIVE_ANON = 3;
+  VMSTAT_NR_ACTIVE_ANON = 4;
+  VMSTAT_NR_INACTIVE_FILE = 5;
+  VMSTAT_NR_ACTIVE_FILE = 6;
+  VMSTAT_NR_UNEVICTABLE = 7;
+  VMSTAT_NR_MLOCK = 8;
+  VMSTAT_NR_ANON_PAGES = 9;
+  VMSTAT_NR_MAPPED = 10;
+  VMSTAT_NR_FILE_PAGES = 11;
+  VMSTAT_NR_DIRTY = 12;
+  VMSTAT_NR_WRITEBACK = 13;
+  VMSTAT_NR_SLAB_RECLAIMABLE = 14;
+  VMSTAT_NR_SLAB_UNRECLAIMABLE = 15;
+  VMSTAT_NR_PAGE_TABLE_PAGES = 16;
+  VMSTAT_NR_KERNEL_STACK = 17;
+  VMSTAT_NR_OVERHEAD = 18;
+  VMSTAT_NR_UNSTABLE = 19;
+  VMSTAT_NR_BOUNCE = 20;
+  VMSTAT_NR_VMSCAN_WRITE = 21;
+  VMSTAT_NR_VMSCAN_IMMEDIATE_RECLAIM = 22;
+  VMSTAT_NR_WRITEBACK_TEMP = 23;
+  VMSTAT_NR_ISOLATED_ANON = 24;
+  VMSTAT_NR_ISOLATED_FILE = 25;
+  VMSTAT_NR_SHMEM = 26;
+  VMSTAT_NR_DIRTIED = 27;
+  VMSTAT_NR_WRITTEN = 28;
+  VMSTAT_NR_PAGES_SCANNED = 29;
+  VMSTAT_WORKINGSET_REFAULT = 30;
+  VMSTAT_WORKINGSET_ACTIVATE = 31;
+  VMSTAT_WORKINGSET_NODERECLAIM = 32;
+  VMSTAT_NR_ANON_TRANSPARENT_HUGEPAGES = 33;
+  VMSTAT_NR_FREE_CMA = 34;
+  VMSTAT_NR_SWAPCACHE = 35;
+  VMSTAT_NR_DIRTY_THRESHOLD = 36;
+  VMSTAT_NR_DIRTY_BACKGROUND_THRESHOLD = 37;
+  VMSTAT_PGPGIN = 38;
+  VMSTAT_PGPGOUT = 39;
+  VMSTAT_PGPGOUTCLEAN = 40;
+  VMSTAT_PSWPIN = 41;
+  VMSTAT_PSWPOUT = 42;
+  VMSTAT_PGALLOC_DMA = 43;
+  VMSTAT_PGALLOC_NORMAL = 44;
+  VMSTAT_PGALLOC_MOVABLE = 45;
+  VMSTAT_PGFREE = 46;
+  VMSTAT_PGACTIVATE = 47;
+  VMSTAT_PGDEACTIVATE = 48;
+  VMSTAT_PGFAULT = 49;
+  VMSTAT_PGMAJFAULT = 50;
+  VMSTAT_PGREFILL_DMA = 51;
+  VMSTAT_PGREFILL_NORMAL = 52;
+  VMSTAT_PGREFILL_MOVABLE = 53;
+  VMSTAT_PGSTEAL_KSWAPD_DMA = 54;
+  VMSTAT_PGSTEAL_KSWAPD_NORMAL = 55;
+  VMSTAT_PGSTEAL_KSWAPD_MOVABLE = 56;
+  VMSTAT_PGSTEAL_DIRECT_DMA = 57;
+  VMSTAT_PGSTEAL_DIRECT_NORMAL = 58;
+  VMSTAT_PGSTEAL_DIRECT_MOVABLE = 59;
+  VMSTAT_PGSCAN_KSWAPD_DMA = 60;
+  VMSTAT_PGSCAN_KSWAPD_NORMAL = 61;
+  VMSTAT_PGSCAN_KSWAPD_MOVABLE = 62;
+  VMSTAT_PGSCAN_DIRECT_DMA = 63;
+  VMSTAT_PGSCAN_DIRECT_NORMAL = 64;
+  VMSTAT_PGSCAN_DIRECT_MOVABLE = 65;
+  VMSTAT_PGSCAN_DIRECT_THROTTLE = 66;
+  VMSTAT_PGINODESTEAL = 67;
+  VMSTAT_SLABS_SCANNED = 68;
+  VMSTAT_KSWAPD_INODESTEAL = 69;
+  VMSTAT_KSWAPD_LOW_WMARK_HIT_QUICKLY = 70;
+  VMSTAT_KSWAPD_HIGH_WMARK_HIT_QUICKLY = 71;
+  VMSTAT_PAGEOUTRUN = 72;
+  VMSTAT_ALLOCSTALL = 73;
+  VMSTAT_PGROTATED = 74;
+  VMSTAT_DROP_PAGECACHE = 75;
+  VMSTAT_DROP_SLAB = 76;
+  VMSTAT_PGMIGRATE_SUCCESS = 77;
+  VMSTAT_PGMIGRATE_FAIL = 78;
+  VMSTAT_COMPACT_MIGRATE_SCANNED = 79;
+  VMSTAT_COMPACT_FREE_SCANNED = 80;
+  VMSTAT_COMPACT_ISOLATED = 81;
+  VMSTAT_COMPACT_STALL = 82;
+  VMSTAT_COMPACT_FAIL = 83;
+  VMSTAT_COMPACT_SUCCESS = 84;
+  VMSTAT_COMPACT_DAEMON_WAKE = 85;
+  VMSTAT_UNEVICTABLE_PGS_CULLED = 86;
+  VMSTAT_UNEVICTABLE_PGS_SCANNED = 87;
+  VMSTAT_UNEVICTABLE_PGS_RESCUED = 88;
+  VMSTAT_UNEVICTABLE_PGS_MLOCKED = 89;
+  VMSTAT_UNEVICTABLE_PGS_MUNLOCKED = 90;
+  VMSTAT_UNEVICTABLE_PGS_CLEARED = 91;
+  VMSTAT_UNEVICTABLE_PGS_STRANDED = 92;
+}
+// End of protos/perfetto/common/sys_stats_counters.proto
+
 // Begin of protos/perfetto/trace/trace.proto
 
 message Trace {
@@ -30,14 +171,19 @@
 // The root object emitted by Perfetto. A perfetto trace is just a stream of
 // TracePacket(s).
 //
-// Next id: 7.
+// Next id: 9.
 message TracePacket {
+  // TODO: in future we should add a timestamp_clock_domain field to
+  // allow mixing timestamps from different clock domains.
+  optional uint64 timestamp = 8;  // Timestamp [ns].
+
   oneof data {
     FtraceEventBundle ftrace_events = 1;
     ProcessTree process_tree = 2;
     InodeFileMap inode_file_map = 4;
     // removed field with id 5
-    // removed field with id 6
+    ClockSnapshot clock_snapshot = 6;
+    SysStats sys_stats = 7;
 
     // IDs up to 32 are reserved for events that are quite frequent because they
     // take only one byte to encode their preamble.
@@ -55,6 +201,7 @@
     // This field is only used for testing.
     // removed field with id 536870911  // 2^29 - 1, max field id for protos.
   }
+
   // Trusted user id of the producer which generated this packet. Keep in sync
   // with TrustedPacket.trusted_uid.
   oneof optional_trusted_uid { int32 trusted_uid = 3; };
@@ -443,6 +590,86 @@
 
 // End of protos/perfetto/trace/ps/process_tree.proto
 
+// Begin of protos/perfetto/trace/clock_snapshot.proto
+
+// A snapshot of clock readings to allow for trace alignment.
+message ClockSnapshot {
+  message Clock {
+    enum Type {
+      UNKNOWN = 0;
+      REALTIME = 1;
+      REALTIME_COARSE = 2;
+      MONOTONIC = 3;
+      MONOTONIC_COARSE = 4;
+      MONOTONIC_RAW = 5;
+      BOOTTIME = 6;
+      PROCESS_CPUTIME = 7;
+      THREAD_CPUTIME = 8;
+    }
+    optional Type type = 1;
+    optional uint64 timestamp = 2;
+  }
+  repeated Clock clocks = 1;
+}
+
+// End of protos/perfetto/trace/clock_snapshot.proto
+
+// Begin of protos/perfetto/trace/sys_stats/sys_stats.proto
+
+
+// Various Linux system stat counters from /proc.
+// The fields in this message can be reported at different rates and with
+// different granularity. See sys_stats_config.proto.
+message SysStats {
+  // Counters from /proc/meminfo. Values are in KB.
+  message MeminfoValue {
+    optional MeminfoCounters key = 1;
+    optional uint64 value = 2;
+  };
+  repeated MeminfoValue meminfo = 1;
+
+  // Counter from /proc/vmstat. Units are often pages, not KB.
+  message VmstatValue {
+    optional VmstatCounters key = 1;
+    optional uint64 value = 2;
+  };
+  repeated VmstatValue vmstat = 2;
+
+  // Times in each mode, since boot. Unit: nanoseconds.
+  message CpuTimes {
+    optional uint32 cpu_id = 1;
+    optional uint64 user_ns = 2;         // Time spent in user mode.
+    optional uint64 user_ice_ns = 3;     // Time spent in user mode (low prio).
+    optional uint64 system_mode_ns = 4;  // Time spent in system mode.
+    optional uint64 idle_ns = 5;         // Time spent in the idle task.
+    optional uint64 io_wait_ns = 6;      // Time spent waiting for I/O.
+    optional uint64 irq_ns = 7;          // Time spent servicing interrupts.
+    optional uint64 softirq_ns = 8;      // Time spent servicing softirqs.
+  }
+  repeated CpuTimes cpu_stat = 3;  // One entry per cpu.
+
+  // Num processes forked since boot.
+  // Populated only if FORK_COUNT in config.stat_counters.
+  optional uint64 num_forks = 4;
+
+  message InterruptCount {
+    optional int32 irq = 1;
+    optional uint64 count = 2;
+  }
+
+  // Number of interrupts, broken by IRQ number.
+  // Populated only if IRQ_COUNTS in config.stat_counters.
+  optional uint64 num_irq_total = 5;  // Total num of irqs serviced since boot.
+  repeated InterruptCount num_irq = 6;
+
+  // Number of softirqs, broken by softirq number.
+  // Populated only if SOFTIRQ_COUNTS in config.stat_counters.
+  optional uint64 num_softirq_total = 7;    // Total num of softirqs since boot.
+  repeated InterruptCount num_softirq = 8;  // Per-softirq count.
+}
+
+// End of protos/perfetto/trace/sys_stats/sys_stats.proto
+
 // Begin of protos/perfetto/trace/ftrace/print.proto
 
 message PrintFtraceEvent {
diff --git a/protos/perfetto/trace/sys_stats/BUILD.gn b/protos/perfetto/trace/sys_stats/BUILD.gn
new file mode 100644
index 0000000..bb7f93b
--- /dev/null
+++ b/protos/perfetto/trace/sys_stats/BUILD.gn
@@ -0,0 +1,38 @@
+# 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.
+
+import("../../../../gn/perfetto.gni")
+import("../../../../src/protozero/protozero_library.gni")
+
+sys_stats_proto_names = [ "sys_stats.proto" ]
+
+proto_library("lite") {
+  generate_python = false
+  deps = [
+    "../../common:lite",
+  ]
+  sources = sys_stats_proto_names
+  proto_in_dir = "$perfetto_root_path/protos"
+  proto_out_dir = "$perfetto_root_path/protos"
+}
+
+protozero_library("zero") {
+  deps = [
+    "../../common:zero",
+  ]
+  sources = sys_stats_proto_names
+  proto_in_dir = "$perfetto_root_path/protos"
+  proto_out_dir = "$perfetto_root_path/protos"
+  generator_plugin_options = "wrapper_namespace=pbzero"
+}
diff --git a/protos/perfetto/trace/sys_stats/sys_stats.proto b/protos/perfetto/trace/sys_stats/sys_stats.proto
new file mode 100644
index 0000000..7b298bf
--- /dev/null
+++ b/protos/perfetto/trace/sys_stats/sys_stats.proto
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+package perfetto.protos;
+
+import "perfetto/common/sys_stats_counters.proto";
+
+// Various Linux system stat counters from /proc.
+// The fields in this message can be reported at different rates and with
+// different granularity. See sys_stats_config.proto.
+message SysStats {
+  // Counters from /proc/meminfo. Values are in KB.
+  message MeminfoValue {
+    optional MeminfoCounters key = 1;
+    optional uint64 value = 2;
+  };
+  repeated MeminfoValue meminfo = 1;
+
+  // Counter from /proc/vmstat. Units are often pages, not KB.
+  message VmstatValue {
+    optional VmstatCounters key = 1;
+    optional uint64 value = 2;
+  };
+  repeated VmstatValue vmstat = 2;
+
+  // Times in each mode, since boot. Unit: nanoseconds.
+  message CpuTimes {
+    optional uint32 cpu_id = 1;
+    optional uint64 user_ns = 2;         // Time spent in user mode.
+    optional uint64 user_ice_ns = 3;     // Time spent in user mode (low prio).
+    optional uint64 system_mode_ns = 4;  // Time spent in system mode.
+    optional uint64 idle_ns = 5;         // Time spent in the idle task.
+    optional uint64 io_wait_ns = 6;      // Time spent waiting for I/O.
+    optional uint64 irq_ns = 7;          // Time spent servicing interrupts.
+    optional uint64 softirq_ns = 8;      // Time spent servicing softirqs.
+  }
+  repeated CpuTimes cpu_stat = 3;  // One entry per cpu.
+
+  // Num processes forked since boot.
+  // Populated only if FORK_COUNT in config.stat_counters.
+  optional uint64 num_forks = 4;
+
+  message InterruptCount {
+    optional int32 irq = 1;
+    optional uint64 count = 2;
+  }
+
+  // Number of interrupts, broken by IRQ number.
+  // Populated only if IRQ_COUNTS in config.stat_counters.
+  optional uint64 num_irq_total = 5;  // Total num of irqs serviced since boot.
+  repeated InterruptCount num_irq = 6;
+
+  // Number of softirqs, broken by softirq number.
+  // Populated only if SOFTIRQ_COUNTS in config.stat_counters.
+  optional uint64 num_softirq_total = 7;    // Total num of softirqs since boot.
+  repeated InterruptCount num_softirq = 8;  // Per-softirq count.
+}
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 553ade2..e6fe149 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -24,6 +24,7 @@
 import "perfetto/trace/ftrace/ftrace_event_bundle.proto";
 import "perfetto/trace/ftrace/ftrace_stats.proto";
 import "perfetto/trace/ps/process_tree.proto";
+import "perfetto/trace/sys_stats/sys_stats.proto";
 import "perfetto/trace/test_event.proto";
 import "perfetto/trace/trace_stats.proto";
 
@@ -32,14 +33,19 @@
 // The root object emitted by Perfetto. A perfetto trace is just a stream of
 // TracePacket(s).
 //
-// Next id: 7.
+// Next id: 9.
 message TracePacket {
+  // TODO(primiano): in future we should add a timestamp_clock_domain field to
+  // allow mixing timestamps from different clock domains.
+  optional uint64 timestamp = 8;  // Timestamp [ns].
+
   oneof data {
     FtraceEventBundle ftrace_events = 1;
     ProcessTree process_tree = 2;
     InodeFileMap inode_file_map = 4;
     ChromeEventBundle chrome_events = 5;
     ClockSnapshot clock_snapshot = 6;
+    SysStats sys_stats = 7;
 
     // IDs up to 32 are reserved for events that are quite frequent because they
     // take only one byte to encode their preamble.
@@ -57,6 +63,7 @@
     // This field is only used for testing.
     TestEvent for_testing = 536870911;  // 2^29 - 1, max field id for protos.
   }
+
   // Trusted user id of the producer which generated this packet. Keep in sync
   // with TrustedPacket.trusted_uid.
   oneof optional_trusted_uid { int32 trusted_uid = 3; };
diff --git a/src/perfetto_cmd/BUILD.gn b/src/perfetto_cmd/BUILD.gn
index 9e893e4..d71b1ed 100644
--- a/src/perfetto_cmd/BUILD.gn
+++ b/src/perfetto_cmd/BUILD.gn
@@ -22,7 +22,7 @@
   ]
   deps = [
     "../../gn:default_deps",
-    "../../protos/perfetto/config",
+    "../../protos/perfetto/config:lite",
     "../base",
     "../protozero",
     "../tracing:ipc_consumer",
diff --git a/src/protozero/scattered_stream_delegate_for_testing.cc b/src/protozero/scattered_stream_delegate_for_testing.cc
index 0409a06..ce474ff 100644
--- a/src/protozero/scattered_stream_delegate_for_testing.cc
+++ b/src/protozero/scattered_stream_delegate_for_testing.cc
@@ -38,23 +38,17 @@
   return {begin, begin + chunk_size_};
 }
 
-std::unique_ptr<uint8_t[]> ScatteredStreamDelegateForTesting::StitchChunks(
-    size_t size) {
-  std::unique_ptr<uint8_t[]> buffer =
-      std::unique_ptr<uint8_t[]>(new uint8_t[size]);
-  size_t remaining = size;
+std::vector<uint8_t> ScatteredStreamDelegateForTesting::StitchChunks() {
+  std::vector<uint8_t> buffer;
   size_t i = 0;
   for (const auto& chunk : chunks_) {
-    size_t chunk_size = remaining;
-    if (i < chunks_used_size_.size()) {
-      chunk_size = chunks_used_size_[i];
-    }
+    size_t chunk_size = (i < chunks_used_size_.size())
+                            ? chunks_used_size_[i]
+                            : (chunk_size_ - writer_->bytes_available());
     PERFETTO_CHECK(chunk_size <= chunk_size_);
-    memcpy(buffer.get() + size - remaining, chunk.get(), chunk_size);
-    remaining -= chunk_size;
+    buffer.insert(buffer.end(), chunk.get(), chunk.get() + chunk_size);
     i++;
   }
-
   return buffer;
 }
 
diff --git a/src/protozero/scattered_stream_delegate_for_testing.h b/src/protozero/scattered_stream_delegate_for_testing.h
index 93b6874..365ad43 100644
--- a/src/protozero/scattered_stream_delegate_for_testing.h
+++ b/src/protozero/scattered_stream_delegate_for_testing.h
@@ -35,7 +35,7 @@
   protozero::ContiguousMemoryRange GetNewBuffer() override;
 
   // Stitch all the chunks into a single contiguous buffer.
-  std::unique_ptr<uint8_t[]> StitchChunks(size_t size);
+  std::vector<uint8_t> StitchChunks();
 
   const std::vector<std::unique_ptr<uint8_t[]>>& chunks() const {
     return chunks_;
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 811443f..092c6bf 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -108,6 +108,9 @@
       "../../protos/perfetto/trace_processor:lite",
       "../base",
     ]
+    if (build_standalone) {
+      deps += [ "../../buildtools:linenoise" ]
+    }
     sources = [
       "trace_processor_shell.cc",
     ]
diff --git a/src/trace_processor/counters_table.cc b/src/trace_processor/counters_table.cc
index 86cf70c..1cc4026 100644
--- a/src/trace_processor/counters_table.cc
+++ b/src/trace_processor/counters_table.cc
@@ -42,8 +42,9 @@
          "name text, "
          "value UNSIGNED BIG INT, "
          "dur UNSIGNED BIG INT, "
+         "value_delta UNSIGNED BIG INT, "
          "ref UNSIGNED INT, "
-         "reftype TEXT, "
+         "ref_type TEXT, "
          "PRIMARY KEY(name, ts, ref)"
          ") WITHOUT ROWID;";
 }
@@ -52,54 +53,65 @@
   return std::unique_ptr<Table::Cursor>(new Cursor(storage_));
 }
 
-int CountersTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) {
-  info->estimated_cost = 10;
-
-  // If the query has a constraint on the |kRef| field, return a reduced cost
-  // because we can do that filter efficiently.
-  const auto& constraints = qc.constraints();
-  if (constraints.size() == 1 && constraints.front().iColumn == Column::kRef) {
-    info->estimated_cost = IsOpEq(constraints.front().op) ? 1 : 10;
-  }
-
+int CountersTable::BestIndex(const QueryConstraints&, BestIndexInfo* info) {
+  // TODO(taylori): Work out cost dependant on constraints.
+  info->estimated_cost =
+      static_cast<uint32_t>(storage_->counters().counter_count());
   return SQLITE_OK;
 }
 
-CountersTable::Cursor::Cursor(const TraceStorage* storage)
-    : storage_(storage) {}
+CountersTable::Cursor::Cursor(const TraceStorage* storage) : storage_(storage) {
+  num_rows_ = storage->counters().counter_count();
+}
 
 int CountersTable::Cursor::Column(sqlite3_context* context, int N) {
   switch (N) {
     case Column::kTimestamp: {
-      const auto& freq = storage_->GetFreqForCpu(current_cpu_);
-      sqlite3_result_int64(context,
-                           static_cast<int64_t>(freq[index_in_cpu_].first));
+      sqlite3_result_int64(
+          context,
+          static_cast<int64_t>(storage_->counters().timestamps()[row_]));
       break;
     }
     case Column::kValue: {
-      const auto& freq = storage_->GetFreqForCpu(current_cpu_);
-      sqlite3_result_int64(context, freq[index_in_cpu_].second);
+      sqlite3_result_int64(
+          context, static_cast<int64_t>(storage_->counters().values()[row_]));
       break;
     }
     case Column::kName: {
-      sqlite3_result_text(context, "cpufreq", -1, nullptr);
+      sqlite3_result_text(
+          context,
+          storage_->GetString(storage_->counters().name_ids()[row_]).c_str(),
+          -1, nullptr);
       break;
     }
     case Column::kRef: {
-      sqlite3_result_int64(context, current_cpu_);
+      sqlite3_result_int64(
+          context, static_cast<int64_t>(storage_->counters().refs()[row_]));
       break;
     }
     case Column::kRefType: {
-      sqlite3_result_text(context, "cpu", -1, nullptr);
+      switch (storage_->counters().types()[row_]) {
+        case RefType::kCPU_ID: {
+          sqlite3_result_text(context, "cpu", -1, nullptr);
+          break;
+        }
+        case RefType::kUTID: {
+          sqlite3_result_text(context, "utid", -1, nullptr);
+          break;
+        }
+      }
       break;
     }
     case Column::kDuration: {
-      const auto& freq = storage_->GetFreqForCpu(current_cpu_);
-      uint64_t duration = 0;
-      if (index_in_cpu_ + 1 < freq.size()) {
-        duration = freq[index_in_cpu_ + 1].first - freq[index_in_cpu_].first;
-      }
-      sqlite3_result_int64(context, static_cast<int64_t>(duration));
+      sqlite3_result_int64(
+          context,
+          static_cast<int64_t>(storage_->counters().durations()[row_]));
+      break;
+    }
+    case Column::kValueDelta: {
+      sqlite3_result_int64(
+          context,
+          static_cast<int64_t>(storage_->counters().value_deltas()[row_]));
       break;
     }
     default:
@@ -109,47 +121,17 @@
   return SQLITE_OK;
 }
 
-int CountersTable::Cursor::Filter(const QueryConstraints& qc,
-                                  sqlite3_value** argv) {
-  for (size_t j = 0; j < qc.constraints().size(); j++) {
-    const auto& cs = qc.constraints()[j];
-    if (cs.iColumn == Column::kRef) {
-      auto constraint_cpu = static_cast<uint32_t>(sqlite3_value_int(argv[j]));
-      if (IsOpEq(cs.op)) {
-        filter_by_cpu_ = true;
-        filter_cpu_ = constraint_cpu;
-      }
-    }
-  }
-
+int CountersTable::Cursor::Filter(const QueryConstraints&, sqlite3_value**) {
   return SQLITE_OK;
 }
 
 int CountersTable::Cursor::Next() {
-  if (filter_by_cpu_) {
-    current_cpu_ = filter_cpu_;
-    ++index_in_cpu_;
-  } else {
-    if (index_in_cpu_ < storage_->GetFreqForCpu(current_cpu_).size() - 1) {
-      index_in_cpu_++;
-    } else if (current_cpu_ < storage_->GetMaxCpu()) {
-      ++current_cpu_;
-      index_in_cpu_ = 0;
-    }
-    // If the cpu is has no freq events, move to the next one.
-    while (current_cpu_ != storage_->GetMaxCpu() &&
-           storage_->GetFreqForCpu(current_cpu_).size() == 0) {
-      ++current_cpu_;
-    }
-  }
+  row_++;
   return SQLITE_OK;
 }
 
 int CountersTable::Cursor::Eof() {
-  if (filter_by_cpu_) {
-    return index_in_cpu_ == storage_->GetFreqForCpu(current_cpu_).size();
-  }
-  return current_cpu_ == storage_->GetMaxCpu();
+  return row_ >= num_rows_;
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/counters_table.h b/src/trace_processor/counters_table.h
index a28f366..5ef21ba 100644
--- a/src/trace_processor/counters_table.h
+++ b/src/trace_processor/counters_table.h
@@ -33,8 +33,9 @@
     kName = 1,
     kValue = 2,
     kDuration = 3,
-    kRef = 4,
-    kRefType = 5,
+    kValueDelta = 4,
+    kRef = 5,
+    kRefType = 6,
   };
 
   static void RegisterTable(sqlite3* db, const TraceStorage* storage);
@@ -58,10 +59,8 @@
     int Column(sqlite3_context*, int N) override;
 
    private:
-    bool filter_by_cpu_ = false;
-    uint32_t current_cpu_ = 0;
-    size_t index_in_cpu_ = 0;
-    uint32_t filter_cpu_ = 0;
+    size_t num_rows_;
+    size_t row_ = 0;
 
     const TraceStorage* const storage_;
   };
diff --git a/src/trace_processor/counters_table_unittest.cc b/src/trace_processor/counters_table_unittest.cc
index 4ec5792..211cb78 100644
--- a/src/trace_processor/counters_table_unittest.cc
+++ b/src/trace_processor/counters_table_unittest.cc
@@ -15,6 +15,7 @@
  */
 
 #include "src/trace_processor/counters_table.h"
+#include "src/trace_processor/sched_tracker.h"
 #include "src/trace_processor/scoped_db.h"
 #include "src/trace_processor/trace_processor_context.h"
 
@@ -33,6 +34,7 @@
     db_.reset(db);
 
     context_.storage.reset(new TraceStorage());
+    context_.sched_tracker.reset(new SchedTracker(&context_));
 
     CountersTable::RegisterTable(db_.get(), context_.storage.get());
   }
@@ -60,20 +62,23 @@
 TEST_F(CountersTableUnittest, SelectWhereCpu) {
   uint64_t timestamp = 1000;
   uint32_t freq = 3000;
-  context_.storage->PushCpuFreq(timestamp, 1 /* cpu */, freq);
-  context_.storage->PushCpuFreq(timestamp + 1, 1 /* cpu */, freq + 1000);
-  context_.storage->PushCpuFreq(timestamp + 2, 2 /* cpu */, freq + 2000);
+  context_.storage->mutable_counters()->AddCounter(
+      timestamp, 0, 1, freq, 0, 1 /* cpu */, RefType::kCPU_ID);
+  context_.storage->mutable_counters()->AddCounter(
+      timestamp + 1, 1, 1, freq + 1000, 1000, 1 /* cpu */, RefType::kCPU_ID);
+  context_.storage->mutable_counters()->AddCounter(
+      timestamp + 2, 1, 1, freq + 2000, 1000, 2 /* cpu */, RefType::kCPU_ID);
 
   PrepareValidStatement("SELECT ts, dur, value FROM counters where ref = 1");
 
   ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
   ASSERT_EQ(sqlite3_column_int(*stmt_, 0), timestamp);
-  ASSERT_EQ(sqlite3_column_int(*stmt_, 1), 1);
+  ASSERT_EQ(sqlite3_column_int(*stmt_, 1), 0);
   ASSERT_EQ(sqlite3_column_int(*stmt_, 2), freq);
 
   ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
   ASSERT_EQ(sqlite3_column_int(*stmt_, 0), timestamp + 1);
-  ASSERT_EQ(sqlite3_column_int(*stmt_, 1), 0);
+  ASSERT_EQ(sqlite3_column_int(*stmt_, 1), 1);
   ASSERT_EQ(sqlite3_column_int(*stmt_, 2), freq + 1000);
 
   ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_DONE);
@@ -82,9 +87,16 @@
 TEST_F(CountersTableUnittest, GroupByFreq) {
   uint64_t timestamp = 1000;
   uint32_t freq = 3000;
-  context_.storage->PushCpuFreq(timestamp, 1 /* cpu */, freq);
-  context_.storage->PushCpuFreq(timestamp + 1, 1 /* cpu */, freq + 1000);
-  context_.storage->PushCpuFreq(timestamp + 3, 1 /* cpu */, freq);
+  uint32_t name_id = 1;
+  context_.storage->mutable_counters()->AddCounter(
+      timestamp, 1 /* dur */, name_id, freq, 0 /* value delta */, 1 /* cpu */,
+      RefType::kCPU_ID);
+  context_.storage->mutable_counters()->AddCounter(
+      timestamp + 1, 2 /* dur */, name_id, freq + 1000, 1000 /* value delta */,
+      1 /* cpu */, RefType::kCPU_ID);
+  context_.storage->mutable_counters()->AddCounter(
+      timestamp + 3, 0 /* dur */, name_id, freq, -1000 /* value delta */,
+      1 /* cpu */, RefType::kCPU_ID);
 
   PrepareValidStatement(
       "SELECT value, sum(dur) as dur_sum FROM counters where value > 0 group "
diff --git a/src/trace_processor/process_tracker.cc b/src/trace_processor/process_tracker.cc
index 295cf5a..2da8994 100644
--- a/src/trace_processor/process_tracker.cc
+++ b/src/trace_processor/process_tracker.cc
@@ -42,7 +42,8 @@
     auto prev_utid = std::prev(pair_it.second)->second;
     TraceStorage::Thread* thread =
         context_->storage->GetMutableThread(prev_utid);
-    thread->name_id = thread_name_id;
+    if (thread_name_id)
+      thread->name_id = thread_name_id;
     return prev_utid;
   }
 
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index 8cf1101..ac1b65c 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -16,6 +16,8 @@
 
 #include "src/trace_processor/proto_trace_parser.h"
 
+#include <string.h>
+
 #include <string>
 
 #include "perfetto/base/logging.h"
@@ -43,38 +45,53 @@
   const char* s = str.data();
   size_t len = str.size();
 
-  // If str matches '[BEC]\|[0-9]+[\|\n]' set pid_length to the length of
+  // If str matches '[BEC]\|[0-9]+[\|\n]' set tid_length to the length of
   // the number. Otherwise return false.
   if (len < 3 || s[1] != '|')
     return false;
   if (s[0] != 'B' && s[0] != 'E' && s[0] != 'C')
     return false;
-  size_t pid_length;
+  size_t tid_length;
   for (size_t i = 2;; i++) {
     if (i >= len)
       return false;
     if (s[i] == '|' || s[i] == '\n') {
-      pid_length = i - 2;
+      tid_length = i - 2;
       break;
     }
     if (s[i] < '0' || s[i] > '9')
       return false;
   }
 
-  std::string pid_str(s + 2, pid_length);
-  out->pid = static_cast<uint32_t>(std::stoi(pid_str.c_str()));
+  std::string tid_str(s + 2, tid_length);
+  out->tid = static_cast<uint32_t>(std::stoi(tid_str.c_str()));
 
   out->phase = s[0];
   switch (s[0]) {
     case 'B': {
-      size_t name_index = 2 + pid_length + 1;
+      size_t name_index = 2 + tid_length + 1;
       out->name = base::StringView(s + name_index, len - name_index);
       return true;
     }
-    case 'E':
+    case 'E': {
       return true;
-    case 'C':
+    }
+    case 'C': {
+      size_t name_index = 2 + tid_length + 1;
+      size_t name_length = 0;
+      for (size_t i = name_index; i < len; i++) {
+        if (s[i] == '|' || s[i] == '\n') {
+          name_length = i - name_index;
+          break;
+        }
+      }
+      out->name = base::StringView(s + name_index, name_length);
+      size_t value_index = name_index + name_length + 1;
+      char value_str[32];
+      strcpy(value_str, s + value_index);
+      out->value = std::stod(value_str);
       return true;
+    }
     default:
       return false;
   }
@@ -84,7 +101,8 @@
 using protozero::proto_utils::kFieldTypeLengthDelimited;
 
 ProtoTraceParser::ProtoTraceParser(TraceProcessorContext* context)
-    : context_(context) {}
+    : context_(context),
+      cpu_freq_name_id_(context->storage->InternString("cpufreq")) {}
 
 ProtoTraceParser::~ProtoTraceParser() = default;
 
@@ -204,20 +222,20 @@
 void ProtoTraceParser::ParseCpuFreq(uint64_t timestamp, TraceBlobView view) {
   ProtoDecoder decoder(view.data(), view.length());
 
-  uint32_t cpu = 0;
+  uint32_t cpu_affected = 0;
   uint32_t new_freq = 0;
   for (auto fld = decoder.ReadField(); fld.id != 0; fld = decoder.ReadField()) {
     switch (fld.id) {
       case protos::CpuFrequencyFtraceEvent::kCpuIdFieldNumber:
-        cpu = fld.as_uint32();
+        cpu_affected = fld.as_uint32();
         break;
       case protos::CpuFrequencyFtraceEvent::kStateFieldNumber:
         new_freq = fld.as_uint32();
         break;
     }
   }
-
-  context_->storage->PushCpuFreq(timestamp, cpu, new_freq);
+  context_->sched_tracker->PushCounter(timestamp, new_freq, cpu_freq_name_id_,
+                                       cpu_affected, RefType::kCPU_ID);
 
   PERFETTO_DCHECK(decoder.IsEndOfBuffer());
 }
@@ -271,19 +289,26 @@
   if (!ParseSystraceTracePoint(buf, &point))
     return;
 
-  UniquePid upid = context_->process_tracker->UpdateProcess(point.pid);
+  UniqueTid utid =
+      context_->process_tracker->UpdateThread(timestamp, point.tid, 0);
 
   switch (point.phase) {
     case 'B': {
       StringId name_id = context_->storage->InternString(point.name);
-      context_->slice_tracker->Begin(timestamp, upid, 0 /*cat_id*/, name_id);
+      context_->slice_tracker->Begin(timestamp, utid, 0 /*cat_id*/, name_id);
       break;
     }
 
     case 'E': {
-      context_->slice_tracker->End(timestamp, upid);
+      context_->slice_tracker->End(timestamp, utid);
       break;
     }
+
+    case 'C': {
+      StringId name_id = context_->storage->InternString(point.name);
+      context_->sched_tracker->PushCounter(timestamp, point.value, name_id,
+                                           utid, RefType::kUTID);
+    }
   }
   PERFETTO_DCHECK(decoder.IsEndOfBuffer());
 }
diff --git a/src/trace_processor/proto_trace_parser.h b/src/trace_processor/proto_trace_parser.h
index be3bc40..9da91e4 100644
--- a/src/trace_processor/proto_trace_parser.h
+++ b/src/trace_processor/proto_trace_parser.h
@@ -31,19 +31,19 @@
 
 struct SystraceTracePoint {
   char phase;
-  uint32_t pid;
+  uint32_t tid;
 
   // For phase = 'B' and phase = 'C' only.
   base::StringView name;
 
   // For phase = 'C' only.
-  int64_t value;
+  double value;
 };
 
 inline bool operator==(const SystraceTracePoint& x,
                        const SystraceTracePoint& y) {
-  return std::tie(x.phase, x.pid, x.name, x.value) ==
-         std::tie(y.phase, y.pid, y.name, y.value);
+  return std::tie(x.phase, x.tid, x.name, x.value) ==
+         std::tie(y.phase, y.tid, y.name, y.value);
 }
 
 bool ParseSystraceTracePoint(base::StringView, SystraceTracePoint* out);
@@ -67,6 +67,7 @@
 
  private:
   TraceProcessorContext* context_;
+  const StringId cpu_freq_name_id_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/proto_trace_parser_unittest.cc b/src/trace_processor/proto_trace_parser_unittest.cc
index 68efcdd..c0881c8 100644
--- a/src/trace_processor/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/proto_trace_parser_unittest.cc
@@ -49,6 +49,13 @@
                     uint32_t prev_state,
                     base::StringView prev_comm,
                     uint32_t next_pid));
+
+  MOCK_METHOD5(PushCounter,
+               void(uint64_t timestamp,
+                    double value,
+                    StringId name_id,
+                    uint64_t ref,
+                    RefType ref_type));
 };
 
 class MockProcessTracker : public ProcessTracker {
@@ -66,8 +73,7 @@
  public:
   MockTraceStorage() : TraceStorage() {}
 
-  MOCK_METHOD3(PushCpuFreq,
-               void(uint64_t timestamp, uint32_t cpu, uint32_t new_freq));
+  MOCK_METHOD1(InternString, StringId(base::StringView));
 };
 
 class ProtoTraceParserTest : public ::testing::Test {
@@ -236,7 +242,7 @@
   cpu_freq->set_cpu_id(10);
   cpu_freq->set_state(2000);
 
-  EXPECT_CALL(*storage_, PushCpuFreq(1000, 10, 2000));
+  EXPECT_CALL(*sched_, PushCounter(1000, 2000, 0, 10, RefType::kCPU_ID));
   Tokenize(trace_1);
 }
 
@@ -291,6 +297,10 @@
 
   ASSERT_TRUE(ParseSystraceTracePoint(base::StringView("B|42|Bar"), &result));
   EXPECT_EQ(result, (SystraceTracePoint{'B', 42, base::StringView("Bar"), 0}));
+
+  ASSERT_TRUE(
+      ParseSystraceTracePoint(base::StringView("C|543|foo|8"), &result));
+  EXPECT_EQ(result, (SystraceTracePoint{'C', 543, base::StringView("foo"), 8}));
 }
 
 }  // namespace
diff --git a/src/trace_processor/sched_slice_table.cc b/src/trace_processor/sched_slice_table.cc
index 352d244..dfdde24 100644
--- a/src/trace_processor/sched_slice_table.cc
+++ b/src/trace_processor/sched_slice_table.cc
@@ -103,7 +103,6 @@
          "dur UNSIGNED BIG INT, "
          "quantized_group UNSIGNED BIG INT, "
          "utid UNSIGNED INT, "
-         "cycles UNSIGNED BIG INT, "
          "quantum HIDDEN BIG INT, "
          "ts_lower_bound HIDDEN BIG INT, "
          "ts_clip HIDDEN BOOLEAN, "
@@ -275,11 +274,6 @@
       sqlite3_result_int64(context, slices.utids()[row]);
       break;
     }
-    case Column::kCycles: {
-      sqlite3_result_int64(context,
-                           static_cast<sqlite3_int64>(slices.cycles()[row]));
-      break;
-    }
   }
   return SQLITE_OK;
 }
@@ -463,8 +457,6 @@
       return Compare(f_cpu, s_cpu, ob.desc);
     case SchedSliceTable::Column::kUtid:
       return Compare(f_sl.utids()[f_idx], s_sl.utids()[s_idx], ob.desc);
-    case SchedSliceTable::Column::kCycles:
-      return Compare(f_sl.cycles()[f_idx], s_sl.cycles()[s_idx], ob.desc);
     case SchedSliceTable::Column::kQuantizedGroup: {
       // We don't support sorting in descending order on quantized group when
       // we have a non-zero quantum.
diff --git a/src/trace_processor/sched_slice_table.h b/src/trace_processor/sched_slice_table.h
index 1cfca45..4e86336 100644
--- a/src/trace_processor/sched_slice_table.h
+++ b/src/trace_processor/sched_slice_table.h
@@ -39,12 +39,11 @@
     kDuration = 2,
     kQuantizedGroup = 3,
     kUtid = 4,
-    kCycles = 5,
 
     // Hidden columns.
-    kQuantum = 6,
-    kTimestampLowerBound = 7,
-    kClipTimestamp = 8,
+    kQuantum = 5,
+    kTimestampLowerBound = 6,
+    kClipTimestamp = 7,
   };
 
   SchedSliceTable(sqlite3*, const TraceStorage* storage);
diff --git a/src/trace_processor/sched_slice_table_unittest.cc b/src/trace_processor/sched_slice_table_unittest.cc
index fbc84ff..31c215f 100644
--- a/src/trace_processor/sched_slice_table_unittest.cc
+++ b/src/trace_processor/sched_slice_table_unittest.cc
@@ -366,42 +366,6 @@
               ElementsAre(71));
 }
 
-TEST_F(SchedSliceTableTest, CyclesOrdering) {
-  uint32_t cpu = 3;
-  uint64_t timestamp = 100;
-  uint32_t pid_1 = 2;
-  uint32_t prev_state = 32;
-  static const char kCommProc1[] = "process1";
-  static const char kCommProc2[] = "process2";
-  uint32_t pid_2 = 4;
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp, pid_1, prev_state,
-                                          kCommProc1, pid_2);
-  context_.storage->PushCpuFreq(timestamp + 1, cpu, 1e9);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 2, pid_2, prev_state,
-                                          kCommProc2, pid_1);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 4, pid_1, prev_state,
-                                          kCommProc1, pid_2);
-  context_.storage->PushCpuFreq(timestamp + 5, cpu, 2e9);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 7, pid_2, prev_state,
-                                          kCommProc2, pid_1);
-
-  PrepareValidStatement("SELECT cycles, ts FROM sched ORDER BY cycles desc");
-
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
-  ASSERT_EQ(sqlite3_column_int64(*stmt_, 0), 5000 /* cycles */);
-  ASSERT_EQ(sqlite3_column_int64(*stmt_, 1), timestamp + 4);
-
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
-  ASSERT_EQ(sqlite3_column_int64(*stmt_, 0), 2000 /* cycles */);
-  ASSERT_EQ(sqlite3_column_int64(*stmt_, 1), timestamp + 2);
-
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
-  ASSERT_EQ(sqlite3_column_int64(*stmt_, 0), 1000 /* cycles */);
-  ASSERT_EQ(sqlite3_column_int64(*stmt_, 1), timestamp);
-
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_DONE);
-}
-
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/sched_tracker.cc b/src/trace_processor/sched_tracker.cc
index e1e1854..4dcc097 100644
--- a/src/trace_processor/sched_tracker.cc
+++ b/src/trace_processor/sched_tracker.cc
@@ -54,9 +54,7 @@
                             : context_->storage->InternString(prev_comm);
     UniqueTid utid = context_->process_tracker->UpdateThread(
         prev->timestamp, prev->next_pid /* == prev_pid */, prev_thread_name_id);
-    uint64_t cycles = CalculateCycles(cpu, prev->timestamp, timestamp);
-    context_->storage->AddSliceToCpu(cpu, prev->timestamp, duration, utid,
-                                     cycles);
+    context_->storage->AddSliceToCpu(cpu, prev->timestamp, duration, utid);
   }
 
   // If the this events previous pid does not match the previous event's next
@@ -72,42 +70,36 @@
   prev->next_pid = next_pid;
 };
 
-uint64_t SchedTracker::CalculateCycles(uint32_t cpu,
-                                       uint64_t start_ns,
-                                       uint64_t end_ns) {
-  const auto& frequencies = context_->storage->GetFreqForCpu(cpu);
-  auto lower_index = lower_index_per_cpu_[cpu];
-  if (frequencies.empty())
-    return 0;
+void SchedTracker::PushCounter(uint64_t timestamp,
+                               double value,
+                               StringId name_id,
+                               uint64_t ref,
+                               RefType ref_type) {
+  if (timestamp < prev_timestamp_) {
+    PERFETTO_ELOG("counter event out of order by %.4f ms, skipping",
+                  (prev_timestamp_ - timestamp) / 1e6);
+    return;
+  }
+  prev_timestamp_ = timestamp;
 
-  long double cycles = 0;
+  // The previous counter with the same ref and name_id.
+  Counter& prev = prev_counters_[CounterKey{ref, name_id}];
 
-  // Move the lower index up to the first cpu_freq event before start_ns.
-  while (lower_index + 1 < frequencies.size()) {
-    if (frequencies[lower_index + 1].first >= start_ns)
-      break;
-    ++lower_index;
-  };
+  uint64_t duration = 0;
+  double value_delta = 0;
 
-  // Since events are processed in timestamp order, we don't have any cpu_freq
-  // events with a timestamp larger than end_ns. Therefore we care about all
-  // freq events from lower_index (first event before start_ns) to the last
-  // cpu_freq event.
-  for (size_t i = lower_index; i < frequencies.size(); ++i) {
-    // Using max handles the special case for the first cpu_freq event.
-    uint64_t cycle_start = std::max(frequencies[i].first, start_ns);
-    // If there are no more freq_events we compute cycles until |end_ns|.
-    uint64_t cycle_end = end_ns;
-    if (i + 1 < frequencies.size())
-      cycle_end = frequencies[i + 1].first;
+  if (prev.timestamp != 0) {
+    duration = timestamp - prev.timestamp;
+    value_delta = value - prev.value;
 
-    uint32_t freq_khz = frequencies[i].second;
-    cycles += ((cycle_end - cycle_start) / 1E6L) * freq_khz;
+    context_->storage->mutable_counters()->AddCounter(
+        prev.timestamp, duration, name_id, prev.value, value_delta,
+        static_cast<int64_t>(ref), ref_type);
   }
 
-  lower_index_per_cpu_[cpu] = frequencies.size() - 1;
-  return static_cast<uint64_t>(round(cycles));
-}
+  prev.timestamp = timestamp;
+  prev.value = value;
+};
 
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/sched_tracker.h b/src/trace_processor/sched_tracker.h
index 53855a3..b2c5791 100644
--- a/src/trace_processor/sched_tracker.h
+++ b/src/trace_processor/sched_tracker.h
@@ -46,6 +46,33 @@
     bool valid() const { return timestamp != 0; }
   };
 
+  // A Counter is a trace event that has a value attached to a timestamp.
+  // These include CPU frequency ftrace events and systrace trace_marker
+  // counter events.
+  struct Counter {
+    uint64_t timestamp = 0;
+    double value = 0;
+  };
+
+  // Used as the key in |prev_counters_| to find the previous counter with the
+  // same ref and name_id.
+  struct CounterKey {
+    uint64_t ref;      // cpu, utid, ...
+    StringId name_id;  // "cpufreq"
+
+    bool operator==(const CounterKey& other) const {
+      return (ref == other.ref && name_id == other.name_id);
+    }
+
+    struct Hasher {
+      size_t operator()(const CounterKey& c) const {
+        size_t const h1(std::hash<uint64_t>{}(c.ref));
+        size_t const h2(std::hash<size_t>{}(c.name_id));
+        return h1 ^ (h2 << 1);
+      }
+    };
+  };
+
   // This method is called when a sched switch event is seen in the trace.
   virtual void PushSchedSwitch(uint32_t cpu,
                                uint64_t timestamp,
@@ -54,16 +81,24 @@
                                base::StringView prev_comm,
                                uint32_t next_pid);
 
- private:
-  // Based on the cpu frequencies stored in trace_storage, the number of cycles
-  // between start_ns and end_ns on |cpu| is calculated.
-  uint64_t CalculateCycles(uint32_t cpu, uint64_t start_ns, uint64_t end_ns);
+  // This method is called when a cpu freq event is seen in the trace.
+  // TODO(taylori): Move to a more appropriate class or rename class.
+  virtual void PushCounter(uint64_t timestamp,
+                           double value,
+                           StringId name_id,
+                           uint64_t ref,
+                           RefType ref_type);
 
+ private:
   // Store the previous sched event to calculate the duration before storing it.
   std::array<SchedSwitchEvent, base::kMaxCpus> last_sched_per_cpu_;
 
-  std::array<size_t, base::kMaxCpus> lower_index_per_cpu_{};
+  // Store the previous counter event to calculate the duration and value delta
+  // before storing it in trace storage.
+  std::unordered_map<CounterKey, Counter, CounterKey::Hasher> prev_counters_;
 
+  // Timestamp of the previous event. Used to discard events arriving out
+  // of order.
   uint64_t prev_timestamp_ = 0;
 
   StringId const idle_string_id_;
diff --git a/src/trace_processor/sched_tracker_unittest.cc b/src/trace_processor/sched_tracker_unittest.cc
index b50f135..dbd94e6 100644
--- a/src/trace_processor/sched_tracker_unittest.cc
+++ b/src/trace_processor/sched_tracker_unittest.cc
@@ -97,32 +97,60 @@
   ASSERT_EQ(context.storage->SlicesForCpu(cpu).durations().at(2), 31u - 11u);
   ASSERT_EQ(context.storage->SlicesForCpu(cpu).utids().at(0),
             context.storage->SlicesForCpu(cpu).utids().at(2));
-  ASSERT_EQ(context.storage->SlicesForCpu(cpu).cycles().at(0), 0);
 }
 
-TEST_F(SchedTrackerTest, TestCyclesCalculation) {
+TEST_F(SchedTrackerTest, CounterDuration) {
   uint32_t cpu = 3;
-  uint64_t timestamp = 1e9;
-  context.storage->PushCpuFreq(timestamp, cpu, 1e6);
+  uint64_t timestamp = 100;
+  StringId name_id = 0;
+  context.sched_tracker->PushCounter(timestamp, 1000, name_id, cpu,
+                                     RefType::kCPU_ID);
+  context.sched_tracker->PushCounter(timestamp + 1, 4000, name_id, cpu,
+                                     RefType::kCPU_ID);
+  context.sched_tracker->PushCounter(timestamp + 3, 5000, name_id, cpu,
+                                     RefType::kCPU_ID);
+  context.sched_tracker->PushCounter(timestamp + 9, 1000, name_id, cpu,
+                                     RefType::kCPU_ID);
 
-  uint32_t prev_state = 32;
-  static const char kCommProc1[] = "process1";
-  static const char kCommProc2[] = "process2";
+  ASSERT_EQ(context.storage->counters().counter_count(), 3ul);
+  ASSERT_EQ(context.storage->counters().timestamps().at(0), timestamp);
+  ASSERT_EQ(context.storage->counters().durations().at(0), 1);
+  ASSERT_EQ(context.storage->counters().values().at(0), 1000);
 
-  context.sched_tracker->PushSchedSwitch(
-      cpu, static_cast<uint64_t>(timestamp + 1e7L), /*tid=*/2, prev_state,
-      kCommProc1,
-      /*tid=*/4);
+  ASSERT_EQ(context.storage->counters().timestamps().at(1), timestamp + 1);
+  ASSERT_EQ(context.storage->counters().durations().at(1), 2);
+  ASSERT_EQ(context.storage->counters().values().at(1), 4000);
 
-  context.storage->PushCpuFreq(static_cast<uint64_t>(timestamp + 1e8L), cpu,
-                               2e6);
-  context.storage->PushCpuFreq(static_cast<uint64_t>(timestamp + 2e8L), cpu,
-                               3e6);
-  context.sched_tracker->PushSchedSwitch(
-      cpu, static_cast<uint64_t>(timestamp + 3e8L), /*tid=*/4, prev_state,
-      kCommProc2,
-      /*tid=*/2);
-  ASSERT_EQ(context.storage->SlicesForCpu(cpu).cycles().at(0), 590000000);
+  ASSERT_EQ(context.storage->counters().timestamps().at(2), timestamp + 3);
+  ASSERT_EQ(context.storage->counters().durations().at(2), 6);
+  ASSERT_EQ(context.storage->counters().values().at(2), 5000);
+}
+
+TEST_F(SchedTrackerTest, MixedEventsValueDelta) {
+  uint32_t cpu = 3;
+  uint64_t timestamp = 100;
+  StringId name_id_cpu = 0;
+  StringId name_id_upid = 0;
+  UniquePid upid = 12;
+  context.sched_tracker->PushCounter(timestamp, 1000, name_id_cpu, cpu,
+                                     RefType::kCPU_ID);
+  context.sched_tracker->PushCounter(timestamp + 1, 0, name_id_upid, upid,
+                                     RefType::kUTID);
+  context.sched_tracker->PushCounter(timestamp + 3, 5000, name_id_cpu, cpu,
+                                     RefType::kCPU_ID);
+  context.sched_tracker->PushCounter(timestamp + 9, 1, name_id_upid, upid,
+                                     RefType::kUTID);
+
+  ASSERT_EQ(context.storage->counters().counter_count(), 2ul);
+  ASSERT_EQ(context.storage->counters().timestamps().at(0), timestamp);
+  ASSERT_EQ(context.storage->counters().durations().at(0), 3);
+  ASSERT_EQ(context.storage->counters().values().at(0), 1000);
+  ASSERT_EQ(context.storage->counters().value_deltas().at(0), 4000);
+
+  ASSERT_EQ(context.storage->counters().timestamps().at(1), timestamp + 1);
+  ASSERT_EQ(context.storage->counters().durations().at(1), 8);
+  ASSERT_EQ(context.storage->counters().values().at(1), 0);
+  ASSERT_EQ(context.storage->counters().value_deltas().at(1), 1);
 }
 
 }  // namespace
diff --git a/src/trace_processor/slice_tracker.cc b/src/trace_processor/slice_tracker.cc
index 1d0e8d1..16dbc3c 100644
--- a/src/trace_processor/slice_tracker.cc
+++ b/src/trace_processor/slice_tracker.cc
@@ -56,7 +56,9 @@
                        StringId name) {
   auto& stack = threads_[utid];
   MaybeCloseStack(timestamp, stack);
-  PERFETTO_CHECK(!stack.empty());
+  if (stack.empty()) {
+    return;
+  }
 
   PERFETTO_CHECK(cat == 0 || stack.back().cat_id == cat);
   PERFETTO_CHECK(name == 0 || stack.back().name_id == name);
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index c485934..fd8af99 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -36,6 +36,10 @@
 #define PERFETTO_HAS_SIGNAL_H() 0
 #endif
 
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+#include <linenoise.h>
+#endif
+
 #if PERFETTO_HAS_SIGNAL_H()
 #include <signal.h>
 #endif
@@ -46,11 +50,45 @@
 namespace {
 TraceProcessor* g_tp;
 
-void PrintPrompt() {
-  printf("\r%80s\r> ", "");
-  fflush(stdout);
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+
+void SetupLineEditor() {
+  linenoiseSetMultiLine(true);
+  linenoiseHistorySetMaxLen(1000);
 }
 
+void FreeLine(char* line) {
+  linenoiseHistoryAdd(line);
+  linenoiseFree(line);
+}
+
+char* GetLine(const char* prompt) {
+  return linenoise(prompt);
+}
+
+#else
+
+void SetupLineEditor() {}
+
+void FreeLine(char* line) {
+  free(line);
+}
+
+char* GetLine(const char* prompt) {
+  printf("\r%80s\r%s", "", prompt);
+  fflush(stdout);
+  char* line = new char[1024];
+  if (!fgets(line, 1024 - 1, stdin)) {
+    FreeLine(line);
+    return nullptr;
+  }
+  if (strlen(line) > 0)
+    line[strlen(line) - 1] = 0;
+  return line;
+}
+
+#endif
+
 void OnQueryResult(base::TimeNanos t_start, const protos::RawQueryResult& res) {
   if (res.has_error()) {
     PERFETTO_ELOG("SQLite error: %s", res.error().c_str());
@@ -175,12 +213,13 @@
   signal(SIGINT, [](int) { g_tp->InterruptQuery(); });
 #endif
 
+  SetupLineEditor();
+
   for (;;) {
-    PrintPrompt();
-    char line[1024];
-    if (!fgets(line, sizeof(line) - 1, stdin) || strcmp(line, "q\n") == 0)
-      return 0;
-    if (strcmp(line, "\n") == 0)
+    char* line = GetLine("> ");
+    if (!line || strcmp(line, "q\n") == 0)
+      break;
+    if (strcmp(line, "") == 0)
       continue;
     protos::RawQueryArgs query;
     query.set_sql_query(line);
@@ -188,5 +227,9 @@
     g_tp->ExecuteQuery(query, [t_start](const protos::RawQueryResult& res) {
       OnQueryResult(t_start, res);
     });
+
+    FreeLine(line);
   }
+
+  return 0;
 }
diff --git a/src/trace_processor/trace_sorter_unittest.cc b/src/trace_processor/trace_sorter_unittest.cc
index db1a8b6..8716fbb 100644
--- a/src/trace_processor/trace_sorter_unittest.cc
+++ b/src/trace_processor/trace_sorter_unittest.cc
@@ -52,10 +52,19 @@
   }
 };
 
+class MockTraceStorage : public TraceStorage {
+ public:
+  MockTraceStorage() : TraceStorage() {}
+
+  MOCK_METHOD1(InternString, StringId(base::StringView view));
+};
+
 class TraceSorterTest : public ::testing::TestWithParam<OptimizationMode> {
  public:
   TraceSorterTest()
       : test_buffer_(std::unique_ptr<uint8_t[]>(new uint8_t[8]), 0, 8) {
+    storage_ = new MockTraceStorage();
+    context_.storage.reset(storage_);
     context_.sorter.reset(
         new TraceSorter(&context_, GetParam(), 0 /*window_size*/));
     parser_ = new MockTraceParser(&context_);
@@ -65,6 +74,7 @@
  protected:
   TraceProcessorContext context_;
   MockTraceParser* parser_;
+  MockTraceStorage* storage_;
   TraceBlobView test_buffer_;
 };
 
diff --git a/src/trace_processor/trace_storage.cc b/src/trace_processor/trace_storage.cc
index ce5fae1..43c5944 100644
--- a/src/trace_processor/trace_storage.cc
+++ b/src/trace_processor/trace_storage.cc
@@ -29,9 +29,6 @@
   // Reserve string ID 0 for the empty string.
   InternString("");
 
-  // Initialize all CPUs @ freq 0Hz.
-  for (size_t cpu = 0; cpu < base::kMaxCpus; cpu++)
-    cpu_freq_[cpu].emplace_back(0, 0);
 }
 
 TraceStorage::~TraceStorage() {}
@@ -39,9 +36,8 @@
 void TraceStorage::AddSliceToCpu(uint32_t cpu,
                                  uint64_t start_ns,
                                  uint64_t duration_ns,
-                                 UniqueTid utid,
-                                 uint64_t cycles) {
-  cpu_events_[cpu].AddSlice(start_ns, duration_ns, utid, cycles);
+                                 UniqueTid utid) {
+  cpu_events_[cpu].AddSlice(start_ns, duration_ns, utid);
 };
 
 StringId TraceStorage::InternString(base::StringView str) {
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 79b1492..47ccbf7 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -43,9 +43,7 @@
 // StringId is an offset into |string_pool_|.
 using StringId = size_t;
 
-// A map containing timestamps and the cpu frequency set at that time.
-using CpuFreq =
-    std::deque<std::pair<uint64_t /*timestamp*/, uint32_t /*freq*/>>;
+enum RefType { kUTID = 0, kCPU_ID = 1 };
 
 // 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
@@ -84,12 +82,10 @@
    public:
     inline void AddSlice(uint64_t start_ns,
                          uint64_t duration_ns,
-                         UniqueTid utid,
-                         uint64_t cycles) {
+                         UniqueTid utid) {
       start_ns_.emplace_back(start_ns);
       durations_.emplace_back(duration_ns);
       utids_.emplace_back(utid);
-      cycles_.emplace_back(cycles);
     }
 
     size_t slice_count() const { return start_ns_.size(); }
@@ -100,15 +96,12 @@
 
     const std::deque<UniqueTid>& utids() const { return utids_; }
 
-    const std::deque<uint64_t>& cycles() const { return cycles_; }
-
    private:
     // Each deque below has the same number of entries (the number of slices
     // in the trace for the CPU).
     std::deque<uint64_t> start_ns_;
     std::deque<uint64_t> durations_;
     std::deque<UniqueTid> utids_;
-    std::deque<uint64_t> cycles_;
   };
 
   class NestableSlices {
@@ -154,13 +147,55 @@
     std::deque<uint64_t> parent_stack_ids_;
   };
 
+  class Counters {
+   public:
+    inline void AddCounter(uint64_t timestamp,
+                           uint64_t duration,
+                           StringId name_id,
+                           double value,
+                           double value_delta,
+                           int64_t ref,
+                           RefType type) {
+      timestamps_.emplace_back(timestamp);
+      durations_.emplace_back(duration);
+      name_ids_.emplace_back(name_id);
+      values_.emplace_back(value);
+      value_deltas_.emplace_back(value_delta);
+      refs_.emplace_back(ref);
+      types_.emplace_back(type);
+    }
+    size_t counter_count() const { return timestamps_.size(); }
+
+    const std::deque<uint64_t>& timestamps() const { return timestamps_; }
+
+    const std::deque<uint64_t>& durations() const { return durations_; }
+
+    const std::deque<StringId>& name_ids() const { return name_ids_; }
+
+    const std::deque<double>& values() const { return values_; }
+
+    const std::deque<double>& value_deltas() const { return value_deltas_; }
+
+    const std::deque<int64_t>& refs() const { return refs_; }
+
+    const std::deque<RefType>& types() const { return types_; }
+
+   private:
+    std::deque<uint64_t> timestamps_;
+    std::deque<uint64_t> durations_;
+    std::deque<StringId> name_ids_;
+    std::deque<double> values_;
+    std::deque<double> value_deltas_;
+    std::deque<int64_t> refs_;
+    std::deque<RefType> types_;
+  };
+
   void ResetStorage();
 
   void AddSliceToCpu(uint32_t cpu,
                      uint64_t start_ns,
                      uint64_t duration_ns,
-                     UniqueTid utid,
-                     uint64_t cycles);
+                     UniqueTid utid);
 
   UniqueTid AddEmptyThread(uint32_t tid) {
     unique_threads_.emplace_back(tid);
@@ -176,7 +211,8 @@
 
   // Return an unqiue identifier for the contents of each string.
   // The string is copied internally and can be destroyed after this called.
-  StringId InternString(base::StringView);
+  // Virtual for testing.
+  virtual StringId InternString(base::StringView);
 
   Process* GetMutableProcess(UniquePid upid) {
     PERFETTO_DCHECK(upid > 0 && upid < unique_processes_.size());
@@ -184,7 +220,7 @@
   }
 
   Thread* GetMutableThread(UniqueTid utid) {
-    PERFETTO_DCHECK(utid >= 0 && utid < unique_threads_.size());
+    PERFETTO_DCHECK(utid < unique_threads_.size());
     return &unique_threads_[utid];
   }
 
@@ -206,31 +242,15 @@
 
   const Thread& GetThread(UniqueTid utid) const {
     // Allow utid == 0 for idle thread retrieval.
-    PERFETTO_DCHECK(utid >= 0 && utid < unique_threads_.size());
+    PERFETTO_DCHECK(utid < unique_threads_.size());
     return unique_threads_[utid];
   }
 
   const NestableSlices& nestable_slices() const { return nestable_slices_; }
   NestableSlices* mutable_nestable_slices() { return &nestable_slices_; }
 
-  // Virtual for testing.
-  virtual void PushCpuFreq(uint64_t timestamp,
-                           uint32_t cpu,
-                           uint32_t new_freq) {
-    auto& freqs = cpu_freq_[cpu];
-    if (!freqs.empty() && timestamp < freqs.back().first) {
-      PERFETTO_ELOG("cpufreq out of order by %.4f ms, skipping",
-                    (freqs.back().first - timestamp) / 1e6);
-      return;
-    }
-    freqs.emplace_back(timestamp, new_freq);
-  }
-
-  const CpuFreq& GetFreqForCpu(uint32_t cpu) const { return cpu_freq_[cpu]; }
-
-  uint32_t GetMaxCpu() const {
-    return static_cast<uint32_t>(cpu_freq_.size() - 1);
-  }
+  const Counters& counters() const { return counters_; }
+  Counters* mutable_counters() { return &counters_; }
 
   // |unique_processes_| always contains at least 1 element becuase the 0th ID
   // is reserved to indicate an invalid process.
@@ -254,10 +274,6 @@
   // One entry for each CPU in the trace.
   std::array<SlicesPerCpu, base::kMaxCpus> cpu_events_;
 
-  // One map containing frequencies for every CPU in the trace. The map contains
-  // timestamps and the cpu frequency value at that time.
-  std::array<CpuFreq, base::kMaxCpus> cpu_freq_;
-
   // One entry for each unique string in the trace.
   std::deque<std::string> string_pool_;
 
@@ -272,6 +288,10 @@
 
   // Slices coming from userspace events (e.g. Chromium TRACE_EVENT macros).
   NestableSlices nestable_slices_;
+
+  // Counter events from the trace. This includes CPU frequency events as well
+  // systrace trace_marker counter events.
+  Counters counters_;
 };
 
 }  // namespace trace_processor
diff --git a/src/traced/probes/BUILD.gn b/src/traced/probes/BUILD.gn
index babc591..fd01252 100644
--- a/src/traced/probes/BUILD.gn
+++ b/src/traced/probes/BUILD.gn
@@ -40,6 +40,7 @@
     "../../tracing:tracing",
     "filesystem",
     "ps",
+    "sys_stats",
   ]
   sources = [
     "probes_producer.cc",
@@ -69,5 +70,6 @@
     "../../tracing:test_support",
     "filesystem:unittests",
     "ps:unittests",
+    "sys_stats:unittests",
   ]
 }
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 6938547..048eb41 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -88,10 +88,8 @@
   // on success and nullptr on failure.
   std::unique_ptr<ProtoT> ParseProto() {
     auto bundle = std::unique_ptr<ProtoT>(new ProtoT());
-    size_t msg_size =
-        delegate_.chunks().size() * chunk_size_ - stream_.bytes_available();
-    std::unique_ptr<uint8_t[]> buffer = delegate_.StitchChunks(msg_size);
-    if (!bundle->ParseFromArray(buffer.get(), static_cast<int>(msg_size)))
+    std::vector<uint8_t> buffer = delegate_.StitchChunks();
+    if (!bundle->ParseFromArray(buffer.data(), static_cast<int>(buffer.size())))
       return nullptr;
     return bundle;
   }
diff --git a/src/traced/probes/probes_producer.cc b/src/traced/probes/probes_producer.cc
index e56eb73..cc90242 100644
--- a/src/traced/probes/probes_producer.cc
+++ b/src/traced/probes/probes_producer.cc
@@ -50,6 +50,7 @@
 constexpr char kFtraceSourceName[] = "linux.ftrace";
 constexpr char kProcessStatsSourceName[] = "linux.process_stats";
 constexpr char kInodeMapSourceName[] = "linux.inode_file_map";
+constexpr char kSysStatsSourceName[] = "linux.sys_stats";
 
 }  // namespace.
 
@@ -74,17 +75,29 @@
   ResetConnectionBackoff();
   PERFETTO_LOG("Connected to the service");
 
-  DataSourceDescriptor ftrace_descriptor;
-  ftrace_descriptor.set_name(kFtraceSourceName);
-  endpoint_->RegisterDataSource(ftrace_descriptor);
+  {
+    DataSourceDescriptor desc;
+    desc.set_name(kFtraceSourceName);
+    endpoint_->RegisterDataSource(desc);
+  }
 
-  DataSourceDescriptor process_stats_descriptor;
-  process_stats_descriptor.set_name(kProcessStatsSourceName);
-  endpoint_->RegisterDataSource(process_stats_descriptor);
+  {
+    DataSourceDescriptor desc;
+    desc.set_name(kProcessStatsSourceName);
+    endpoint_->RegisterDataSource(desc);
+  }
 
-  DataSourceDescriptor inode_map_descriptor;
-  inode_map_descriptor.set_name(kInodeMapSourceName);
-  endpoint_->RegisterDataSource(inode_map_descriptor);
+  {
+    DataSourceDescriptor desc;
+    desc.set_name(kInodeMapSourceName);
+    endpoint_->RegisterDataSource(desc);
+  }
+
+  {
+    DataSourceDescriptor desc;
+    desc.set_name(kSysStatsSourceName);
+    endpoint_->RegisterDataSource(desc);
+  }
 }
 
 void ProbesProducer::OnDisconnect() {
@@ -129,6 +142,8 @@
     data_source = CreateInodeFileDataSource(session_id, instance_id, config);
   } else if (config.name() == kProcessStatsSourceName) {
     data_source = CreateProcessStatsDataSource(session_id, instance_id, config);
+  } else if (config.name() == kSysStatsSourceName) {
+    data_source = CreateSysStatsDataSource(session_id, instance_id, config);
   }
 
   if (!data_source) {
@@ -214,6 +229,18 @@
   return std::move(data_source);
 }
 
+std::unique_ptr<SysStatsDataSource> ProbesProducer::CreateSysStatsDataSource(
+    TracingSessionID session_id,
+    DataSourceInstanceID id,
+    const DataSourceConfig& config) {
+  base::ignore_result(id);
+  auto buffer_id = static_cast<BufferID>(config.target_buffer());
+  auto data_source = std::unique_ptr<SysStatsDataSource>(
+      new SysStatsDataSource(task_runner_, session_id,
+                             endpoint_->CreateTraceWriter(buffer_id), config));
+  return data_source;
+}
+
 void ProbesProducer::TearDownDataSourceInstance(DataSourceInstanceID id) {
   PERFETTO_LOG("Producer stop (id=%" PRIu64 ")", id);
   auto it = data_sources_.find(id);
@@ -290,6 +317,8 @@
       case ProcessStatsDataSource::kTypeId:
         ps_data_source = static_cast<ProcessStatsDataSource*>(ds);
         break;
+      case SysStatsDataSource::kTypeId:
+        break;
       default:
         PERFETTO_DCHECK(false);
     }  // switch (type_id)
diff --git a/src/traced/probes/probes_producer.h b/src/traced/probes/probes_producer.h
index 46e0f57..c3670ce 100644
--- a/src/traced/probes/probes_producer.h
+++ b/src/traced/probes/probes_producer.h
@@ -31,6 +31,7 @@
 #include "src/traced/probes/ftrace/ftrace_controller.h"
 #include "src/traced/probes/ftrace/ftrace_metadata.h"
 #include "src/traced/probes/ps/process_stats_data_source.h"
+#include "src/traced/probes/sys_stats/sys_stats_data_source.h"
 
 #include "perfetto/trace/filesystem/inode_file_map.pbzero.h"
 
@@ -74,6 +75,10 @@
       TracingSessionID session_id,
       DataSourceInstanceID id,
       DataSourceConfig config);
+  std::unique_ptr<SysStatsDataSource> CreateSysStatsDataSource(
+      TracingSessionID session_id,
+      DataSourceInstanceID id,
+      const DataSourceConfig& config);
 
  private:
   enum State {
diff --git a/src/traced/probes/sys_stats/BUILD.gn b/src/traced/probes/sys_stats/BUILD.gn
new file mode 100644
index 0000000..16ae4e0
--- /dev/null
+++ b/src/traced/probes/sys_stats/BUILD.gn
@@ -0,0 +1,48 @@
+# 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.
+
+source_set("sys_stats") {
+  public_deps = [
+    "../../../tracing",
+  ]
+  deps = [
+    "..:data_source",
+    "../../../../gn:default_deps",
+    "../../../../include/perfetto/traced",
+    "../../../../include/perfetto/traced:sys_stats_counters",
+    "../../../../protos/perfetto/config:lite",
+    "../../../../protos/perfetto/trace/sys_stats:zero",
+    "../../../base",
+  ]
+  sources = [
+    "sys_stats_data_source.cc",
+    "sys_stats_data_source.h",
+  ]
+}
+
+source_set("unittests") {
+  testonly = true
+  deps = [
+    ":sys_stats",
+    "../../../../gn:default_deps",
+    "../../../../gn:gtest_deps",
+    "../../../../protos/perfetto/config:lite",
+    "../../../../protos/perfetto/trace:lite",
+    "../../../../src/base:test_support",
+    "../../../../src/tracing:test_support",
+  ]
+  sources = [
+    "sys_stats_data_source_unittest.cc",
+  ]
+}
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.cc b/src/traced/probes/sys_stats/sys_stats_data_source.cc
new file mode 100644
index 0000000..ebc3733
--- /dev/null
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.cc
@@ -0,0 +1,304 @@
+/*
+ * 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/traced/probes/sys_stats/sys_stats_data_source.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <utility>
+
+#include "perfetto/base/file_utils.h"
+#include "perfetto/base/metatrace.h"
+#include "perfetto/base/scoped_file.h"
+#include "perfetto/base/string_splitter.h"
+#include "perfetto/base/task_runner.h"
+#include "perfetto/base/time.h"
+#include "perfetto/base/utils.h"
+#include "perfetto/traced/sys_stats_counters.h"
+#include "perfetto/tracing/core/sys_stats_config.h"
+
+#include "perfetto/common/sys_stats_counters.pbzero.h"
+#include "perfetto/config/sys_stats/sys_stats_config.pb.h"
+#include "perfetto/trace/sys_stats/sys_stats.pbzero.h"
+#include "perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+
+namespace {
+constexpr size_t kReadBufSize = 1024 * 16;
+
+base::ScopedFile OpenReadOnly(const char* path) {
+  base::ScopedFile fd;
+  fd.reset(open(path, O_RDONLY | O_CLOEXEC));
+  if (!fd)
+    PERFETTO_PLOG("Failed opening %s", path);
+  return fd;
+}
+
+}  // namespace
+
+// static
+constexpr int SysStatsDataSource::kTypeId;
+
+SysStatsDataSource::SysStatsDataSource(base::TaskRunner* task_runner,
+                                       TracingSessionID session_id,
+                                       std::unique_ptr<TraceWriter> writer,
+                                       const DataSourceConfig& ds_config,
+                                       OpenFunction open_fn)
+    : ProbesDataSource(session_id, kTypeId),
+      task_runner_(task_runner),
+      writer_(std::move(writer)),
+      weak_factory_(this) {
+  const auto& config = ds_config.sys_stats_config();
+
+  ns_per_user_hz_ = 1000000000ull / static_cast<uint64_t>(sysconf(_SC_CLK_TCK));
+
+  open_fn = open_fn ? open_fn : OpenReadOnly;
+  meminfo_fd_ = open_fn("/proc/meminfo");
+  vmstat_fd_ = open_fn("/proc/vmstat");
+  stat_fd_ = open_fn("/proc/stat");
+
+  read_buf_ = base::PageAllocator::Allocate(kReadBufSize);
+
+  // Build a lookup map that allows to quickly translate strings like "MemTotal"
+  // into the corresponding enum value, only for the counters enabled in the
+  // config.
+  for (const auto& counter_id : config.meminfo_counters()) {
+    for (size_t i = 0; i < base::ArraySize(kMeminfoKeys); i++) {
+      const auto& k = kMeminfoKeys[i];
+      if (static_cast<int>(k.id) == static_cast<int>(counter_id))
+        meminfo_counters_.emplace(k.str, k.id);
+    }
+  }
+
+  for (const auto& counter_id : config.vmstat_counters()) {
+    for (size_t i = 0; i < base::ArraySize(kVmstatKeys); i++) {
+      const auto& k = kVmstatKeys[i];
+      if (static_cast<int>(k.id) == static_cast<int>(counter_id))
+        vmstat_counters_.emplace(k.str, k.id);
+    }
+  }
+
+  for (const auto& counter_id : config.stat_counters()) {
+    stat_enabled_fields_ |= 1 << counter_id;
+  }
+
+  std::array<uint32_t, 3> periods_ms{};
+  std::array<uint32_t, 3> ticks{};
+  static_assert(periods_ms.size() == ticks.size(), "must have same size");
+  periods_ms[0] = config.meminfo_period_ms();
+  periods_ms[1] = config.vmstat_period_ms();
+  periods_ms[2] = config.stat_period_ms();
+  tick_period_ms_ = 0;
+  for (uint32_t ms : periods_ms) {
+    if (ms && (ms < tick_period_ms_ || tick_period_ms_ == 0))
+      tick_period_ms_ = ms;
+  }
+  if (tick_period_ms_ == 0)
+    return;  // No polling configured.
+
+  for (size_t i = 0; i < periods_ms.size(); i++) {
+    auto ms = periods_ms[i];
+    if (ms && ms % tick_period_ms_ != 0) {
+      PERFETTO_ELOG("SysStat periods are not integer multiples of each other");
+      return;
+    }
+    ticks[i] = ms / tick_period_ms_;
+  }
+  meminfo_ticks_ = ticks[0];
+  vmstat_ticks_ = ticks[1];
+  stat_ticks_ = ticks[2];
+  auto weak_this = GetWeakPtr();
+  task_runner_->PostTask(std::bind(&SysStatsDataSource::Tick, weak_this));
+}
+
+// static
+void SysStatsDataSource::Tick(base::WeakPtr<SysStatsDataSource> weak_this) {
+  if (!weak_this)
+    return;
+  SysStatsDataSource& thiz = *weak_this;
+
+  uint32_t period_ms = thiz.tick_period_ms_;
+  uint32_t delay_ms = period_ms - (base::GetWallTimeMs().count() % period_ms);
+  thiz.task_runner_->PostDelayedTask(
+      std::bind(&SysStatsDataSource::Tick, weak_this), delay_ms);
+  thiz.ReadSysStats();
+}
+
+SysStatsDataSource::~SysStatsDataSource() = default;
+
+void SysStatsDataSource::ReadSysStats() {
+  PERFETTO_METATRACE("ReadSysStats", 0);
+  auto packet = writer_->NewTracePacket();
+
+  packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
+  auto* sys_stats = packet->set_sys_stats();
+
+  if (meminfo_ticks_ && tick_ % meminfo_ticks_ == 0)
+    ReadMeminfo(sys_stats);
+
+  if (vmstat_ticks_ && tick_ % vmstat_ticks_ == 0)
+    ReadVmstat(sys_stats);
+
+  if (stat_ticks_ && tick_ % stat_ticks_ == 0)
+    ReadStat(sys_stats);
+
+  tick_++;
+}
+
+void SysStatsDataSource::ReadMeminfo(protos::pbzero::SysStats* sys_stats) {
+  size_t rsize = ReadFile(&meminfo_fd_, "/proc/meminfo");
+  if (!rsize)
+    return;
+  char* buf = static_cast<char*>(read_buf_.get());
+  for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
+    base::StringSplitter words(&lines, ' ');
+    if (!words.Next())
+      continue;
+    // Extract the meminfo key, dropping trailing ':' (e.g., "MemTotal: NN KB").
+    words.cur_token()[words.cur_token_size() - 1] = '\0';
+    auto it = meminfo_counters_.find(words.cur_token());
+    if (it == meminfo_counters_.end())
+      continue;
+    int counter_id = it->second;
+    if (!words.Next())
+      continue;
+    auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
+    auto* meminfo = sys_stats->add_meminfo();
+    meminfo->set_key(static_cast<protos::pbzero::MeminfoCounters>(counter_id));
+    meminfo->set_value(value);
+  }
+}
+
+void SysStatsDataSource::ReadVmstat(protos::pbzero::SysStats* sys_stats) {
+  size_t rsize = ReadFile(&vmstat_fd_, "/proc/vmstat");
+  if (!rsize)
+    return;
+  char* buf = static_cast<char*>(read_buf_.get());
+  for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
+    base::StringSplitter words(&lines, ' ');
+    if (!words.Next())
+      continue;
+    auto it = vmstat_counters_.find(words.cur_token());
+    if (it == vmstat_counters_.end())
+      continue;
+    int counter_id = it->second;
+    if (!words.Next())
+      continue;
+    auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
+    auto* vmstat = sys_stats->add_vmstat();
+    vmstat->set_key(static_cast<protos::pbzero::VmstatCounters>(counter_id));
+    vmstat->set_value(value);
+  }
+}
+
+void SysStatsDataSource::ReadStat(protos::pbzero::SysStats* sys_stats) {
+  size_t rsize = ReadFile(&stat_fd_, "/proc/stat");
+  if (!rsize)
+    return;
+  char* buf = static_cast<char*>(read_buf_.get());
+  for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
+    base::StringSplitter words(&lines, ' ');
+    if (!words.Next())
+      continue;
+
+    // Per-CPU stats.
+    if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_CPU_TIMES)) &&
+        words.cur_token_size() > 3 && !strncmp(words.cur_token(), "cpu", 3)) {
+      long cpu_id = strtol(words.cur_token() + 3, nullptr, 10);
+      std::array<uint64_t, 7> cpu_times{};
+      for (size_t i = 0; i < cpu_times.size() && words.Next(); i++) {
+        cpu_times[i] =
+            static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
+      }
+      auto* cpu_stat = sys_stats->add_cpu_stat();
+      cpu_stat->set_cpu_id(static_cast<uint32_t>(cpu_id));
+      cpu_stat->set_user_ns(cpu_times[0] * ns_per_user_hz_);
+      cpu_stat->set_user_ice_ns(cpu_times[1] * ns_per_user_hz_);
+      cpu_stat->set_system_mode_ns(cpu_times[2] * ns_per_user_hz_);
+      cpu_stat->set_idle_ns(cpu_times[3] * ns_per_user_hz_);
+      cpu_stat->set_io_wait_ns(cpu_times[4] * ns_per_user_hz_);
+      cpu_stat->set_irq_ns(cpu_times[5] * ns_per_user_hz_);
+      cpu_stat->set_softirq_ns(cpu_times[6] * ns_per_user_hz_);
+    }
+    // IRQ counters
+    else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_IRQ_COUNTS)) &&
+             !strcmp(words.cur_token(), "intr")) {
+      for (size_t i = 0; words.Next(); i++) {
+        auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
+        if (i == 0) {
+          sys_stats->set_num_irq_total(v);
+        } else {
+          auto* irq_stat = sys_stats->add_num_irq();
+          irq_stat->set_irq(static_cast<int32_t>(i - 1));
+          irq_stat->set_count(v);
+        }
+      }
+    }
+    // Softirq counters.
+    else if ((stat_enabled_fields_ &
+              (1 << SysStatsConfig::STAT_SOFTIRQ_COUNTS)) &&
+             !strcmp(words.cur_token(), "softirq")) {
+      for (size_t i = 0; words.Next(); i++) {
+        auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
+        if (i == 0) {
+          sys_stats->set_num_softirq_total(v);
+        } else {
+          auto* softirq_stat = sys_stats->add_num_softirq();
+          softirq_stat->set_irq(static_cast<int32_t>(i - 1));
+          softirq_stat->set_count(v);
+        }
+      }
+    }
+    // Number of forked processes since boot.
+    else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_FORK_COUNT)) &&
+             !strcmp(words.cur_token(), "processes")) {
+      if (words.Next()) {
+        sys_stats->set_num_forks(
+            static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10)));
+      }
+    }
+
+  }  // for (line)
+}
+
+base::WeakPtr<SysStatsDataSource> SysStatsDataSource::GetWeakPtr() const {
+  return weak_factory_.GetWeakPtr();
+}
+
+void SysStatsDataSource::Flush() {
+  writer_->Flush();
+}
+
+size_t SysStatsDataSource::ReadFile(base::ScopedFile* fd, const char* path) {
+  if (!*fd)
+    return 0;
+  ssize_t res = pread(**fd, read_buf_.get(), kReadBufSize - 1, 0);
+  if (res <= 0) {
+    PERFETTO_PLOG("Failed reading %s", path);
+    fd->reset();
+    return 0;
+  }
+  size_t rsize = static_cast<size_t>(res);
+  static_cast<char*>(read_buf_.get())[rsize] = '\0';
+  return rsize + 1;  // Include null terminator in the count.
+}
+
+}  // namespace perfetto
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.h b/src/traced/probes/sys_stats/sys_stats_data_source.h
new file mode 100644
index 0000000..a5ea4f7
--- /dev/null
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.h
@@ -0,0 +1,104 @@
+/*
+ * 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_TRACED_PROBES_SYS_STATS_SYS_STATS_DATA_SOURCE_H_
+#define SRC_TRACED_PROBES_SYS_STATS_SYS_STATS_DATA_SOURCE_H_
+
+#include <string.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "perfetto/base/page_allocator.h"
+#include "perfetto/base/scoped_file.h"
+#include "perfetto/base/weak_ptr.h"
+#include "perfetto/tracing/core/basic_types.h"
+#include "perfetto/tracing/core/data_source_config.h"
+#include "perfetto/tracing/core/trace_writer.h"
+#include "src/traced/probes/probes_data_source.h"
+
+namespace perfetto {
+
+namespace base {
+class TaskRunner;
+}
+
+namespace protos {
+namespace pbzero {
+class SysStats;
+}
+}  // namespace protos
+
+class SysStatsDataSource : public ProbesDataSource {
+ public:
+  static constexpr int kTypeId = 4;
+  using OpenFunction = base::ScopedFile (*)(const char*);
+  SysStatsDataSource(base::TaskRunner*,
+                     TracingSessionID,
+                     std::unique_ptr<TraceWriter> writer,
+                     const DataSourceConfig&,
+                     OpenFunction = nullptr);
+  ~SysStatsDataSource() override;
+
+  // ProbesDataSource implementation.
+  void Flush() override;
+
+  base::WeakPtr<SysStatsDataSource> GetWeakPtr() const;
+
+  void set_ns_per_user_hz_for_testing(uint64_t ns) { ns_per_user_hz_ = ns; }
+  uint32_t tick_for_testing() const { return tick_; }
+
+ private:
+  struct CStrCmp {
+    bool operator()(const char* a, const char* b) const {
+      return strcmp(a, b) < 0;
+    }
+  };
+
+  static void Tick(base::WeakPtr<SysStatsDataSource>);
+
+  SysStatsDataSource(const SysStatsDataSource&) = delete;
+  SysStatsDataSource& operator=(const SysStatsDataSource&) = delete;
+  void ReadSysStats();  // Virtual for testing.
+  void ReadMeminfo(protos::pbzero::SysStats* sys_stats);
+  void ReadVmstat(protos::pbzero::SysStats* sys_stats);
+  void ReadStat(protos::pbzero::SysStats* sys_stats);
+  size_t ReadFile(base::ScopedFile*, const char* path);
+
+  base::TaskRunner* const task_runner_;
+  std::unique_ptr<TraceWriter> writer_;
+  base::ScopedFile meminfo_fd_;
+  base::ScopedFile vmstat_fd_;
+  base::ScopedFile stat_fd_;
+  base::PageAllocator::UniquePtr read_buf_;
+  TraceWriter::TracePacketHandle cur_packet_;
+  std::map<const char*, int, CStrCmp> meminfo_counters_;
+  std::map<const char*, int, CStrCmp> vmstat_counters_;
+  uint64_t ns_per_user_hz_ = 0;
+  uint32_t tick_ = 0;
+  uint32_t tick_period_ms_ = 0;
+  uint32_t meminfo_ticks_ = 0;
+  uint32_t vmstat_ticks_ = 0;
+  uint32_t stat_ticks_ = 0;
+  uint32_t stat_enabled_fields_ = 0;
+
+  base::WeakPtrFactory<SysStatsDataSource> weak_factory_;  // Keep last.
+};
+
+}  // namespace perfetto
+
+#endif  // SRC_TRACED_PROBES_SYS_STATS_SYS_STATS_DATA_SOURCE_H_
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
new file mode 100644
index 0000000..e2c18d9
--- /dev/null
+++ b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
@@ -0,0 +1,370 @@
+/*
+ * 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 <unistd.h>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "perfetto/base/temp_file.h"
+#include "src/base/test/test_task_runner.h"
+#include "src/traced/probes/sys_stats/sys_stats_data_source.h"
+#include "src/tracing/core/trace_writer_for_testing.h"
+
+#include "perfetto/config/sys_stats/sys_stats_config.pb.h"
+#include "perfetto/trace/trace_packet.pb.h"
+#include "perfetto/trace/trace_packet.pbzero.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::UnorderedElementsAre;
+
+namespace perfetto {
+namespace {
+
+const char kMockMeminfo[] = R"(
+MemTotal:        3744240 kB
+MemFree:           73328 kB
+MemAvailable:     629896 kB
+Buffers:           19296 kB
+Cached:           731032 kB
+SwapCached:         4936 kB
+Active:          1616348 kB
+Inactive:         745492 kB
+Active(anon):    1322636 kB
+Inactive(anon):   449172 kB
+Active(file):     293712 kB
+Inactive(file):   296320 kB
+Unevictable:      142152 kB
+Mlocked:          142152 kB
+SwapTotal:        524284 kB
+SwapFree:            128 kB
+Dirty:                 0 kB
+Writeback:             0 kB
+AnonPages:       1751140 kB
+Mapped:           508372 kB
+Shmem:             18604 kB
+Slab:             240352 kB
+SReclaimable:      64684 kB
+SUnreclaim:       175668 kB
+KernelStack:       62672 kB
+PageTables:        70108 kB
+NFS_Unstable:          0 kB
+Bounce:                0 kB
+WritebackTmp:          0 kB
+CommitLimit:     2396404 kB
+Committed_AS:   81911488 kB
+VmallocTotal:   258867136 kB
+VmallocUsed:           0 kB
+VmallocChunk:          0 kB
+CmaTotal:         196608 kB
+CmaFree:              60 kB)";
+
+const char kMockVmstat[] = R"(
+nr_free_pages 16449
+nr_alloc_batch 79
+nr_inactive_anon 112545
+nr_active_anon 322027
+nr_inactive_file 75904
+nr_active_file 87939
+nr_unevictable 35538
+nr_mlock 35538
+nr_anon_pages 429005
+nr_mapped 125844
+nr_file_pages 205523
+nr_dirty 23
+nr_writeback 0
+nr_slab_reclaimable 15840
+nr_slab_unreclaimable 43912
+nr_page_table_pages 17158
+nr_kernel_stack 3822
+nr_overhead 0
+nr_unstable 0
+nr_bounce 0
+nr_vmscan_write 558690
+nr_vmscan_immediate_reclaim 14853
+nr_writeback_temp 0
+nr_isolated_anon 0
+nr_isolated_file 0
+nr_shmem 5027
+nr_dirtied 6732417
+nr_written 6945513
+nr_pages_scanned 0
+workingset_refault 32784684
+workingset_activate 8200928
+workingset_nodereclaim 0
+nr_anon_transparent_hugepages 0
+nr_free_cma 0
+nr_swapcache 1254
+nr_dirty_threshold 33922
+nr_dirty_background_threshold 8449
+pgpgin 161257156
+pgpgout 35973852
+pgpgoutclean 37181384
+pswpin 185308
+pswpout 557662
+pgalloc_dma 79259070
+pgalloc_normal 88265512
+pgalloc_movable 0
+pgfree 175051592
+pgactivate 11897892
+pgdeactivate 20412230
+pgfault 181696234
+pgmajfault 1060871
+pgrefill_dma 12970047
+pgrefill_normal 14391564
+pgrefill_movable 0
+pgsteal_kswapd_dma 19471476
+pgsteal_kswapd_normal 21138380
+pgsteal_kswapd_movable 0
+pgsteal_direct_dma 40625
+pgsteal_direct_normal 50912
+pgsteal_direct_movable 0
+pgscan_kswapd_dma 23544417
+pgscan_kswapd_normal 25623715
+pgscan_kswapd_movable 0
+pgscan_direct_dma 50369
+pgscan_direct_normal 66284
+pgscan_direct_movable 0
+pgscan_direct_throttle 0
+pginodesteal 0
+slabs_scanned 39582828
+kswapd_inodesteal 110199
+kswapd_low_wmark_hit_quickly 21321
+kswapd_high_wmark_hit_quickly 4112
+pageoutrun 37666
+allocstall 1587
+pgrotated 12086
+drop_pagecache 0
+drop_slab 0
+pgmigrate_success 5923482
+pgmigrate_fail 3439
+compact_migrate_scanned 92906456
+compact_free_scanned 467077168
+compact_isolated 13456528
+compact_stall 197
+compact_fail 42
+compact_success 155
+compact_daemon_wake 2131
+unevictable_pgs_culled 50170
+unevictable_pgs_scanned 0
+unevictable_pgs_rescued 14640
+unevictable_pgs_mlocked 52520
+unevictable_pgs_munlocked 14640
+unevictable_pgs_cleared 2342
+unevictable_pgs_stranded 2342)";
+
+const char kMockStat[] = R"(
+cpu  2655987 822682 2352153 8801203 41917 322733 175055 0 0 0
+cpu0 762178 198125 902284 8678856 41716 152974 68262 0 0 0
+cpu1 613833 243394 504323 15194 96 60625 28785 0 0 0
+cpu2 207349 95060 248856 17351 42 32148 26108 0 0 0
+cpu3 138474 92158 174852 17537 48 25076 25035 0 0 0
+cpu4 278720 34689 141048 18117 1 20782 5873 0 0 0
+cpu5 235376 33907 85098 18278 2 10049 3774 0 0 0
+cpu6 239568 67149 155814 17890 5 11518 3807 0 0 0
+cpu7 180484 58196 139874 17975 3 9556 13407 0 0 0
+intr 238128517 0 0 0 63500984 0 6253792 6 4 5 0 0 0 0 0 0 0 160331 0 0 14 0 0 0 0 0 0 0 0 0 0 0 20430 2279 11 11 83272 0 0 0 0 0 0 0 5754 220829 0 154753 908545 1824602 7314228 0 0 0 6898259 0 0 10 0 0 2 0 0 0 0 0 0 0 42 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 575816 1447531 134022 0 0 0 0 0 435008 319921 2755476 0 0 0 0 91 310212 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 4 0 0 545 901 554 9 3377 4184 12 10 588851 0 2 1109045 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 8 0 0 0 0 0 0 0 0 0 0 0 0 497 0 0 0 0 0 26172 0 0 0 0 0 0 0 1362 0 0 0 0 0 0 0 424 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 23427 0 0 0 0 1 1298 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 108 0 0 0 0 86 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1784935 407979 2140 10562241 52374 74699 6976 84926 222 169088 0 0 0 0 174 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2789 51543 0 83 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 8 0 13 11 17 1393 0 0 0 0 0 0 0 0 0 0 26 0 0 2 106 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 11150 0 13 0 1 390 6 0 6 4 0 0 0 0 352 284743 2 0 0 24 3 0 3 0 0 0 12 0 668788 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 680 0 0
+ctxt 373122860
+btime 1536912218
+processes 243320
+procs_running 1
+procs_blocked 0
+softirq 84611084 10220177 28299167 155083 3035679 6390543 66234 4396819 15604187 0 16443195)";
+
+base::ScopedFile MockOpenReadOnly(const char* path) {
+  base::TempFile tmp_ = base::TempFile::CreateUnlinked();
+  if (!strcmp(path, "/proc/meminfo")) {
+    EXPECT_GT(pwrite(tmp_.fd(), kMockMeminfo, strlen(kMockMeminfo), 0), 0);
+  } else if (!strcmp(path, "/proc/vmstat")) {
+    EXPECT_GT(pwrite(tmp_.fd(), kMockVmstat, strlen(kMockVmstat), 0), 0);
+  } else if (!strcmp(path, "/proc/stat")) {
+    EXPECT_GT(pwrite(tmp_.fd(), kMockStat, strlen(kMockStat), 0), 0);
+  } else {
+    PERFETTO_FATAL("Unexpected file opened %s", path);
+  }
+  return tmp_.ReleaseFD();
+}
+
+class SysStatsDataSourceTest : public ::testing::Test {
+ protected:
+  std::unique_ptr<SysStatsDataSource> GetSysStatsDataSource(
+      const DataSourceConfig& cfg) {
+    auto writer =
+        std::unique_ptr<TraceWriterForTesting>(new TraceWriterForTesting());
+    writer_raw_ = writer.get();
+    auto instance = std::unique_ptr<SysStatsDataSource>(new SysStatsDataSource(
+        &task_runner_, 0, std::move(writer), cfg, MockOpenReadOnly));
+    instance->set_ns_per_user_hz_for_testing(1000000000ull / 100);  // 100 Hz.
+    return instance;
+  }
+
+  void Poller(SysStatsDataSource* ds, std::function<void()> checkpoint) {
+    if (ds->tick_for_testing())
+      checkpoint();
+    else
+      task_runner_.PostDelayedTask(
+          [ds, checkpoint, this] { Poller(ds, checkpoint); }, 1);
+  }
+
+  void WaitTick(SysStatsDataSource* data_source) {
+    auto checkpoint = task_runner_.CreateCheckpoint("on_tick");
+    Poller(data_source, checkpoint);
+    task_runner_.RunUntilCheckpoint("on_tick");
+  }
+
+  TraceWriterForTesting* writer_raw_ = nullptr;
+  base::TestTaskRunner task_runner_;
+};
+
+TEST_F(SysStatsDataSourceTest, Meminfo) {
+  using C = protos::MeminfoCounters;
+  protos::DataSourceConfig config;
+  config.mutable_sys_stats_config()->set_meminfo_period_ms(1);
+  config.mutable_sys_stats_config()->add_meminfo_counters(C::MEMINFO_MEM_TOTAL);
+  config.mutable_sys_stats_config()->add_meminfo_counters(C::MEMINFO_MEM_FREE);
+  config.mutable_sys_stats_config()->add_meminfo_counters(
+      C::MEMINFO_ACTIVE_ANON);
+  config.mutable_sys_stats_config()->add_meminfo_counters(
+      C::MEMINFO_INACTIVE_FILE);
+  config.mutable_sys_stats_config()->add_meminfo_counters(C::MEMINFO_CMA_FREE);
+  DataSourceConfig config_obj;
+  config_obj.FromProto(config);
+  auto data_source = GetSysStatsDataSource(config_obj);
+
+  WaitTick(data_source.get());
+
+  std::unique_ptr<protos::TracePacket> packet = writer_raw_->ParseProto();
+  ASSERT_TRUE(packet->has_sys_stats());
+  const auto& sys_stats = packet->sys_stats();
+  EXPECT_EQ(sys_stats.vmstat_size(), 0);
+  EXPECT_EQ(sys_stats.cpu_stat_size(), 0);
+
+  using KV = std::pair<int, uint64_t>;
+  std::vector<KV> kvs;
+  for (const auto& kv : sys_stats.meminfo())
+    kvs.push_back({kv.key(), kv.value()});
+
+  EXPECT_THAT(kvs,
+              UnorderedElementsAre(KV{C::MEMINFO_MEM_TOTAL, 3744240},     //
+                                   KV{C::MEMINFO_MEM_FREE, 73328},        //
+                                   KV{C::MEMINFO_ACTIVE_ANON, 1322636},   //
+                                   KV{C::MEMINFO_INACTIVE_FILE, 296320},  //
+                                   KV{C::MEMINFO_CMA_FREE, 60}));
+}
+
+TEST_F(SysStatsDataSourceTest, Vmstat) {
+  using C = protos::VmstatCounters;
+  protos::DataSourceConfig config;
+  config.mutable_sys_stats_config()->set_vmstat_period_ms(1);
+  config.mutable_sys_stats_config()->add_vmstat_counters(
+      C::VMSTAT_NR_FREE_PAGES);
+  config.mutable_sys_stats_config()->add_vmstat_counters(C::VMSTAT_PGACTIVATE);
+  config.mutable_sys_stats_config()->add_vmstat_counters(
+      C::VMSTAT_PGMIGRATE_FAIL);
+  DataSourceConfig config_obj;
+  config_obj.FromProto(config);
+  auto data_source = GetSysStatsDataSource(config_obj);
+
+  WaitTick(data_source.get());
+
+  std::unique_ptr<protos::TracePacket> packet = writer_raw_->ParseProto();
+  ASSERT_TRUE(packet->has_sys_stats());
+  const auto& sys_stats = packet->sys_stats();
+  EXPECT_EQ(sys_stats.meminfo_size(), 0);
+  EXPECT_EQ(sys_stats.cpu_stat_size(), 0);
+
+  using KV = std::pair<int, uint64_t>;
+  std::vector<KV> kvs;
+  for (const auto& kv : sys_stats.vmstat())
+    kvs.push_back({kv.key(), kv.value()});
+
+  EXPECT_THAT(kvs, UnorderedElementsAre(KV{C::VMSTAT_NR_FREE_PAGES, 16449},  //
+                                        KV{C::VMSTAT_PGACTIVATE, 11897892},  //
+                                        KV{C::VMSTAT_PGMIGRATE_FAIL, 3439}));
+}
+
+TEST_F(SysStatsDataSourceTest, StatAll) {
+  using C = protos::SysStatsConfig;
+  protos::DataSourceConfig config;
+  config.mutable_sys_stats_config()->set_stat_period_ms(1);
+  config.mutable_sys_stats_config()->add_stat_counters(C::STAT_CPU_TIMES);
+  config.mutable_sys_stats_config()->add_stat_counters(C::STAT_IRQ_COUNTS);
+  config.mutable_sys_stats_config()->add_stat_counters(C::STAT_SOFTIRQ_COUNTS);
+  config.mutable_sys_stats_config()->add_stat_counters(C::STAT_FORK_COUNT);
+  DataSourceConfig config_obj;
+  config_obj.FromProto(config);
+  auto data_source = GetSysStatsDataSource(config_obj);
+
+  WaitTick(data_source.get());
+
+  std::unique_ptr<protos::TracePacket> packet = writer_raw_->ParseProto();
+  ASSERT_TRUE(packet);
+  ASSERT_TRUE(packet->has_sys_stats());
+  const auto& sys_stats = packet->sys_stats();
+  EXPECT_EQ(sys_stats.meminfo_size(), 0);
+  EXPECT_EQ(sys_stats.vmstat_size(), 0);
+
+  ASSERT_EQ(sys_stats.cpu_stat_size(), 8);
+  EXPECT_EQ(sys_stats.cpu_stat(0).user_ns(), 762178 * 10000000ull);
+  EXPECT_EQ(sys_stats.cpu_stat(0).system_mode_ns(), 902284 * 10000000ull);
+  EXPECT_EQ(sys_stats.cpu_stat(0).softirq_ns(), 68262 * 10000000ull);
+  EXPECT_EQ(sys_stats.cpu_stat(7).user_ns(), 180484 * 10000000ull);
+  EXPECT_EQ(sys_stats.cpu_stat(7).system_mode_ns(), 139874 * 10000000ull);
+  EXPECT_EQ(sys_stats.cpu_stat(7).softirq_ns(), 13407 * 10000000ull);
+
+  EXPECT_EQ(sys_stats.num_forks(), 243320);
+
+  EXPECT_EQ(sys_stats.num_irq_total(), 238128517);
+  ASSERT_EQ(sys_stats.num_irq_size(), 793);
+  EXPECT_EQ(sys_stats.num_irq(0).count(), 0);
+  EXPECT_EQ(sys_stats.num_irq(3).count(), 63500984);
+  EXPECT_EQ(sys_stats.num_irq(790).count(), 680);
+
+  EXPECT_EQ(sys_stats.num_softirq_total(), 84611084);
+  ASSERT_EQ(sys_stats.num_softirq_size(), 10);
+  EXPECT_EQ(sys_stats.num_softirq(0).count(), 10220177);
+  EXPECT_EQ(sys_stats.num_softirq(9).count(), 16443195);
+
+  EXPECT_EQ(sys_stats.num_softirq_total(), 84611084);
+}
+
+TEST_F(SysStatsDataSourceTest, StatForksOnly) {
+  using C = protos::SysStatsConfig;
+  protos::DataSourceConfig config;
+  config.mutable_sys_stats_config()->set_stat_period_ms(1);
+  config.mutable_sys_stats_config()->add_stat_counters(C::STAT_FORK_COUNT);
+  DataSourceConfig config_obj;
+  config_obj.FromProto(config);
+  auto data_source = GetSysStatsDataSource(config_obj);
+
+  WaitTick(data_source.get());
+
+  std::unique_ptr<protos::TracePacket> packet = writer_raw_->ParseProto();
+  ASSERT_TRUE(packet->has_sys_stats());
+  const auto& sys_stats = packet->sys_stats();
+  EXPECT_EQ(sys_stats.meminfo_size(), 0);
+  EXPECT_EQ(sys_stats.vmstat_size(), 0);
+  ASSERT_EQ(sys_stats.cpu_stat_size(), 0);
+  EXPECT_EQ(sys_stats.num_forks(), 243320);
+  EXPECT_EQ(sys_stats.num_irq_total(), 0);
+  ASSERT_EQ(sys_stats.num_irq_size(), 0);
+  EXPECT_EQ(sys_stats.num_softirq_total(), 0);
+  ASSERT_EQ(sys_stats.num_softirq_size(), 0);
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index 2e8831f..4455bfb 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -19,7 +19,7 @@
 source_set("tracing") {
   public_deps = [
     "../../include/perfetto/tracing/core",
-    "../../protos/perfetto/common",
+    "../../protos/perfetto/common:lite",
     "../../protos/perfetto/trace:minimal_lite",
     "../../protos/perfetto/trace:trusted_lite",
     "../../protos/perfetto/trace:zero",
@@ -27,7 +27,7 @@
   deps = [
     "../../gn:default_deps",
     "../../gn:gtest_prod_config",
-    "../../protos/perfetto/config",
+    "../../protos/perfetto/config:lite",
     "../base",
     "../protozero",
   ]
@@ -51,6 +51,7 @@
     "core/shared_memory_arbiter_impl.h",
     "core/sliced_protobuf_input_stream.cc",
     "core/sliced_protobuf_input_stream.h",
+    "core/sys_stats_config.cc",
     "core/test_config.cc",
     "core/trace_buffer.cc",
     "core/trace_buffer.h",
@@ -71,7 +72,7 @@
     ":tracing",
     "../../gn:default_deps",
     "../../gn:gtest_deps",
-    "../../protos/perfetto/config",
+    "../../protos/perfetto/config:lite",
     "../../protos/perfetto/trace:lite",
     "../../protos/perfetto/trace:zero",
     "../base",
diff --git a/src/tracing/core/data_source_config.cc b/src/tracing/core/data_source_config.cc
index 0addd0e..405b430 100644
--- a/src/tracing/core/data_source_config.cc
+++ b/src/tracing/core/data_source_config.cc
@@ -32,6 +32,7 @@
 #include "perfetto/config/ftrace/ftrace_config.pb.h"
 #include "perfetto/config/inode_file/inode_file_config.pb.h"
 #include "perfetto/config/process_stats/process_stats_config.pb.h"
+#include "perfetto/config/sys_stats/sys_stats_config.pb.h"
 #include "perfetto/config/test_config.pb.h"
 
 namespace perfetto {
@@ -72,6 +73,8 @@
 
   process_stats_config_.FromProto(proto.process_stats_config());
 
+  sys_stats_config_.FromProto(proto.sys_stats_config());
+
   static_assert(sizeof(legacy_config_) == sizeof(proto.legacy_config()),
                 "size mismatch");
   legacy_config_ = static_cast<decltype(legacy_config_)>(proto.legacy_config());
@@ -112,6 +115,8 @@
 
   process_stats_config_.ToProto(proto->mutable_process_stats_config());
 
+  sys_stats_config_.ToProto(proto->mutable_sys_stats_config());
+
   static_assert(sizeof(legacy_config_) == sizeof(proto->legacy_config()),
                 "size mismatch");
   proto->set_legacy_config(
diff --git a/src/tracing/core/sys_stats_config.cc b/src/tracing/core/sys_stats_config.cc
new file mode 100644
index 0000000..444fc84
--- /dev/null
+++ b/src/tracing/core/sys_stats_config.cc
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/*******************************************************************************
+ * AUTOGENERATED - DO NOT EDIT
+ *******************************************************************************
+ * This file has been generated from the protobuf message
+ * perfetto/config/sys_stats/sys_stats_config.proto
+ * by
+ * ../../tools/proto_to_cpp/proto_to_cpp.cc.
+ * If you need to make changes here, change the .proto file and then run
+ * ./tools/gen_tracing_cpp_headers_from_protos.py
+ */
+
+#include "perfetto/tracing/core/sys_stats_config.h"
+
+#include "perfetto/common/sys_stats_counters.pb.h"
+#include "perfetto/config/sys_stats/sys_stats_config.pb.h"
+
+namespace perfetto {
+
+SysStatsConfig::SysStatsConfig() = default;
+SysStatsConfig::~SysStatsConfig() = default;
+SysStatsConfig::SysStatsConfig(const SysStatsConfig&) = default;
+SysStatsConfig& SysStatsConfig::operator=(const SysStatsConfig&) = default;
+SysStatsConfig::SysStatsConfig(SysStatsConfig&&) noexcept = default;
+SysStatsConfig& SysStatsConfig::operator=(SysStatsConfig&&) = default;
+
+void SysStatsConfig::FromProto(const perfetto::protos::SysStatsConfig& proto) {
+  static_assert(sizeof(meminfo_period_ms_) == sizeof(proto.meminfo_period_ms()),
+                "size mismatch");
+  meminfo_period_ms_ =
+      static_cast<decltype(meminfo_period_ms_)>(proto.meminfo_period_ms());
+
+  meminfo_counters_.clear();
+  for (const auto& field : proto.meminfo_counters()) {
+    meminfo_counters_.emplace_back();
+    static_assert(
+        sizeof(meminfo_counters_.back()) == sizeof(proto.meminfo_counters(0)),
+        "size mismatch");
+    meminfo_counters_.back() =
+        static_cast<decltype(meminfo_counters_)::value_type>(field);
+  }
+
+  static_assert(sizeof(vmstat_period_ms_) == sizeof(proto.vmstat_period_ms()),
+                "size mismatch");
+  vmstat_period_ms_ =
+      static_cast<decltype(vmstat_period_ms_)>(proto.vmstat_period_ms());
+
+  vmstat_counters_.clear();
+  for (const auto& field : proto.vmstat_counters()) {
+    vmstat_counters_.emplace_back();
+    static_assert(
+        sizeof(vmstat_counters_.back()) == sizeof(proto.vmstat_counters(0)),
+        "size mismatch");
+    vmstat_counters_.back() =
+        static_cast<decltype(vmstat_counters_)::value_type>(field);
+  }
+
+  static_assert(sizeof(stat_period_ms_) == sizeof(proto.stat_period_ms()),
+                "size mismatch");
+  stat_period_ms_ =
+      static_cast<decltype(stat_period_ms_)>(proto.stat_period_ms());
+
+  stat_counters_.clear();
+  for (const auto& field : proto.stat_counters()) {
+    stat_counters_.emplace_back();
+    static_assert(
+        sizeof(stat_counters_.back()) == sizeof(proto.stat_counters(0)),
+        "size mismatch");
+    stat_counters_.back() =
+        static_cast<decltype(stat_counters_)::value_type>(field);
+  }
+  unknown_fields_ = proto.unknown_fields();
+}
+
+void SysStatsConfig::ToProto(perfetto::protos::SysStatsConfig* proto) const {
+  proto->Clear();
+
+  static_assert(
+      sizeof(meminfo_period_ms_) == sizeof(proto->meminfo_period_ms()),
+      "size mismatch");
+  proto->set_meminfo_period_ms(
+      static_cast<decltype(proto->meminfo_period_ms())>(meminfo_period_ms_));
+
+  for (const auto& it : meminfo_counters_) {
+    proto->add_meminfo_counters(
+        static_cast<decltype(proto->meminfo_counters(0))>(it));
+    static_assert(sizeof(it) == sizeof(proto->meminfo_counters(0)),
+                  "size mismatch");
+  }
+
+  static_assert(sizeof(vmstat_period_ms_) == sizeof(proto->vmstat_period_ms()),
+                "size mismatch");
+  proto->set_vmstat_period_ms(
+      static_cast<decltype(proto->vmstat_period_ms())>(vmstat_period_ms_));
+
+  for (const auto& it : vmstat_counters_) {
+    proto->add_vmstat_counters(
+        static_cast<decltype(proto->vmstat_counters(0))>(it));
+    static_assert(sizeof(it) == sizeof(proto->vmstat_counters(0)),
+                  "size mismatch");
+  }
+
+  static_assert(sizeof(stat_period_ms_) == sizeof(proto->stat_period_ms()),
+                "size mismatch");
+  proto->set_stat_period_ms(
+      static_cast<decltype(proto->stat_period_ms())>(stat_period_ms_));
+
+  for (const auto& it : stat_counters_) {
+    proto->add_stat_counters(
+        static_cast<decltype(proto->stat_counters(0))>(it));
+    static_assert(sizeof(it) == sizeof(proto->stat_counters(0)),
+                  "size mismatch");
+  }
+  *(proto->mutable_unknown_fields()) = unknown_fields_;
+}
+
+}  // namespace perfetto
diff --git a/src/tracing/core/sys_stats_counters.cc b/src/tracing/core/sys_stats_counters.cc
new file mode 100644
index 0000000..6edb5b1
--- /dev/null
+++ b/src/tracing/core/sys_stats_counters.cc
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/*******************************************************************************
+ * AUTOGENERATED - DO NOT EDIT
+ *******************************************************************************
+ * This file has been generated from the protobuf message
+ * perfetto/common/sys_stats_counters.proto
+ * by
+ * ../../tools/proto_to_cpp/proto_to_cpp.cc.
+ * If you need to make changes here, change the .proto file and then run
+ * ./tools/gen_tracing_cpp_headers_from_protos.py
+ */
+
+#include "perfetto/tracing/core/sys_stats_counters.h"
+
+#include "perfetto/common/sys_stats_counters.pb.h"
+
+namespace perfetto {}  // namespace perfetto
diff --git a/src/tracing/core/trace_writer_for_testing.cc b/src/tracing/core/trace_writer_for_testing.cc
index 1b7e531..3f95c0f 100644
--- a/src/tracing/core/trace_writer_for_testing.cc
+++ b/src/tracing/core/trace_writer_for_testing.cc
@@ -44,12 +44,9 @@
 
 std::unique_ptr<protos::TracePacket> TraceWriterForTesting::ParseProto() {
   PERFETTO_CHECK(cur_packet_->is_finalized());
-  size_t chunk_size_ = base::kPageSize;
   auto packet = std::unique_ptr<protos::TracePacket>(new protos::TracePacket());
-  size_t msg_size =
-      delegate_.chunks().size() * chunk_size_ - stream_.bytes_available();
-  std::unique_ptr<uint8_t[]> buffer = delegate_.StitchChunks(msg_size);
-  if (!packet->ParseFromArray(buffer.get(), static_cast<int>(msg_size)))
+  std::vector<uint8_t> buffer = delegate_.StitchChunks();
+  if (!packet->ParseFromArray(buffer.data(), static_cast<int>(buffer.size())))
     return nullptr;
   return packet;
 }
diff --git a/test/configs/BUILD.gn b/test/configs/BUILD.gn
index 1505529..cbc0970 100644
--- a/test/configs/BUILD.gn
+++ b/test/configs/BUILD.gn
@@ -30,6 +30,7 @@
       "long_trace.cfg",
       "processes.cfg",
       "summary.cfg",
+      "sys_stats.cfg",
     ]
 
     outputs = [
diff --git a/test/configs/sys_stats.cfg b/test/configs/sys_stats.cfg
new file mode 100644
index 0000000..fd4f6ea
--- /dev/null
+++ b/test/configs/sys_stats.cfg
@@ -0,0 +1,89 @@
+# Example config for a trace that polls system counters.
+
+duration_ms: 2000
+
+buffers {
+  size_kb: 16384
+  fill_policy: RING_BUFFER
+}
+
+buffers {
+  size_kb: 16384
+  fill_policy: RING_BUFFER
+}
+
+# Enable various data sources as usual.
+data_sources {
+  config {
+    name: "linux.ftrace"
+    target_buffer: 0
+    ftrace_config {
+      # These parameters affect only the kernel trace buffer size and how
+      # frequently it gets moved into the userspace buffer defined above.
+      buffer_size_kb: 16384
+      drain_period_ms: 250
+      ftrace_events: "cpu_frequency"
+      ftrace_events: "cpu_idle"
+      ftrace_events: "sched_switch"
+      ftrace_events: "tracing_mark_write"
+    }
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.sys_stats"
+    target_buffer: 1
+    sys_stats_config {
+      meminfo_period_ms: 100
+      meminfo_counters: MEMINFO_MEM_AVAILABLE
+      meminfo_counters: MEMINFO_BUFFERS
+      meminfo_counters: MEMINFO_CACHED
+      meminfo_counters: MEMINFO_SWAP_CACHED
+      meminfo_counters: MEMINFO_ACTIVE
+      meminfo_counters: MEMINFO_INACTIVE
+      meminfo_counters: MEMINFO_ACTIVE_ANON
+      meminfo_counters: MEMINFO_INACTIVE_ANON
+      meminfo_counters: MEMINFO_ACTIVE_FILE
+      meminfo_counters: MEMINFO_INACTIVE_FILE
+      meminfo_counters: MEMINFO_UNEVICTABLE
+
+      vmstat_period_ms: 100
+      vmstat_counters: VMSTAT_NR_FREE_PAGES
+      vmstat_counters: VMSTAT_NR_ALLOC_BATCH
+      vmstat_counters: VMSTAT_NR_INACTIVE_ANON
+      vmstat_counters: VMSTAT_NR_ACTIVE_ANON
+      vmstat_counters: VMSTAT_NR_INACTIVE_FILE
+      vmstat_counters: VMSTAT_NR_ACTIVE_FILE
+      vmstat_counters: VMSTAT_NR_UNEVICTABLE
+      vmstat_counters: VMSTAT_NR_MLOCK
+      vmstat_counters: VMSTAT_NR_ANON_PAGES
+      vmstat_counters: VMSTAT_NR_MAPPED
+      vmstat_counters: VMSTAT_NR_FILE_PAGES
+      vmstat_counters: VMSTAT_NR_DIRTY
+      vmstat_counters: VMSTAT_NR_WRITEBACK
+      vmstat_counters: VMSTAT_NR_SLAB_RECLAIMABLE
+      vmstat_counters: VMSTAT_NR_SLAB_UNRECLAIMABLE
+      vmstat_counters: VMSTAT_NR_PAGE_TABLE_PAGES
+      vmstat_counters: VMSTAT_NR_KERNEL_STACK
+      vmstat_counters: VMSTAT_NR_OVERHEAD
+      vmstat_counters: VMSTAT_NR_UNSTABLE
+      vmstat_counters: VMSTAT_NR_BOUNCE
+      vmstat_counters: VMSTAT_NR_VMSCAN_WRITE
+      vmstat_counters: VMSTAT_NR_VMSCAN_IMMEDIATE_RECLAIM
+      vmstat_counters: VMSTAT_NR_WRITEBACK_TEMP
+
+      stat_period_ms: 100
+      stat_counters: STAT_CPU_TIMES
+      stat_counters: STAT_IRQ_COUNTS
+      stat_counters: STAT_FORK_COUNT
+    }
+  }
+}
\ No newline at end of file
diff --git a/tools/gen_merged_protos b/tools/gen_merged_protos
index 77bab2b..8d42fcd 100755
--- a/tools/gen_merged_protos
+++ b/tools/gen_merged_protos
@@ -22,11 +22,13 @@
 import sys
 
 CONFIG_PROTOS = (
+  'protos/perfetto/common/sys_stats_counters.proto',
   'protos/perfetto/config/chrome/chrome_config.proto',
-  'protos/perfetto/config/inode_file/inode_file_config.proto',
-  'protos/perfetto/config/process_stats/process_stats_config.proto',
   'protos/perfetto/config/data_source_config.proto',
   'protos/perfetto/config/ftrace/ftrace_config.proto',
+  'protos/perfetto/config/inode_file/inode_file_config.proto',
+  'protos/perfetto/config/process_stats/process_stats_config.proto',
+  'protos/perfetto/config/sys_stats/sys_stats_config.proto',
   'protos/perfetto/config/test_config.proto',
   'protos/perfetto/config/trace_config.proto',
 )
@@ -34,12 +36,15 @@
 MERGED_CONFIG_PROTO = 'protos/perfetto/config/perfetto_config.proto'
 
 TRACE_PROTOS = (
+  'protos/perfetto/common/sys_stats_counters.proto',
   'protos/perfetto/trace/trace.proto',
   'protos/perfetto/trace/trace_packet.proto',
   'protos/perfetto/trace/ftrace/ftrace_event_bundle.proto',
   'protos/perfetto/trace/ftrace/ftrace_event.proto',
   'protos/perfetto/trace/filesystem/inode_file_map.proto',
   'protos/perfetto/trace/ps/process_tree.proto',
+  'protos/perfetto/trace/clock_snapshot.proto',
+  'protos/perfetto/trace/sys_stats/sys_stats.proto',
 
   # Print proto is special: it doesn't have a enable file so is
   # not present in genfs_contexts.
diff --git a/tools/gen_tracing_cpp_headers_from_protos.py b/tools/gen_tracing_cpp_headers_from_protos.py
index ce7fdb6..901d73e 100755
--- a/tools/gen_tracing_cpp_headers_from_protos.py
+++ b/tools/gen_tracing_cpp_headers_from_protos.py
@@ -22,11 +22,13 @@
   'perfetto/config/data_source_config.proto',
   'perfetto/config/inode_file/inode_file_config.proto',
   'perfetto/config/process_stats/process_stats_config.proto',
+  'perfetto/config/sys_stats/sys_stats_config.proto',
   'perfetto/config/data_source_descriptor.proto',
   'perfetto/config/ftrace/ftrace_config.proto',
   'perfetto/config/trace_config.proto',
   'perfetto/config/test_config.proto',
   'perfetto/common/commit_data_request.proto',
+  'perfetto/common/sys_stats_counters.proto',
 )
 
 HEADER_PATH = 'include/perfetto/tracing/core'
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 5368905..d9512b5 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -178,6 +178,13 @@
    '4adec454b60a943dd58603d4be80d42b2db62cbd',
    'all',
   ),
+
+  # Linenoise
+  ('buildtools/linenoise',
+   'https://github.com/antirez/linenoise.git',
+   '4a961c0108720741e2683868eb10495f015ee422',
+   'all'
+  ),
 ]
 
 # Dependencies required to build Android code.
diff --git a/tools/proto_to_cpp/proto_to_cpp.cc b/tools/proto_to_cpp/proto_to_cpp.cc
index 2a77542..ac773ea 100644
--- a/tools/proto_to_cpp/proto_to_cpp.cc
+++ b/tools/proto_to_cpp/proto_to_cpp.cc
@@ -270,7 +270,7 @@
 
   cpp_printer.Print("}  // namespace perfetto\n");
   header_printer.Print("}  // namespace perfetto\n");
-  header_printer.Print("#endif  // $g$\n", "g", include_guard);
+  header_printer.Print("\n#endif  // $g$\n", "g", include_guard);
 }
 
 void ProtoToCpp::GenHeader(const Descriptor* msg, Printer* p) {
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index 56b24bc..ba7cd9f 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -20,6 +20,7 @@
     "../../gn:default_deps",
     "../../gn:protobuf_full_deps",
     "../../include/perfetto/base",
+    "../../include/perfetto/traced:sys_stats_counters",
     "../../protos/perfetto/trace:lite",
     "../../protos/perfetto/trace/ftrace:lite",
   ]
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index b775326..709d139 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -43,6 +43,7 @@
 #include "perfetto/trace/ftrace/ftrace_stats.pb.h"
 #include "perfetto/trace/trace.pb.h"
 #include "perfetto/trace/trace_packet.pb.h"
+#include "perfetto/traced/sys_stats_counters.h"
 #include "tools/trace_to_text/ftrace_event_formatter.h"
 #include "tools/trace_to_text/ftrace_inode_handler.h"
 
@@ -92,6 +93,7 @@
 using protos::FtraceStats;
 using protos::FtraceStats_Phase_START_OF_TRACE;
 using protos::FtraceStats_Phase_END_OF_TRACE;
+using protos::SysStats;
 using Entry = protos::InodeFileMap::Entry;
 using Process = protos::ProcessTree::Process;
 
@@ -176,17 +178,46 @@
                     bool wrap_in_json) {
   std::multimap<uint64_t, std::string> sorted;
 
-  ForEachPacketInTrace(input, [&sorted](const protos::TracePacket& packet) {
-    if (!packet.has_ftrace_events())
-      return;
+  std::vector<const char*> meminfo_strs = BuildMeminfoCounterNames();
+  std::vector<const char*> vmstat_strs = BuildVmstatCounterNames();
 
-    const FtraceEventBundle& bundle = packet.ftrace_events();
-    for (const FtraceEvent& event : bundle.event()) {
-      std::string line =
-          FormatFtraceEvent(event.timestamp(), bundle.cpu(), event);
-      if (line == "")
-        continue;
-      sorted.emplace(event.timestamp(), line);
+  ForEachPacketInTrace(input, [&sorted, &meminfo_strs, &vmstat_strs](
+                                  const protos::TracePacket& packet) {
+    if (packet.has_ftrace_events()) {
+      const FtraceEventBundle& bundle = packet.ftrace_events();
+      for (const FtraceEvent& event : bundle.event()) {
+        std::string line =
+            FormatFtraceEvent(event.timestamp(), bundle.cpu(), event);
+        if (line == "")
+          continue;
+        sorted.emplace(event.timestamp(), line);
+      }
+    }  // packet.has_ftrace_events
+
+    if (packet.has_sys_stats()) {
+      const SysStats& sys_stats = packet.sys_stats();
+      for (const auto& meminfo : sys_stats.meminfo()) {
+        FtraceEvent event;
+        uint64_t ts = static_cast<uint64_t>(packet.timestamp());
+        char str[256];
+        event.set_timestamp(ts);
+        event.set_pid(1);
+        sprintf(str, "C|1|%s|%" PRIu64, meminfo_strs[meminfo.key()],
+                static_cast<uint64_t>(meminfo.value()));
+        event.mutable_print()->set_buf(str);
+        sorted.emplace(ts, FormatFtraceEvent(ts, 0, event));
+      }
+      for (const auto& vmstat : sys_stats.vmstat()) {
+        FtraceEvent event;
+        uint64_t ts = static_cast<uint64_t>(packet.timestamp());
+        char str[256];
+        event.set_timestamp(ts);
+        event.set_pid(1);
+        sprintf(str, "C|1|%s|%" PRIu64, vmstat_strs[vmstat.key()],
+                static_cast<uint64_t>(vmstat.value()));
+        event.mutable_print()->set_buf(str);
+        sorted.emplace(ts, FormatFtraceEvent(ts, 0, event));
+      }
     }
   });
 
diff --git a/ui/BUILD.gn b/ui/BUILD.gn
index 104b6e8..8783c9f 100644
--- a/ui/BUILD.gn
+++ b/ui/BUILD.gn
@@ -260,6 +260,7 @@
     main_css,
     "src/assets/sidebar.scss",
     "src/assets/topbar.scss",
+    "src/assets/record.scss",
   ]
   outputs = [
     "$ui_dir/perfetto.css",
@@ -347,6 +348,9 @@
     "$root_build_dir/wasm/trace_processor.js",
     "$root_build_dir/wasm/trace_processor.wasm",
   ]
+  if (is_debug) {
+    sources += [ "$root_build_dir/wasm/trace_processor.wasm.map" ]
+  }
   outputs = [
     "$ui_gen_dir/{{source_file_part}}",
   ]
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 95daabd..cac3d75 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -143,6 +143,8 @@
 
 @import 'sidebar';
 @import 'topbar';
+@import 'record';
+
 .home-page {
     text-align: center;
     padding-top: 20vh;
@@ -309,3 +311,77 @@
 header {
   height: 25px;
 }
+
+.text-column {
+  font-size: 115%;
+  // 2-3 alphabets per line is comfortable for reading.
+  // https://practicaltypography.com/line-length.html 
+  max-width: calc(26ch*2.34);
+  margin: 3rem auto;
+  user-select: text;
+  word-break: break-word;
+}
+
+.debug-panel-border {
+  position: absolute;
+  top: 0px;
+  height: 100%;
+  width: 100%;
+  border: 1px solid rgba(69, 187, 73, 0.5);
+  pointer-events: none;
+}
+
+.perf-stats {
+  --perfetto-orange: hsl(45, 100%, 48%);
+  --perfetto-red: hsl(6, 70%, 53%);
+  --stroke-color: hsl(217, 39%, 94%);
+  position: fixed;
+  bottom: 0;
+  color: var(--stroke-color);
+  font-family: monospace;
+  padding: 2px 0px;
+  z-index: 100;
+  button:hover {
+    color: var(--perfetto-red);
+  }
+  &[expanded=true] {
+    width: 600px;
+    background-color: rgba(27, 28, 29, 0.95);
+    button {
+      color: var(--perfetto-orange);
+      &:hover {
+        color: var(--perfetto-red);
+      }
+    }
+  }
+  &[expanded=false] {
+    width: var(--sidebar-width);
+    background-color: transparent;
+  }
+  i {
+    margin: 0px 24px;
+    font-size: 30px;
+  }
+  .perf-stats-content {
+    margin: 10px 24px;
+    & > section {
+      padding: 5px;
+      border-bottom: 1px solid var(--stroke-color);
+    }
+    button {
+      text-decoration: underline;
+    }
+    div {
+      margin: 2px 0px;
+    }
+    table, td, th {
+      border: 1px solid var(--stroke-color);
+      text-align: center;
+      padding: 4px;
+      margin: 4px 0px;
+    }
+    table {
+      border-collapse: collapse;
+    }
+  }
+}
diff --git a/ui/src/assets/record.scss b/ui/src/assets/record.scss
new file mode 100644
index 0000000..e46456a
--- /dev/null
+++ b/ui/src/assets/record.scss
@@ -0,0 +1,50 @@
+// 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.
+
+.example-code {
+  display: block;
+  padding: 1rem;
+  background-color: black;
+  color: white;
+  margin: 1rem 0;
+  margin-top: calc(20px + 1rem);
+  border-radius: 3px;
+  position: relative;
+  border-top-right-radius: 4px;
+  overflow: initial;
+  user-select: text;
+
+  ::before {
+    height: 20px;
+    content: "";
+    display: block;
+    width: 100%;
+    background-color: rgb(87%, 87%, 87%);
+    left: 0;
+    position: absolute;
+    right: 0;
+    top: -18px;
+    border-top-left-radius: 4px;
+    border-top-right-radius: 4px;
+  }
+
+  button {
+    margin-left: auto;
+    display: block;
+    font-style: italic;
+    font-size: 75%;
+  }
+}
+
+
diff --git a/ui/src/assets/topbar.scss b/ui/src/assets/topbar.scss
index 2ecd4ae..81a4a1b 100644
--- a/ui/src/assets/topbar.scss
+++ b/ui/src/assets/topbar.scss
@@ -180,4 +180,4 @@
             right: -90%;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index b06a44a..9da59d9 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -34,12 +34,14 @@
 }
 
 // TODO(hjd): Remove CPU and add a generic way to handle track specific state.
-export function addTrack(engineId: string, trackKind: string, cpu: number) {
+export function addTrack(
+    engineId: string, trackKind: string, name: string, config: {}) {
   return {
     type: 'ADD_TRACK',
     engineId,
     trackKind,
-    cpu,
+    name,
+    config,
   };
 }
 
@@ -52,25 +54,6 @@
   return {type: 'CLEAR_TRACK_DATA_REQ', trackId};
 }
 
-// TODO: There should be merged with addTrack above.
-export function addChromeSliceTrack(
-    engineId: string,
-    trackKind: string,
-    upid: number,
-    utid: number,
-    threadName: string,
-    maxDepth: number) {
-  return {
-    type: 'ADD_CHROME_TRACK',
-    engineId,
-    trackKind,
-    upid,
-    utid,
-    threadName,
-    maxDepth,
-  };
-}
-
 export function executeQuery(engineId: string, queryId: string, query: string) {
   return {
     type: 'EXECUTE_QUERY',
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index c1fe8b4..c3c9e21 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -22,16 +22,10 @@
 export interface TrackState {
   id: string;
   engineId: string;
-  maxDepth: number;
   kind: string;
   name: string;
-  // TODO: These need to be nested into track kind spesific state.
-  // cpu slice state:
-  cpu: number;
-  // chrome slice state:
-  upid?: number;
-  utid?: number;
   dataReq?: TrackDataRequest;
+  config: {};
 }
 
 export interface TrackDataRequest {
diff --git a/ui/src/controller/engine.ts b/ui/src/controller/engine.ts
index 81c3872..47f06e4 100644
--- a/ui/src/controller/engine.ts
+++ b/ui/src/controller/engine.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {IRawQueryArgs, RawQueryResult, TraceProcessor} from '../common/protos';
+import {RawQueryResult, TraceProcessor} from '../common/protos';
 import {TimeSpan} from '../common/time';
 
 /**
@@ -24,7 +24,7 @@
  * the given service.
  *
  * Engine also defines helpers for the most common service methods
- * (e.g. rawQuery).
+ * (e.g. query).
  */
 export abstract class Engine {
   abstract readonly id: string;
@@ -46,14 +46,15 @@
   abstract get rpc(): TraceProcessor;
 
   /**
-   * Send a raw SQL query to the engine.
+   * Shorthand for sending a SQL query to the engine.
+   * Exactly the same as engine.rpc.rawQuery({rawQuery});
    */
-  rawQuery(args: IRawQueryArgs): Promise<RawQueryResult> {
-    return this.rpc.rawQuery(args);
+  query(sqlQuery: string): Promise<RawQueryResult> {
+    return this.rpc.rawQuery({sqlQuery});
   }
 
-  async rawQueryOneRow(sqlQuery: string): Promise<number[]> {
-    const result = await this.rawQuery({sqlQuery});
+  async queryOneRow(query: string): Promise<number[]> {
+    const result = await this.query(query);
     const res: number[] = [];
     result.columns.map(c => res.push(+c.longValues![0]));
     return res;
@@ -62,18 +63,16 @@
   // TODO(hjd): Maybe we should cache result? But then Engine must be
   // streaming aware.
   async getNumberOfCpus(): Promise<number> {
-    const result = await this.rawQuery({
-      sqlQuery: 'select count(distinct(cpu)) as cpuCount from sched;',
-    });
+    const result =
+        await this.query('select count(distinct(cpu)) as cpuCount from sched;');
     return +result.columns[0].longValues![0];
   }
 
   // TODO: This should live in code that's more specific to chrome, instead of
   // in engine.
   async getNumberOfProcesses(): Promise<number> {
-    const result = await this.rawQuery({
-      sqlQuery: 'select count(distinct(upid)) from thread;',
-    });
+    const result =
+        await this.query('select count(distinct(upid)) from thread;');
     return +result.columns[0].longValues![0];
   }
 
@@ -82,13 +81,8 @@
         'union all select max(ts) as ts from slices)';
     const minQuery = 'select min(ts) from (select min(ts) as ts from sched ' +
         'union all select min(ts) as ts from slices)';
-    const start = (await this.rawQueryOneRow(minQuery))[0];
-    const end = (await this.rawQueryOneRow(maxQuery))[0];
+    const start = (await this.queryOneRow(minQuery))[0];
+    const end = (await this.queryOneRow(maxQuery))[0];
     return new TimeSpan(start / 1e9, end / 1e9);
   }
 }
-
-export interface EnginePortAndId {
-  id: string;
-  port: MessagePort;
-}
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 950b678..8a336fa 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -17,9 +17,13 @@
 import {Action} from '../common/actions';
 import {createEmptyState, State} from '../common/state';
 import {ControllerAny} from './controller';
-import {Engine, EnginePortAndId} from './engine';
+import {Engine} from './engine';
 import {rootReducer} from './reducer';
 import {WasmEngineProxy} from './wasm_engine_proxy';
+import {
+  createWasmEngine,
+  destroyWasmEngine,
+} from './wasm_engine_proxy';
 
 /**
  * Global accessors for state/dispatch in the controller.
@@ -79,14 +83,14 @@
     console.timeEnd(summary);
   }
 
-  async createEngine(): Promise<Engine> {
-    const portAndId = await assertExists(this._frontend)
-                          .send<EnginePortAndId>('createEngine', []);
-    return WasmEngineProxy.create(portAndId);
+  createEngine(): Engine {
+    const id = new Date().toUTCString();
+    const portAndId = {id, worker: createWasmEngine(id)};
+    return new WasmEngineProxy(portAndId);
   }
 
-  async destroyEngine(id: string): Promise<void> {
-    await assertExists(this._frontend).send<void>('destroyEngine', [id]);
+  destroyEngine(id: string): void {
+    destroyWasmEngine(id);
   }
 
   // TODO: this needs to be cleaned up.
diff --git a/ui/src/controller/index.ts b/ui/src/controller/index.ts
index 43d9535..28e095c 100644
--- a/ui/src/controller/index.ts
+++ b/ui/src/controller/index.ts
@@ -18,18 +18,21 @@
 
 import {AppController} from './app_controller';
 import {globals} from './globals';
+import {warmupWasmEngine} from './wasm_engine_proxy';
 
 function main(port: MessagePort) {
+  warmupWasmEngine();
   let receivedFrontendPort = false;
   port.onmessage = ({data}) => {
-    if (!receivedFrontendPort) {
-      const frontendPort = data as MessagePort;
-      const frontend = new Remote(frontendPort);
-      globals.initialize(new AppController(), frontend);
-      receivedFrontendPort = true;
-    } else {
+    if (receivedFrontendPort) {
       globals.dispatch(data);
+      return;
     }
+
+    const frontendPort = data as MessagePort;
+    const frontend = new Remote(frontendPort);
+    globals.initialize(new AppController(), frontend);
+    receivedFrontendPort = true;
   };
 }
 
diff --git a/ui/src/controller/query_controller.ts b/ui/src/controller/query_controller.ts
index c268d3a..a700f71 100644
--- a/ui/src/controller/query_controller.ts
+++ b/ui/src/controller/query_controller.ts
@@ -54,7 +54,7 @@
 
   private async runQuery(sqlQuery: string) {
     const startMs = performance.now();
-    const rawResult = await this.args.engine.rawQuery({sqlQuery});
+    const rawResult = await this.args.engine.query(sqlQuery);
     const durationMs = performance.now() - startMs;
     const columns = rawQueryResultColumns(rawResult);
     const rows =
diff --git a/ui/src/controller/reducer.ts b/ui/src/controller/reducer.ts
index 5988b6f..a969bc0 100644
--- a/ui/src/controller/reducer.ts
+++ b/ui/src/controller/reducer.ts
@@ -56,18 +56,19 @@
         id,
         engineId: action.engineId,
         kind: action.trackKind,
-        name: `Cpu Track ${id}`,
-        maxDepth: 1,
-        cpu: action.cpu,
+        name: action.name,
+        config: action.config,
       };
       nextState.scrollingTracks.push(id);
       return nextState;
     }
 
     case 'REQ_TRACK_DATA': {
+      const id = action.trackId;
       const nextState = {...state};
-      nextState.tracks = {...state.tracks};
-      nextState.tracks[action.trackId].dataReq = {
+      const nextTracks = nextState.tracks = {...state.tracks};
+      const nextTrack = nextTracks[id] = {...nextTracks[id]};
+      nextTrack.dataReq = {
         start: action.start,
         end: action.end,
         resolution: action.resolution
@@ -76,29 +77,11 @@
     }
 
     case 'CLEAR_TRACK_DATA_REQ': {
+      const id = action.trackId;
       const nextState = {...state};
-      nextState.tracks = {...state.tracks};
-      nextState.tracks[action.trackId].dataReq = undefined;
-      return nextState;
-    }
-
-    // TODO: 'ADD_CHROME_TRACK' string should be a shared const.
-    case 'ADD_CHROME_TRACK': {
-      const nextState = {...state};
-      nextState.tracks = {...state.tracks};
-      const id = `${nextState.nextId++}`;
-      nextState.tracks[id] = {
-        id,
-        engineId: action.engineId,
-        kind: action.trackKind,
-        name: `${action.threadName}`,
-        // TODO(dproy): This should be part of published information.
-        maxDepth: action.maxDepth,
-        cpu: 0,  // TODO: Remove this after we have kind specific state.
-        upid: action.upid,
-        utid: action.utid,
-      };
-      nextState.scrollingTracks.push(id);
+      const nextTracks = nextState.tracks = {...state.tracks};
+      const nextTrack = nextTracks[id] = {...nextTracks[id]};
+      nextTrack.dataReq = undefined;
       return nextState;
     }
 
diff --git a/ui/src/controller/reducer_unittest.ts b/ui/src/controller/reducer_unittest.ts
index b297ee3..4abf173 100644
--- a/ui/src/controller/reducer_unittest.ts
+++ b/ui/src/controller/reducer_unittest.ts
@@ -21,10 +21,9 @@
   const track: TrackState = {
     id,
     engineId: '1',
-    maxDepth: 0,
     kind: 'SOME_TRACK_KIND',
     name: 'A track',
-    cpu: 0,
+    config: {},
   };
   state.tracks[id] = track;
   return track;
@@ -60,13 +59,13 @@
     type: 'ADD_TRACK',
     engineId: '1',
     trackKind: 'cpu',
-    cpu: '1',
+    config: {},
   });
   const before = rootReducer(step1, {
     type: 'ADD_TRACK',
     engineId: '2',
     trackKind: 'cpu',
-    cpu: '2',
+    config: {},
   });
 
   const firstTrackId = before.scrollingTracks[0];
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index a7119c3..06f570f 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -17,7 +17,6 @@
 import {assertExists, assertTrue} from '../base/logging';
 import {
   Action,
-  addChromeSliceTrack,
   addTrack,
   navigate,
   setEngineReady,
@@ -113,7 +112,7 @@
   private async loadTrace() {
     globals.dispatch(updateStatus('Creating trace processor'));
     const engineCfg = assertExists(globals.state.engines[this.engineId]);
-    this.engine = await globals.createEngine();
+    this.engine = globals.createEngine();
 
     const statusHeader = 'Opening trace';
     if (engineCfg.source instanceof File) {
@@ -185,13 +184,14 @@
     const numCpus = await engine.getNumberOfCpus();
     for (let cpu = 0; cpu < numCpus; cpu++) {
       addToTrackActions.push(
-          addTrack(this.engineId, CPU_SLICE_TRACK_KIND, cpu));
+          addTrack(this.engineId, CPU_SLICE_TRACK_KIND, `Cpu ${cpu}`, {
+            cpu,
+          }));
     }
 
-    const threadQuery = await engine.rawQuery({
-      sqlQuery: 'select upid, utid, tid, thread.name, max(slices.depth) ' +
-          'from thread inner join slices using(utid) group by utid'
-    });
+    const threadQuery = await engine.query(
+        'select upid, utid, tid, thread.name, max(slices.depth) ' +
+        'from thread inner join slices using(utid) group by utid');
     for (let i = 0; i < threadQuery.numRecords; i++) {
       const upid = threadQuery.columns[0].longValues![i];
       const utid = threadQuery.columns[1].longValues![i];
@@ -199,13 +199,12 @@
       let threadName = threadQuery.columns[3].stringValues![i];
       threadName += `[${threadId}]`;
       const maxDepth = threadQuery.columns[4].longValues![i];
-      addToTrackActions.push(addChromeSliceTrack(
-          this.engineId,
-          SLICE_TRACK_KIND,
-          upid as number,
-          utid as number,
-          threadName,
-          maxDepth as number));
+      addToTrackActions.push(
+          addTrack(this.engineId, SLICE_TRACK_KIND, threadName, {
+            upid: upid as number,
+            utid: utid as number,
+            maxDepth: maxDepth as number,
+          }));
     }
     globals.dispatchMultiple(addToTrackActions);
   }
@@ -214,7 +213,7 @@
     globals.dispatch(updateStatus('Reading thread list'));
     const sqlQuery = 'select utid, tid, pid, thread.name, process.name ' +
         'from thread inner join process using(upid)';
-    const threadRows = await assertExists(this.engine).rawQuery({sqlQuery});
+    const threadRows = await assertExists(this.engine).query(sqlQuery);
     const threads: ThreadDesc[] = [];
     for (let i = 0; i < threadRows.numRecords; i++) {
       const utid = threadRows.columns[0].longValues![i] as number;
@@ -241,11 +240,10 @@
       const endNs = Math.ceil(endSec * 1e9);
 
       // Sched overview.
-      const schedRows = await engine.rawQuery({
-        sqlQuery: `select sum(dur)/${stepSec}/1e9, cpu from sched ` +
-            `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
-            'group by cpu order by cpu'
-      });
+      const schedRows = await engine.query(
+          `select sum(dur)/${stepSec}/1e9, cpu from sched ` +
+          `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
+          'group by cpu order by cpu');
       const schedData: {[key: string]: QuantizedLoad} = {};
       for (let i = 0; i < schedRows.numRecords; i++) {
         const load = schedRows.columns[0].doubleValues![i];
@@ -255,14 +253,12 @@
       globals.publish('OverviewData', schedData);
 
       // Slices overview.
-      const slicesRows = await engine.rawQuery({
-        sqlQuery:
-            `select sum(dur)/${stepSec}/1e9, process.name, process.pid, upid ` +
-            'from slices inner join thread using(utid) ' +
-            'inner join process using(upid) where depth = 0 ' +
-            `and ts >= ${startNs} and ts < ${endNs} ` +
-            'group by upid'
-      });
+      const slicesRows = await engine.query(
+          `select sum(dur)/${stepSec}/1e9, process.name, process.pid, upid ` +
+          'from slices inner join thread using(utid) ' +
+          'inner join process using(upid) where depth = 0 ' +
+          `and ts >= ${startNs} and ts < ${endNs} ` +
+          'group by upid');
       const slicesData: {[key: string]: QuantizedLoad} = {};
       for (let i = 0; i < slicesRows.numRecords; i++) {
         const load = slicesRows.columns[0].doubleValues![i];
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
index b18c8f9..cbf444c 100644
--- a/ui/src/controller/track_controller.ts
+++ b/ui/src/controller/track_controller.ts
@@ -15,6 +15,8 @@
 import {assertExists} from '../base/logging';
 import {clearTrackDataRequest} from '../common/actions';
 import {Registry} from '../common/registry';
+import {TrackState} from '../common/state';
+
 import {Controller} from './controller';
 import {ControllerFactory} from './controller';
 import {Engine} from './engine';
@@ -22,7 +24,8 @@
 
 // TrackController is a base class overridden by track implementations (e.g.,
 // sched slices, nestable slices, counters).
-export abstract class TrackController extends Controller<'main'> {
+export abstract class TrackController<Config = {}, Data = {}> extends
+    Controller<'main'> {
   readonly trackId: string;
   readonly engine: Engine;
 
@@ -37,10 +40,18 @@
   // to publish new track data in response to this call.
   abstract onBoundsChange(start: number, end: number, resolution: number): void;
 
-  get trackState() {
+  get trackState(): TrackState {
     return assertExists(globals.state.tracks[this.trackId]);
   }
 
+  get config(): Config {
+    return this.trackState.config as Config;
+  }
+
+  publish(data: Data): void {
+    globals.publish('TrackData', {id: this.trackId, data});
+  }
+
   run() {
     const dataReq = this.trackState.dataReq;
     if (dataReq === undefined) return;
diff --git a/ui/src/controller/wasm_engine_proxy.ts b/ui/src/controller/wasm_engine_proxy.ts
index aff7503..80375b9 100644
--- a/ui/src/controller/wasm_engine_proxy.ts
+++ b/ui/src/controller/wasm_engine_proxy.ts
@@ -18,27 +18,18 @@
 import {TraceProcessor} from '../common/protos';
 import {WasmBridgeRequest, WasmBridgeResponse} from '../engine/wasm_bridge';
 
-import {Engine, EnginePortAndId} from './engine';
+import {Engine} from './engine';
 
-interface WorkerAndPort {
-  worker: Worker;
-  port: MessagePort;
-}
+const activeWorkers = new Map<string, Worker>();
+let warmWorker: null|Worker = null;
 
-const activeWorkers = new Map<string, WorkerAndPort>();
-let warmWorker: null|WorkerAndPort = null;
-
-function createWorker(): WorkerAndPort {
-  const channel = new MessageChannel();
-  const worker = new Worker('engine_bundle.js');
-  // tslint:disable-next-line deprecation
-  worker.postMessage(channel.port1, [channel.port1]);
-  return {worker, port: channel.port2};
+function createWorker(): Worker {
+  return new Worker('engine_bundle.js');
 }
 
 // Take the warm engine and start creating a new WASM engine in the background
 // for the next call.
-export function createWasmEngine(id: string): MessagePort {
+export function createWasmEngine(id: string): Worker {
   if (warmWorker === null) {
     throw new Error('warmupWasmEngine() not called');
   }
@@ -48,14 +39,14 @@
   const activeWorker = warmWorker;
   warmWorker = createWorker();
   activeWorkers.set(id, activeWorker);
-  return activeWorker.port;
+  return activeWorker;
 }
 
 export function destroyWasmEngine(id: string) {
   if (!activeWorkers.has(id)) {
     throw new Error(`Cannot find worker ID ${id}`);
   }
-  activeWorkers.get(id)!.worker.terminate();
+  activeWorkers.get(id)!.terminate();
   activeWorkers.delete(id);
 }
 
@@ -79,23 +70,19 @@
  * worker thread.
  */
 export class WasmEngineProxy extends Engine {
-  private readonly port: MessagePort;
+  private readonly worker: Worker;
   private readonly traceProcessor_: TraceProcessor;
   private pendingCallbacks: Map<number, protobufjs.RPCImplCallback>;
   private nextRequestId: number;
   readonly id: string;
 
-  static create(args: EnginePortAndId): Engine {
-    return new WasmEngineProxy(args);
-  }
-
-  constructor(args: EnginePortAndId) {
+  constructor(args: {id: string, worker: Worker}) {
     super();
     this.nextRequestId = 0;
     this.pendingCallbacks = new Map();
-    this.port = args.port;
     this.id = args.id;
-    this.port.onmessage = this.onMessage.bind(this);
+    this.worker = args.worker;
+    this.worker.onmessage = this.onMessage.bind(this);
     this.traceProcessor_ =
         TraceProcessor.create(this.rpcImpl.bind(this, 'trace_processor'));
   }
@@ -110,7 +97,7 @@
         {id, serviceName: 'trace_processor', methodName: 'parse', data};
     const promise = defer<void>();
     this.pendingCallbacks.set(id, () => promise.resolve());
-    this.port.postMessage(request);
+    this.worker.postMessage(request);
     return promise;
   }
 
@@ -121,7 +108,7 @@
         {id, serviceName: 'trace_processor', methodName: 'notifyEof', data};
     const promise = defer<void>();
     this.pendingCallbacks.set(id, () => promise.resolve());
-    this.port.postMessage(request);
+    this.worker.postMessage(request);
     return promise;
   }
 
@@ -147,6 +134,6 @@
       methodName,
       data: requestData,
     };
-    this.port.postMessage(request);
+    this.worker.postMessage(request);
   }
 }
diff --git a/ui/src/engine/index.ts b/ui/src/engine/index.ts
index fbf2a3d..7891a38 100644
--- a/ui/src/engine/index.ts
+++ b/ui/src/engine/index.ts
@@ -17,23 +17,13 @@
 import {WasmBridge, WasmBridgeRequest} from './wasm_bridge';
 
 // tslint:disable no-any
-
-// We expect to get exactly one message from the creator of the worker:
-// a MessagePort we should listen to for future messages.
-// This indirection is due to workers not being able create workers in Chrome
-// which is tracked at: crbug.com/31666
-// TODO(hjd): Remove this once the fix has landed.
-// Once we have the MessagePort we proxy all messages to WasmBridge#callWasm.
+// Proxy all messages to WasmBridge#callWasm.
 const anySelf = (self as any);
+const boundPostMessage = anySelf.postMessage.bind(anySelf);
+const bridge = new WasmBridge(init_trace_processor, boundPostMessage);
+bridge.initialize();
+
 anySelf.onmessage = (msg: MessageEvent) => {
-  const port: MessagePort = msg.data;
-
-  const bridge =
-      new WasmBridge(init_trace_processor, port.postMessage.bind(port));
-  bridge.initialize();
-
-  port.onmessage = (msg: MessageEvent) => {
-    const request: WasmBridgeRequest = msg.data;
-    bridge.callWasm(request);
-  };
+  const request: WasmBridgeRequest = msg.data;
+  bridge.callWasm(request);
 };
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index c295c43..b9aa740 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -27,6 +27,7 @@
   timeScale = new TimeScale(this.visibleWindowTime, [0, 0]);
   private _visibleTimeLastUpdate = 0;
   private pendingGlobalTimeUpdate?: TimeSpan;
+  perfDebug = false;
 
   // TODO: there is some redundancy in the fact that both |visibleWindowTime|
   // and a |timeScale| have a notion of time range. That should live in one
@@ -52,4 +53,9 @@
   get visibleTimeLastUpdate() {
     return this._visibleTimeLastUpdate;
   }
+
+  togglePerfDebug() {
+    this.perfDebug = !this.perfDebug;
+    globals.rafScheduler.scheduleFullRedraw();
+  }
 }
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 054df61..e370127 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -45,20 +45,24 @@
 class Globals {
   private _dispatch?: Dispatch = undefined;
   private _state?: State = undefined;
-  private _trackDataStore?: TrackDataStore = undefined;
-  private _queryResults?: QueryResultsStore = undefined;
   private _frontendLocalState?: FrontendLocalState = undefined;
   private _rafScheduler?: RafScheduler = undefined;
+
+  // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
+  private _trackDataStore?: TrackDataStore = undefined;
+  private _queryResults?: QueryResultsStore = undefined;
   private _overviewStore?: OverviewStore = undefined;
   private _threadMap?: ThreadMap = undefined;
 
   initialize(dispatch?: Dispatch) {
     this._dispatch = dispatch;
     this._state = createEmptyState();
-    this._trackDataStore = new Map<string, {}>();
-    this._queryResults = new Map<string, {}>();
     this._frontendLocalState = new FrontendLocalState();
     this._rafScheduler = new RafScheduler();
+
+    // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
+    this._trackDataStore = new Map<string, {}>();
+    this._queryResults = new Map<string, {}>();
     this._overviewStore = new Map<string, QuantizedLoad[]>();
     this._threadMap = new Map<number, ThreadDesc>();
   }
@@ -75,6 +79,15 @@
     return assertExists(this._dispatch);
   }
 
+  get frontendLocalState() {
+    return assertExists(this._frontendLocalState);
+  }
+
+  get rafScheduler() {
+    return assertExists(this._rafScheduler);
+  }
+
+  // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
   get overviewStore(): OverviewStore {
     return assertExists(this._overviewStore);
   }
@@ -87,14 +100,6 @@
     return assertExists(this._queryResults);
   }
 
-  get frontendLocalState() {
-    return assertExists(this._frontendLocalState);
-  }
-
-  get rafScheduler() {
-    return assertExists(this._rafScheduler);
-  }
-
   get threads() {
     return assertExists(this._threadMap);
   }
@@ -102,11 +107,14 @@
   resetForTesting() {
     this._dispatch = undefined;
     this._state = undefined;
-    this._trackDataStore = undefined;
-    this._queryResults = undefined;
     this._frontendLocalState = undefined;
     this._rafScheduler = undefined;
+
+    // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
+    this._trackDataStore = undefined;
+    this._queryResults = undefined;
     this._overviewStore = undefined;
+    this._threadMap = undefined;
   }
 }
 
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index d331899..0b92765 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -20,15 +20,9 @@
 import {loadPermalink} from '../common/actions';
 import {State} from '../common/state';
 import {TimeSpan} from '../common/time';
-import {EnginePortAndId} from '../controller/engine';
-import {
-  createWasmEngine,
-  destroyWasmEngine,
-  warmupWasmEngine,
-} from '../controller/wasm_engine_proxy';
-
 import {globals, QuantizedLoad, ThreadDesc} from './globals';
 import {HomePage} from './home_page';
+import {RecordPage} from './record_page';
 import {Router} from './router';
 import {ViewerPage} from './viewer_page';
 
@@ -86,22 +80,6 @@
     this.redraw();
   }
 
-  /**
-   * Creates a new trace processor wasm engine (backed by a worker running
-   * engine_bundle.js) and returns a MessagePort for talking to it.
-   * This indirection is due to workers not being able create workers in
-   * Chrome which is tracked at: crbug.com/31666
-   * TODO(hjd): Remove this once the fix has landed.
-   */
-  createEngine(): EnginePortAndId {
-    const id = new Date().toUTCString();
-    return {id, port: createWasmEngine(id)};
-  }
-
-  destroyEngine(id: string) {
-    destroyWasmEngine(id);
-  }
-
   private redraw(): void {
     if (globals.state.route &&
         globals.state.route !== this.router.getRouteFromHash()) {
@@ -125,6 +103,7 @@
       {
         '/': HomePage,
         '/viewer': ViewerPage,
+        '/record': RecordPage,
       },
       dispatch);
   forwardRemoteCalls(channel.port2, new FrontendApi(router));
@@ -133,7 +112,6 @@
   globals.rafScheduler.domRedraw = () =>
       m.render(document.body, m(router.resolve(globals.state.route)));
 
-  warmupWasmEngine();
 
   // Put these variables in the global scope for better debugging.
   (window as {} as {m: {}}).m = m;
diff --git a/ui/src/frontend/pages.ts b/ui/src/frontend/pages.ts
index d258656..a964952 100644
--- a/ui/src/frontend/pages.ts
+++ b/ui/src/frontend/pages.ts
@@ -27,10 +27,37 @@
       hash ? ['Permalink: ', m(`a[href=${url}]`, url)] : 'Uploading...');
 }
 
-const Alerts: m.Component = {
+class Alerts implements m.ClassComponent {
   view() {
     return m('.alerts', renderPermalink());
-  },
+  }
+}
+
+const TogglePerfDebugButton = {
+  view() {
+    return m(
+        '.perf-monitor-button',
+        m('button',
+          {
+            onclick: () => globals.frontendLocalState.togglePerfDebug(),
+          },
+          m('i.material-icons',
+            {
+              title: 'Toggle Perf Debug Mode',
+            },
+            'assessment')));
+  }
+};
+
+const PerfStats: m.Component = {
+  view() {
+    const perfDebug = globals.frontendLocalState.perfDebug;
+    const children = [m(TogglePerfDebugButton)];
+    if (perfDebug) {
+      children.unshift(m('.perf-stats-content'));
+    }
+    return m(`.perf-stats[expanded=${perfDebug}]`, children);
+  }
 };
 
 /**
@@ -44,6 +71,7 @@
         m(Topbar),
         m(component),
         m(Alerts),
+        m(PerfStats),
       ];
     },
   };
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index a3866bb..4fb18f5 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -17,7 +17,14 @@
 import {assertExists, assertTrue} from '../base/logging';
 
 import {globals} from './globals';
-import {isPanelVNode} from './panel';
+import {isPanelVNode, Panel, PanelSize} from './panel';
+import {
+  debugNow,
+  perfDebug,
+  perfDisplay,
+  RunningStatistics,
+  runningStatStr
+} from './perf';
 
 /**
  * If the panel container scrolls, the backing canvas height is
@@ -41,6 +48,13 @@
   private totalPanelHeight = 0;
   private canvasHeight = 0;
 
+  private panelPerfStats = new WeakMap<Panel, RunningStatistics>();
+  private perfStats = {
+    totalPanels: 0,
+    panelsOnCanvas: 0,
+    renderStats: new RunningStatistics(10),
+  };
+
   // attrs received in the most recent mithril redraw.
   private attrs?: Attrs;
 
@@ -56,6 +70,7 @@
         vnode.attrs.doesScroll ? SCROLLING_CANVAS_OVERDRAW_FACTOR : 1;
     this.canvasRedrawer = () => this.redrawCanvas();
     globals.rafScheduler.addRedrawCallback(this.canvasRedrawer);
+    perfDisplay.addContainer(this);
   }
 
   oncreate(vnodeDom: m.CVnodeDOM<Attrs>) {
@@ -113,16 +128,21 @@
     if (attrs.doesScroll) {
       dom.parentElement!.removeEventListener('scroll', this.parentOnScroll);
     }
+    perfDisplay.removeContainer(this);
   }
 
   view({attrs}: m.CVnode<Attrs>) {
     // We receive a new vnode object with new attrs on every mithril redraw. We
     // store the latest attrs so redrawCanvas can use it.
     this.attrs = attrs;
+    const renderPanel = (panel: m.Vnode) => perfDebug() ?
+        m('.panel', panel, m('.debug-panel-border')) :
+        m('.panel', panel);
+
     return m(
         '.scroll-limiter',
         m('canvas.main-canvas'),
-        attrs.panels.map(panel => m('.panel', panel)));
+        attrs.panels.map(renderPanel));
   }
 
   onupdate(vnodeDom: m.CVnodeDOM<Attrs>) {
@@ -186,6 +206,7 @@
   }
 
   private redrawCanvas() {
+    const redrawStart = debugNow();
     if (!this.ctx) return;
     this.ctx.clearRect(0, 0, this.parentWidth, this.canvasHeight);
     const canvasYStart = this.scrollTop - this.getCanvasOverdrawHeightPerSide();
@@ -193,6 +214,7 @@
     let panelYStart = 0;
     const panels = assertExists(this.attrs).panels;
     assertTrue(panels.length === this.panelHeights.length);
+    let totalOnCanvas = 0;
     for (let i = 0; i < panels.length; i++) {
       const panel = panels[i];
       const panelHeight = this.panelHeights[i];
@@ -203,6 +225,8 @@
         continue;
       }
 
+      totalOnCanvas++;
+
       if (!isPanelVNode(panel)) {
         throw Error('Vnode passed to panel container is not a panel');
       }
@@ -213,10 +237,53 @@
       const size = {width: this.parentWidth, height: panelHeight};
       clipRect.rect(0, 0, size.width, size.height);
       this.ctx.clip(clipRect);
+      const beforeRender = debugNow();
       panel.state.renderCanvas(this.ctx, size, panel);
+      this.updatePanelStats(
+          i, panel.state, debugNow() - beforeRender, this.ctx, size);
       this.ctx.restore();
       panelYStart += panelHeight;
     }
+    const redrawDur = debugNow() - redrawStart;
+    this.updatePerfStats(redrawDur, panels.length, totalOnCanvas);
+  }
+
+  private updatePanelStats(
+      panelIndex: number, panel: Panel, renderTime: number,
+      ctx: CanvasRenderingContext2D, size: PanelSize) {
+    if (!perfDebug()) return;
+    let renderStats = this.panelPerfStats.get(panel);
+    if (renderStats === undefined) {
+      renderStats = new RunningStatistics();
+      this.panelPerfStats.set(panel, renderStats);
+    }
+    renderStats.addValue(renderTime);
+
+    const statW = 300;
+    ctx.fillStyle = 'hsl(97, 100%, 96%)';
+    ctx.fillRect(size.width - statW, size.height - 20, statW, 20);
+    ctx.fillStyle = 'hsla(122, 77%, 22%)';
+    const statStr = `Panel ${panelIndex + 1} | ` + runningStatStr(renderStats);
+    ctx.fillText(statStr, size.width - statW, size.height - 10);
+  }
+
+  private updatePerfStats(
+      renderTime: number, totalPanels: number, panelsOnCanvas: number) {
+    if (!perfDebug()) return;
+    this.perfStats.renderStats.addValue(renderTime);
+    this.perfStats.totalPanels = totalPanels;
+    this.perfStats.panelsOnCanvas = panelsOnCanvas;
+  }
+
+  renderPerfStats(index: number) {
+    assertTrue(perfDebug());
+    return [m(
+        'section',
+        m('div', `Panel Container ${index + 1}`),
+        m('div',
+          `${this.perfStats.totalPanels} panels, ` +
+              `${this.perfStats.panelsOnCanvas} on canvas.`),
+        m('div', runningStatStr(this.perfStats.renderStats)), )];
   }
 
   private getCanvasOverdrawHeightPerSide() {
diff --git a/ui/src/frontend/perf.ts b/ui/src/frontend/perf.ts
new file mode 100644
index 0000000..6e4093b
--- /dev/null
+++ b/ui/src/frontend/perf.ts
@@ -0,0 +1,123 @@
+
+// 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.
+
+import * as m from 'mithril';
+
+import {globals} from './globals';
+import {PanelContainer} from './panel_container';
+
+/**
+ * Shorthand for if globals perf debug mode is on.
+ */
+export const perfDebug = () => globals.frontendLocalState.perfDebug;
+
+/**
+ * Returns performance.now() if perfDebug is enabled, otherwise 0.
+ * This is needed because calling performance.now is generally expensive
+ * and should not be done for every frame.
+ */
+export const debugNow = () => perfDebug() ? performance.now() : 0;
+
+/**
+ * Returns execution time of |fn| if perf debug mode is on. Returns 0 otherwise.
+ */
+export function measure(fn: () => void): number {
+  const start = debugNow();
+  fn();
+  return debugNow() - start;
+}
+
+/**
+ * Stores statistics about samples, and keeps a fixed size buffer of most recent
+ * samples.
+ */
+export class RunningStatistics {
+  private _count = 0;
+  private _mean = 0;
+  private _lastValue = 0;
+
+  private buffer: number[] = [];
+
+  constructor(private _maxBufferSize = 10) {}
+
+  addValue(value: number) {
+    this._lastValue = value;
+    this.buffer.push(value);
+    if (this.buffer.length > this._maxBufferSize) {
+      this.buffer.shift();
+    }
+    this._mean = (this._mean * this._count + value) / (this._count + 1);
+    this._count++;
+  }
+
+  get mean() {
+    return this._mean;
+  }
+  get count() {
+    return this._count;
+  }
+  get bufferMean() {
+    return this.buffer.reduce((sum, v) => sum + v, 0) / this.buffer.length;
+  }
+  get bufferSize() {
+    return this.buffer.length;
+  }
+  get maxBufferSize() {
+    return this._maxBufferSize;
+  }
+  get last() {
+    return this._lastValue;
+  }
+}
+
+/**
+ * Returns a summary string representation of a RunningStatistics object.
+ */
+export function runningStatStr(stat: RunningStatistics) {
+  return `Last: ${stat.last.toFixed(2)}ms | ` +
+      `Avg: ${stat.mean.toFixed(2)}ms | ` +
+      `Avg${stat.maxBufferSize}: ${stat.bufferMean.toFixed(2)}ms`;
+}
+
+/**
+ * Globals singleton class that renders performance stats for the whole app.
+ */
+class PerfDisplay {
+  private containers: PanelContainer[] = [];
+  addContainer(container: PanelContainer) {
+    this.containers.push(container);
+  }
+
+  removeContainer(container: PanelContainer) {
+    const i = this.containers.indexOf(container);
+    this.containers.splice(i, 1);
+  }
+
+  renderPerfStats() {
+    if (!perfDebug()) return;
+    const perfDisplayEl = this.getPerfDisplayEl();
+    if (!perfDisplayEl) return;
+    m.render(perfDisplayEl, [
+      m('section', globals.rafScheduler.renderPerfStats()),
+      this.containers.map((c, i) => m('section', c.renderPerfStats(i)))
+    ]);
+  }
+
+  getPerfDisplayEl() {
+    return document.querySelector('.perf-stats-content');
+  }
+}
+
+export const perfDisplay = new PerfDisplay();
diff --git a/ui/src/frontend/raf_scheduler.ts b/ui/src/frontend/raf_scheduler.ts
index 577ccca..bfe2017 100644
--- a/ui/src/frontend/raf_scheduler.ts
+++ b/ui/src/frontend/raf_scheduler.ts
@@ -12,6 +12,37 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import * as m from 'mithril';
+
+import {assertTrue} from '../base/logging';
+
+import {
+  debugNow,
+  measure,
+  perfDebug,
+  perfDisplay,
+  RunningStatistics,
+  runningStatStr
+} from './perf';
+
+function statTableHeader() {
+  return m(
+      'tr',
+      m('th', ''),
+      m('th', 'Last (ms)'),
+      m('th', 'Avg (ms)'),
+      m('th', 'Avg-10 (ms)'), );
+}
+
+function statTableRow(title: string, stat: RunningStatistics) {
+  return m(
+      'tr',
+      m('td', title),
+      m('td', stat.last.toFixed(2)),
+      m('td', stat.mean.toFixed(2)),
+      m('td', stat.bufferMean.toFixed(2)), );
+}
+
 export type ActionCallback = (nowMs: number) => void;
 export type RedrawCallback = (nowMs: number) => void;
 
@@ -31,6 +62,14 @@
   private requestedFullRedraw = false;
   private isRedrawing = false;
 
+  private perfStats = {
+    rafActions: new RunningStatistics(),
+    rafCanvas: new RunningStatistics(),
+    rafDom: new RunningStatistics(),
+    rafTotal: new RunningStatistics(),
+    domRedraw: new RunningStatistics(),
+  };
+
   start(cb: ActionCallback) {
     this.actionCallbacks.add(cb);
     this.maybeScheduleAnimationFrame();
@@ -61,11 +100,23 @@
     this.maybeScheduleAnimationFrame(true);
   }
 
+  syncDomRedraw(nowMs: number) {
+    const redrawStart = debugNow();
+    this._syncDomRedraw(nowMs);
+    if (perfDebug()) {
+      this.perfStats.domRedraw.addValue(debugNow() - redrawStart);
+    }
+  }
+
   private syncCanvasRedraw(nowMs: number) {
+    const redrawStart = debugNow();
     if (this.isRedrawing) return;
     this.isRedrawing = true;
     for (const redraw of this.canvasRedrawCallbacks) redraw(nowMs);
     this.isRedrawing = false;
+    if (perfDebug()) {
+      this.perfStats.rafCanvas.addValue(debugNow() - redrawStart);
+    }
   }
 
   private maybeScheduleAnimationFrame(force = false) {
@@ -77,15 +128,64 @@
   }
 
   private onAnimationFrame(nowMs: number) {
+    const rafStart = debugNow();
     this.hasScheduledNextFrame = false;
 
     const doFullRedraw = this.requestedFullRedraw;
     this.requestedFullRedraw = false;
 
-    for (const action of this.actionCallbacks) action(nowMs);
-    if (doFullRedraw) this._syncDomRedraw(nowMs);
-    this.syncCanvasRedraw(nowMs);
+    const actionTime = measure(() => {
+      for (const action of this.actionCallbacks) action(nowMs);
+    });
+
+    const domTime = measure(() => {
+      if (doFullRedraw) this.syncDomRedraw(nowMs);
+    });
+    const canvasTime = measure(() => this.syncCanvasRedraw(nowMs));
+
+    const totalRafTime = debugNow() - rafStart;
+    this.updatePerfStats(actionTime, domTime, canvasTime, totalRafTime);
+    perfDisplay.renderPerfStats();
 
     this.maybeScheduleAnimationFrame();
   }
+
+  private updatePerfStats(
+      actionsTime: number, domTime: number, canvasTime: number,
+      totalRafTime: number) {
+    if (!perfDebug()) return;
+    this.perfStats.rafActions.addValue(actionsTime);
+    this.perfStats.rafDom.addValue(domTime);
+    this.perfStats.rafCanvas.addValue(canvasTime);
+    this.perfStats.rafTotal.addValue(totalRafTime);
+  }
+
+  renderPerfStats() {
+    assertTrue(perfDebug());
+    return m(
+        'div',
+        m('div',
+          [
+            m('button',
+              {onclick: () => this.scheduleRedraw()},
+              'Do Canvas Redraw'),
+            '   |   ',
+            m('button',
+              {onclick: () => this.scheduleFullRedraw()},
+              'Do Full Redraw'),
+          ]),
+        m('div',
+          'Raf Timing ' +
+              '(Total may not add up due to imprecision)'),
+        m('table',
+          statTableHeader(),
+          statTableRow('Actions', this.perfStats.rafActions),
+          statTableRow('Dom', this.perfStats.rafDom),
+          statTableRow('Canvas', this.perfStats.rafCanvas),
+          statTableRow('Total', this.perfStats.rafTotal), ),
+        m('div',
+          'Dom redraw: ' +
+              `Count: ${this.perfStats.domRedraw.count} | ` +
+              runningStatStr(this.perfStats.domRedraw)), );
+  }
 }
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
new file mode 100644
index 0000000..79d8417
--- /dev/null
+++ b/ui/src/frontend/record_page.ts
@@ -0,0 +1,59 @@
+// 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.
+
+import * as m from 'mithril';
+
+import {createPage} from './pages';
+
+const RECORD_COMMAND_LINE =
+    'echo CgYIgKAGIAESIwohCgxsaW51eC5mdHJhY2UQAKIGDhIFc2NoZWQSBWlucHV0GJBOMh0KFnBlcmZldHRvLnRyYWNlZF9wcm9iZXMQgCAYBEAASAA= | base64 --decode | adb shell "perfetto -c - -o /data/misc/perfetto-traces/trace" && adb pull /data/misc/perfetto-traces/trace /tmp/trace';
+
+async function copyToClipboard(text: string): Promise<void> {
+  try {
+    // TODO(hjd): Fix typescript type for navigator.
+    // tslint:disable-next-line no-any
+    await(navigator as any).clipboard.writeText(text);
+  } catch (err) {
+    console.error(`Failed to copy "${text}" to clipboard: ${err}`);
+  }
+}
+
+interface CodeSampleAttrs {
+  text: string;
+}
+
+class CodeSample implements m.ClassComponent<CodeSampleAttrs> {
+  view({attrs}: m.CVnode<CodeSampleAttrs>) {
+    return m(
+        '.example-code',
+        m('code', attrs.text),
+        m('button',
+          {
+            onclick: () => copyToClipboard(attrs.text),
+          },
+          'Copy to clipboard'), );
+  }
+}
+
+export const RecordPage = createPage({
+  view() {
+    return m(
+        '.text-column',
+        'To collect a 10 second Perfetto trace from an Android phone run this',
+        ' command:',
+        m(CodeSample, {text: RECORD_COMMAND_LINE}),
+        'Then click "Open trace file" in the menu to the left and select',
+        ' "/tmp/trace".');
+  }
+});
diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts
index 425bb33..98bf553 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/frontend/router.ts
@@ -55,7 +55,8 @@
     history.pushState(undefined, undefined, ROUTE_PREFIX + route);
 
     if (!(route in this.routes)) {
-      // Redirect to default route.
+      console.info(
+          `Route ${route} not known redirecting to ${this.defaultRoute}.`);
       this.dispatch(navigate(this.defaultRoute));
     }
   }
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index da204ab..6f8e5d2 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -64,9 +64,12 @@
   };
 }
 
-const EXAMPLE_TRACE_URL =
+const EXAMPLE_ANDROID_TRACE_URL =
     'https://storage.googleapis.com/perfetto-misc/example_trace_30s';
 
+const EXAMPLE_CHROME_TRACE_URL =
+    'https://storage.googleapis.com/perfetto-misc/example_chrome_trace_10s.json';
+
 const SECTIONS = [
   {
     title: 'Traces',
@@ -74,8 +77,17 @@
     expanded: true,
     items: [
       {t: 'Open trace file', a: popupFileSelectionDialog, i: 'folder_open'},
-      {t: 'Open example trace', a: handleOpenTraceUrl, i: 'description'},
-      {t: 'Record new trace', a: navigateHome, i: 'fiber_smart_record'},
+      {
+        t: 'Open Android example',
+        a: openTraceUrl(EXAMPLE_ANDROID_TRACE_URL),
+        i: 'description'
+      },
+      {
+        t: 'Open Chrome example',
+        a: openTraceUrl(EXAMPLE_CHROME_TRACE_URL),
+        i: 'description'
+      },
+      {t: 'Record new trace', a: navigateRecord, i: 'fiber_smart_record'},
       {t: 'Share current trace', a: dispatchCreatePermalink, i: 'share'},
     ],
   },
@@ -133,9 +145,11 @@
   (document.querySelector('input[type=file]')! as HTMLInputElement).click();
 }
 
-function handleOpenTraceUrl(e: Event) {
-  e.preventDefault();
-  globals.dispatch(openTraceFromUrl(EXAMPLE_TRACE_URL));
+function openTraceUrl(url: string): (e: Event) => void {
+  return e => {
+    e.preventDefault();
+    globals.dispatch(openTraceFromUrl(url));
+  };
 }
 
 function onInputElementFileSelectionChanged(e: Event) {
@@ -146,16 +160,22 @@
   globals.dispatch(openTraceFromFile(e.target.files[0]));
 }
 
-function navigateHome(_: Event) {
+function navigateHome(e: Event) {
+  e.preventDefault();
   globals.dispatch(navigate('/'));
 }
 
+function navigateRecord(e: Event) {
+  e.preventDefault();
+  globals.dispatch(navigate('/record'));
+}
+
 function dispatchCreatePermalink(e: Event) {
   e.preventDefault();
   globals.dispatch(createPermalink());
 }
 
-export const Sidebar: m.Component = {
+export class Sidebar implements m.ClassComponent {
   view() {
     const vdomSections = [];
     for (const section of SECTIONS) {
@@ -186,5 +206,5 @@
         m('header', 'Perfetto'),
         m('input[type=file]', {onchange: onInputElementFileSelectionChanged}),
         ...vdomSections);
-  },
-};
+  }
+}
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
index a04d2c6..03b142a 100644
--- a/ui/src/frontend/topbar.ts
+++ b/ui/src/frontend/topbar.ts
@@ -89,13 +89,14 @@
 }
 
 
-const Omnibox: m.Component = {
-  oncreate(vnode) {
+class Omnibox implements m.ClassComponent {
+  oncreate(vnode: m.VnodeDOM) {
     const txt = vnode.dom.querySelector('input') as HTMLInputElement;
     txt.addEventListener('blur', clearOmniboxResults);
     txt.addEventListener('keydown', onKeyDown);
     txt.addEventListener('keyup', onKeyUp);
-  },
+  }
+
   view() {
     const msgTTL = globals.state.status.timestamp + 3 - Date.now() / 1e3;
     let enginesAreBusy = false;
@@ -131,10 +132,10 @@
         `.omnibox${commandMode ? '.command-mode' : ''}`,
         m(`input[placeholder=${placeholder[mode]}]`),
         m('.omnibox-results', results));
-  },
-};
+  }
+}
 
-export const Topbar: m.Component = {
+export class Topbar implements m.ClassComponent {
   view() {
     const progBar = [];
     const engine: EngineConfig = globals.state.engines['0'];
@@ -142,7 +143,6 @@
         (engine !== undefined && !engine.ready)) {
       progBar.push(m('.progress'));
     }
-
     return m('.topbar', m(Omnibox), ...progBar);
-  },
-};
+  }
+}
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 724041f..c543ad8 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import {TrackState} from '../common/state';
+import {globals} from './globals';
 
 /**
  * This interface forces track implementations to have some static properties.
@@ -32,15 +33,26 @@
 /**
  * The abstract class that needs to be implemented by all tracks.
  */
-export abstract class Track {
+export abstract class Track<Config = {}, Data = {}> {
   /**
    * Receive data published by the TrackController of this track.
    */
   constructor(protected trackState: TrackState) {}
   abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
+
+  get config(): Config {
+    return this.trackState.config as Config;
+  }
+
+  data(): Data {
+    return globals.trackDataStore.get(this.trackState.id) as Data;
+  }
+
   getHeight(): number {
     return 40;
   }
+
   onMouseMove(_position: {x: number, y: number}) {}
+
   onMouseOut() {}
 }
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index ccf6041..a12024d 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -32,8 +32,11 @@
   return globals.state.pinnedTracks.indexOf(id) !== -1;
 }
 
-const TrackShell = {
-  view({attrs}) {
+interface TrackShellAttrs {
+  trackState: TrackState;
+}
+class TrackShell implements m.ClassComponent<TrackShellAttrs> {
+  view({attrs}: m.CVnode<TrackShellAttrs>) {
     return m(
         '.track-shell',
         m('h1', attrs.trackState.name),
@@ -49,11 +52,14 @@
           action: toggleTrackPinned(attrs.trackState.id),
           i: isPinned(attrs.trackState.id) ? 'star' : 'star_border',
         }));
-  },
-} as m.Component<{trackState: TrackState}>;
+  }
+}
 
-const TrackContent = {
-  view({attrs}) {
+interface TrackContentAttrs {
+  track: Track;
+}
+class TrackContent implements m.ClassComponent<TrackContentAttrs> {
+  view({attrs}: m.CVnode<TrackContentAttrs>) {
     return m('.track-content', {
       onmousemove: (e: MouseEvent) => {
         attrs.track.onMouseMove({x: e.layerX, y: e.layerY});
@@ -65,19 +71,27 @@
       },
     }, );
   }
-} as m.Component<{track: Track}>;
+}
 
-const TrackComponent = {
-  view({attrs}) {
+interface TrackComponentAttrs {
+  trackState: TrackState;
+  track: Track;
+}
+class TrackComponent implements m.ClassComponent<TrackComponentAttrs> {
+  view({attrs}: m.CVnode<TrackComponentAttrs>) {
     return m('.track', [
       m(TrackShell, {trackState: attrs.trackState}),
       m(TrackContent, {track: attrs.track})
     ]);
   }
-} as m.Component<{trackState: TrackState, track: Track}>;
+}
 
-const TrackButton = {
-  view({attrs}) {
+interface TrackButtonAttrs {
+  action: Action;
+  i: string;
+}
+class TrackButton implements m.ClassComponent<TrackButtonAttrs> {
+  view({attrs}: m.CVnode<TrackButtonAttrs>) {
     return m(
         'i.material-icons.track-button',
         {
@@ -85,11 +99,7 @@
         },
         attrs.i);
   }
-} as m.Component<{
-  action: Action,
-  i: string,
-},
-                    {}>;
+}
 
 interface TrackPanelAttrs {
   id: string;
@@ -121,6 +131,7 @@
   }
 
   renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
+    ctx.save();
     ctx.translate(TRACK_SHELL_WIDTH, 0);
     drawGridLines(
         ctx,
@@ -129,5 +140,6 @@
         size.height);
 
     this.track.renderCanvas(ctx);
+    ctx.restore();
   }
 }
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 94ee184..e801992 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -30,7 +30,7 @@
 
 const MAX_ZOOM_SPAN_SEC = 1e-4;  // 0.1 ms.
 
-const QueryTable: m.Component<{}, {}> = {
+class QueryTable implements m.ClassComponent {
   view() {
     const resp = globals.queryResults.get('command') as QueryResponse;
     if (resp === undefined) {
@@ -58,25 +58,23 @@
         resp.error ?
             m('.query-error', `SQL error: ${resp.error}`) :
             m('table.query-table', m('thead', header), m('tbody', rows)));
-  },
-};
+  }
+}
 
 /**
  * Top-most level component for the viewer page. Holds tracks, brush timeline,
  * panels, and everything else that's part of the main trace viewer page.
  */
-const TraceViewer = {
-  oninit() {
-    this.width = 0;
-  },
+class TraceViewer implements m.ClassComponent {
+  private onResize: () => void = () => {};
+  private zoomContent?: PanAndZoomHandler;
 
-  oncreate(vnode) {
+  oncreate(vnode: m.CVnodeDOM) {
     const frontendLocalState = globals.frontendLocalState;
     const updateDimensions = () => {
       const rect = vnode.dom.getBoundingClientRect();
-      this.width = rect.width;
       frontendLocalState.timeScale.setLimitsPx(
-          0, this.width - TRACK_SHELL_WIDTH);
+          0, rect.width - TRACK_SHELL_WIDTH);
     };
 
     updateDimensions();
@@ -124,12 +122,12 @@
             new TimeSpan(newStartSec, newEndSec));
       }
     });
-  },
+  }
 
   onremove() {
     window.removeEventListener('resize', this.onResize);
-    this.zoomContent.shutdown();
-  },
+    if (this.zoomContent) this.zoomContent.shutdown();
+  }
 
   view() {
     const scrollingPanels = globals.state.scrollingTracks.length > 0 ?
@@ -158,15 +156,8 @@
               doesScroll: true,
               panels: scrollingPanels,
             }))));
-  },
-
-} as m.Component<{}, {
-  onResize: () => void,
-  width: number,
-  zoomContent: PanAndZoomHandler,
-  overviewQueryExecuted: boolean,
-  overviewQueryResponse: QueryResponse,
-}>;
+  }
+}
 
 export const ViewerPage = createPage({
   view() {
diff --git a/ui/src/tracks/chrome_slices/common.ts b/ui/src/tracks/chrome_slices/common.ts
index ca09741..1256dc7 100644
--- a/ui/src/tracks/chrome_slices/common.ts
+++ b/ui/src/tracks/chrome_slices/common.ts
@@ -14,7 +14,13 @@
 
 export const SLICE_TRACK_KIND = 'ChromeSliceTrack';
 
-export interface ChromeSliceTrackData {
+export interface Config {
+  maxDepth: number;
+  upid: number;
+  utid: number;
+}
+
+export interface Data {
   start: number;
   end: number;
   resolution: number;
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index 15d33c9..186491a 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -13,15 +13,14 @@
 // limitations under the License.
 
 import {fromNs} from '../../common/time';
-import {globals} from '../../controller/globals';
 import {
   TrackController,
   trackControllerRegistry
 } from '../../controller/track_controller';
 
-import {ChromeSliceTrackData, SLICE_TRACK_KIND} from './common';
+import {Config, Data, SLICE_TRACK_KIND} from './common';
 
-class ChromeSliceTrackController extends TrackController {
+class ChromeSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = SLICE_TRACK_KIND;
   private busy = false;
 
@@ -34,7 +33,7 @@
     // any index. We need to introduce ts_lower_bound also for the slices table
     // (see sched table).
     const query = `select ts,dur,depth,cat,name from slices ` +
-        `where utid = ${this.trackState.utid} ` +
+        `where utid = ${this.config.utid} ` +
         `and ts >= ${Math.round(start * 1e9)} - dur ` +
         `and ts <= ${Math.round(end * 1e9)} ` +
         `and dur >= ${Math.round(resolution * 1e9)} ` +
@@ -42,8 +41,7 @@
         `limit ${LIMIT};`;
 
     this.busy = true;
-    console.log(query);
-    this.engine.rawQuery({'sqlQuery': query}).then(rawResult => {
+    this.engine.query(query).then(rawResult => {
       this.busy = false;
       if (rawResult.error) {
         throw new Error(`Query error "${query}": ${rawResult.error}`);
@@ -51,7 +49,7 @@
 
       const numRows = +rawResult.numRecords;
 
-      const slices: ChromeSliceTrackData = {
+      const slices: Data = {
         start,
         end,
         resolution,
@@ -85,9 +83,10 @@
       if (numRows === LIMIT) {
         slices.end = slices.ends[slices.ends.length - 1];
       }
-      globals.publish('TrackData', {id: this.trackId, data: slices});
+      this.publish(slices);
     });
   }
 }
 
+
 trackControllerRegistry.register(ChromeSliceTrackController);
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index fd22613..aba1e7f 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -18,10 +18,7 @@
 import {Track} from '../../frontend/track';
 import {trackRegistry} from '../../frontend/track_registry';
 
-import {
-  ChromeSliceTrackData,
-  SLICE_TRACK_KIND,
-} from './common';
+import {Config, Data, SLICE_TRACK_KIND} from './common';
 
 const SLICE_HEIGHT = 30;
 const TRACK_PADDING = 5;
@@ -41,7 +38,7 @@
   return Math.pow(10, Math.floor(Math.log10(resolution)));
 }
 
-class ChromeSliceTrack extends Track {
+class ChromeSliceTrack extends Track<Config, Data> {
   static readonly kind = SLICE_TRACK_KIND;
   static create(trackState: TrackState): ChromeSliceTrack {
     return new ChromeSliceTrack(trackState);
@@ -54,7 +51,6 @@
     super(trackState);
   }
 
-
   reqDataDeferred() {
     const {visibleWindowTime} = globals.frontendLocalState;
     const reqStart = visibleWindowTime.start - visibleWindowTime.duration;
@@ -69,27 +65,27 @@
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
 
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
-    const trackData = this.trackData;
+    const data = this.data();
 
-    // If there aren't enough cached slices data in |trackData| request more to
+    // If there aren't enough cached slices data in |data| request more to
     // the controller.
-    const inRange = trackData !== undefined &&
-        (visibleWindowTime.start >= trackData.start &&
-         visibleWindowTime.end <= trackData.end);
-    if (!inRange || trackData.resolution > getCurResolution()) {
+    const inRange = data !== undefined &&
+        (visibleWindowTime.start >= data.start &&
+         visibleWindowTime.end <= data.end);
+    if (!inRange || data.resolution > getCurResolution()) {
       if (!this.reqPending) {
         this.reqPending = true;
         setTimeout(() => this.reqDataDeferred(), 50);
       }
-      if (trackData === undefined) return;  // Can't possibly draw anything.
+      if (data === undefined) return;  // Can't possibly draw anything.
     }
 
     // If the cached trace slices don't fully cover the visible time range,
     // show a gray rectangle with a "Loading..." label.
     ctx.font = '12px Google Sans';
-    if (trackData.start > visibleWindowTime.start) {
+    if (data.start > visibleWindowTime.start) {
       const rectWidth =
-          timeScale.timeToPx(Math.min(trackData.start, visibleWindowTime.end));
+          timeScale.timeToPx(Math.min(data.start, visibleWindowTime.end));
       ctx.fillStyle = '#eee';
       ctx.fillRect(0, TRACK_PADDING, rectWidth, SLICE_HEIGHT);
       ctx.fillStyle = '#666';
@@ -99,9 +95,9 @@
           TRACK_PADDING + SLICE_HEIGHT / 2,
           rectWidth);
     }
-    if (trackData.end < visibleWindowTime.end) {
+    if (data.end < visibleWindowTime.end) {
       const rectX =
-          timeScale.timeToPx(Math.max(trackData.end, visibleWindowTime.start));
+          timeScale.timeToPx(Math.max(data.end, visibleWindowTime.start));
       const rectWidth = timeScale.timeToPx(visibleWindowTime.end) - rectX;
       ctx.fillStyle = '#eee';
       ctx.fillRect(rectX, TRACK_PADDING, rectWidth, SLICE_HEIGHT);
@@ -120,13 +116,13 @@
     const charWidth = ctx.measureText('abcdefghij').width / 10;
     const pxEnd = timeScale.timeToPx(visibleWindowTime.end);
 
-    for (let i = 0; i < trackData.starts.length; i++) {
-      const tStart = trackData.starts[i];
-      const tEnd = trackData.ends[i];
-      const depth = trackData.depths[i];
-      const cat = trackData.strings[trackData.categories[i]];
-      const titleId = trackData.titles[i];
-      const title = trackData.strings[titleId];
+    for (let i = 0; i < data.starts.length; i++) {
+      const tStart = data.starts[i];
+      const tEnd = data.ends[i];
+      const depth = data.depths[i];
+      const cat = data.strings[data.categories[i]];
+      const titleId = data.titles[i];
+      const title = data.strings[titleId];
       if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
         continue;
       }
@@ -162,18 +158,18 @@
   }
 
   onMouseMove({x, y}: {x: number, y: number}) {
-    const trackData = this.trackData;
+    const data = this.data();
     this.hoveredTitleId = -1;
-    if (trackData === undefined) return;
+    if (data === undefined) return;
     const {timeScale} = globals.frontendLocalState;
     if (y < TRACK_PADDING) return;
     const t = timeScale.pxToTime(x);
     const depth = Math.floor(y / SLICE_HEIGHT);
-    for (let i = 0; i < trackData.starts.length; i++) {
-      const tStart = trackData.starts[i];
-      const tEnd = trackData.ends[i];
-      const titleId = trackData.titles[i];
-      if (tStart <= t && t <= tEnd && depth === trackData.depths[i]) {
+    for (let i = 0; i < data.starts.length; i++) {
+      const tStart = data.starts[i];
+      const tEnd = data.ends[i];
+      const titleId = data.titles[i];
+      if (tStart <= t && t <= tEnd && depth === data.depths[i]) {
         this.hoveredTitleId = titleId;
         break;
       }
@@ -185,12 +181,7 @@
   }
 
   getHeight() {
-    return SLICE_HEIGHT * (this.trackState.maxDepth + 1) + 2 * TRACK_PADDING;
-  }
-
-  private get trackData(): ChromeSliceTrackData {
-    return globals.trackDataStore.get(this.trackState.id) as
-        ChromeSliceTrackData;
+    return SLICE_HEIGHT * (this.config.maxDepth + 1) + 2 * TRACK_PADDING;
   }
 }
 
diff --git a/ui/src/tracks/cpu_slices/common.ts b/ui/src/tracks/cpu_slices/common.ts
index e7d2003..38844f1 100644
--- a/ui/src/tracks/cpu_slices/common.ts
+++ b/ui/src/tracks/cpu_slices/common.ts
@@ -14,7 +14,7 @@
 
 export const CPU_SLICE_TRACK_KIND = 'CpuSliceTrack';
 
-export interface CpuSliceTrackData {
+export interface Data {
   start: number;
   end: number;
   resolution: number;
@@ -24,3 +24,5 @@
   ends: Float64Array;
   utids: Uint32Array;
 }
+
+export interface Config { cpu: number; }
diff --git a/ui/src/tracks/cpu_slices/controller.ts b/ui/src/tracks/cpu_slices/controller.ts
index f2243a1..17a1b0f 100644
--- a/ui/src/tracks/cpu_slices/controller.ts
+++ b/ui/src/tracks/cpu_slices/controller.ts
@@ -13,15 +13,14 @@
 // limitations under the License.
 
 import {fromNs} from '../../common/time';
-import {globals} from '../../controller/globals';
 import {
   TrackController,
   trackControllerRegistry
 } from '../../controller/track_controller';
 
-import {CPU_SLICE_TRACK_KIND, CpuSliceTrackData} from './common';
+import {Config, CPU_SLICE_TRACK_KIND, Data} from './common';
 
-class CpuSliceTrackController extends TrackController {
+class CpuSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = CPU_SLICE_TRACK_KIND;
   private busy = false;
 
@@ -30,7 +29,7 @@
     if (this.busy) return;
     const LIMIT = 10000;
     const query = 'select ts,dur,utid from sched ' +
-        `where cpu = ${this.trackState.cpu} ` +
+        `where cpu = ${this.config.cpu} ` +
         `and ts_lower_bound = ${Math.round(start * 1e9)} ` +
         `and ts <= ${Math.round(end * 1e9)} ` +
         `and dur >= ${Math.round(resolution * 1e9)} ` +
@@ -38,19 +37,15 @@
         `order by ts ` +
         `limit ${LIMIT};`;
 
-    if (this.trackState.cpu === 0) console.log('QUERY', query);
-
     this.busy = true;
-    this.engine.rawQuery({'sqlQuery': query}).then(rawResult => {
+    this.engine.query(query).then(rawResult => {
       this.busy = false;
       if (rawResult.error) {
         throw new Error(`Query error "${query}": ${rawResult.error}`);
       }
-      if (this.trackState.cpu === 0) console.log('QUERY DONE', query);
-
       const numRows = +rawResult.numRecords;
 
-      const slices: CpuSliceTrackData = {
+      const slices: Data = {
         start,
         end,
         resolution,
@@ -69,7 +64,7 @@
       if (numRows === LIMIT) {
         slices.end = slices.ends[slices.ends.length - 1];
       }
-      globals.publish('TrackData', {id: this.trackId, data: slices});
+      this.publish(slices);
     });
   }
 }
diff --git a/ui/src/tracks/cpu_slices/frontend.ts b/ui/src/tracks/cpu_slices/frontend.ts
index 0e0bddb..34b84d5 100644
--- a/ui/src/tracks/cpu_slices/frontend.ts
+++ b/ui/src/tracks/cpu_slices/frontend.ts
@@ -19,7 +19,7 @@
 import {Track} from '../../frontend/track';
 import {trackRegistry} from '../../frontend/track_registry';
 
-import {CPU_SLICE_TRACK_KIND, CpuSliceTrackData} from './common';
+import {Config, CPU_SLICE_TRACK_KIND, Data} from './common';
 
 const MARGIN_TOP = 5;
 const RECT_HEIGHT = 30;
@@ -46,7 +46,7 @@
   return Math.pow(10, Math.floor(Math.log10(resolution)));
 }
 
-class CpuSliceTrack extends Track {
+class CpuSliceTrack extends Track<Config, Data> {
   static readonly kind = CPU_SLICE_TRACK_KIND;
   static create(trackState: TrackState): CpuSliceTrack {
     return new CpuSliceTrack(trackState);
@@ -74,19 +74,19 @@
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
 
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
-    const trackData = this.trackData;
+    const data = this.data();
 
-    // If there aren't enough cached slices data in |trackData| request more to
+    // If there aren't enough cached slices data in |data| request more to
     // the controller.
-    const inRange = trackData !== undefined &&
-        (visibleWindowTime.start >= trackData.start &&
-         visibleWindowTime.end <= trackData.end);
-    if (!inRange || trackData.resolution > getCurResolution()) {
+    const inRange = data !== undefined &&
+        (visibleWindowTime.start >= data.start &&
+         visibleWindowTime.end <= data.end);
+    if (!inRange || data.resolution > getCurResolution()) {
       if (!this.reqPending) {
         this.reqPending = true;
         setTimeout(() => this.reqDataDeferred(), 50);
       }
-      if (trackData === undefined) return;  // Can't possibly draw anything.
+      if (data === undefined) return;  // Can't possibly draw anything.
     }
     ctx.textAlign = 'center';
     ctx.font = '12px Google Sans';
@@ -94,23 +94,23 @@
 
     // TODO: this needs to be kept in sync with the hue generation algorithm
     // of overview_timeline_panel.ts
-    const hue = (128 + (32 * this.trackState.cpu)) % 256;
+    const hue = (128 + (32 * this.config.cpu)) % 256;
 
     // If the cached trace slices don't fully cover the visible time range,
     // show a gray rectangle with a "Loading..." label.
     ctx.font = '12px Google Sans';
-    if (trackData.start > visibleWindowTime.start) {
+    if (data.start > visibleWindowTime.start) {
       const rectWidth =
-          timeScale.timeToPx(Math.min(trackData.start, visibleWindowTime.end));
+          timeScale.timeToPx(Math.min(data.start, visibleWindowTime.end));
       ctx.fillStyle = '#eee';
       ctx.fillRect(0, MARGIN_TOP, rectWidth, RECT_HEIGHT);
       ctx.fillStyle = '#666';
       ctx.fillText(
           'loading...', rectWidth / 2, MARGIN_TOP + RECT_HEIGHT / 2, rectWidth);
     }
-    if (trackData.end < visibleWindowTime.end) {
+    if (data.end < visibleWindowTime.end) {
       const rectX =
-          timeScale.timeToPx(Math.max(trackData.end, visibleWindowTime.start));
+          timeScale.timeToPx(Math.max(data.end, visibleWindowTime.start));
       const rectWidth = timeScale.timeToPx(visibleWindowTime.end) - rectX;
       ctx.fillStyle = '#eee';
       ctx.fillRect(rectX, MARGIN_TOP, rectWidth, RECT_HEIGHT);
@@ -122,12 +122,12 @@
           rectWidth);
     }
 
-    assertTrue(trackData.starts.length === trackData.ends.length);
-    assertTrue(trackData.starts.length === trackData.utids.length);
-    for (let i = 0; i < trackData.starts.length; i++) {
-      const tStart = trackData.starts[i];
-      const tEnd = trackData.ends[i];
-      const utid = trackData.utids[i];
+    assertTrue(data.starts.length === data.ends.length);
+    assertTrue(data.starts.length === data.utids.length);
+    for (let i = 0; i < data.starts.length; i++) {
+      const tStart = data.starts[i];
+      const tEnd = data.ends[i];
+      const utid = data.utids[i];
       if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
         continue;
       }
@@ -185,9 +185,9 @@
   }
 
   onMouseMove({x, y}: {x: number, y: number}) {
-    const trackData = this.trackData;
+    const data = this.data();
     this.mouseXpos = x;
-    if (trackData === undefined) return;
+    if (data === undefined) return;
     const {timeScale} = globals.frontendLocalState;
     if (y < MARGIN_TOP || y > MARGIN_TOP + RECT_HEIGHT) {
       this.hoveredUtid = -1;
@@ -196,10 +196,10 @@
     const t = timeScale.pxToTime(x);
     this.hoveredUtid = -1;
 
-    for (let i = 0; i < trackData.starts.length; i++) {
-      const tStart = trackData.starts[i];
-      const tEnd = trackData.ends[i];
-      const utid = trackData.utids[i];
+    for (let i = 0; i < data.starts.length; i++) {
+      const tStart = data.starts[i];
+      const tEnd = data.ends[i];
+      const utid = data.utids[i];
       if (tStart <= t && t <= tEnd) {
         this.hoveredUtid = utid;
         break;
@@ -211,10 +211,6 @@
     this.hoveredUtid = -1;
     this.mouseXpos = 0;
   }
-
-  private get trackData(): CpuSliceTrackData {
-    return globals.trackDataStore.get(this.trackState.id) as CpuSliceTrackData;
-  }
 }
 
 trackRegistry.register(CpuSliceTrack);