trace_processor: add template substitution support for SQL metrics

Also implement the basic process counter metrics to prove the concept.

Context: go/perfetto-metrics
Bug: 129747127
Change-Id: Ia9304328f6368bca085135fac0178243ffc0d9df
diff --git a/Android.bp b/Android.bp
index 62bf1cf..3544957 100644
--- a/Android.bp
+++ b/Android.bp
@@ -19,6 +19,7 @@
   srcs: [
     "src/trace_processor/metrics/android/android_mem.sql",
     "src/trace_processor/metrics/android/android_mem_lmk.sql",
+    "src/trace_processor/metrics/android/android_mem_proc_counters.sql",
   ],
   cmd: "$(location tools/gen_merged_sql_metrics.py) --cpp_out=$(out) $(in)",
   out: [
diff --git a/BUILD b/BUILD
index 164767e..489b8a8 100644
--- a/BUILD
+++ b/BUILD
@@ -26,6 +26,7 @@
     srcs = [
         "src/trace_processor/metrics/android/android_mem.sql",
         "src/trace_processor/metrics/android/android_mem_lmk.sql",
+        "src/trace_processor/metrics/android/android_mem_proc_counters.sql",
     ],
     outs = [
         "src/trace_processor/metrics/sql_metrics.h",
diff --git a/BUILD.gn b/BUILD.gn
index 084bf9d..830a488 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -138,7 +138,10 @@
     deps += [ "src/profiling/memory:unittests" ]
   }
   if (perfetto_build_standalone && !is_android) {
-    deps += [ "src/trace_processor:unittests" ]
+    deps += [
+      "src/trace_processor:unittests",
+      "src/trace_processor/metrics:unittests",
+    ]
   }
 }
 
diff --git a/include/perfetto/trace_processor/basic_types.h b/include/perfetto/trace_processor/basic_types.h
index 535969c..32ca2cd 100644
--- a/include/perfetto/trace_processor/basic_types.h
+++ b/include/perfetto/trace_processor/basic_types.h
@@ -19,10 +19,11 @@
 
 #include <stdint.h>
 
+#include "perfetto/base/logging.h"
+
 namespace perfetto {
 namespace trace_processor {
 
-
 struct Config {
   uint64_t window_size_ns = 180 * 1000 * 1000 * 1000ULL;  // 3 minutes.
 };
@@ -37,6 +38,11 @@
     kDouble,
   };
 
+  double AsDouble() {
+    PERFETTO_CHECK(type == kDouble);
+    return double_value;
+  }
+
   // Up to 1 of these fields can be accessed depending on |type|.
   union {
     // This string will be owned by the iterator that returned it and is valid
diff --git a/src/trace_processor/metrics/BUILD.gn b/src/trace_processor/metrics/BUILD.gn
index 1f0fdbf..2cc26cb 100644
--- a/src/trace_processor/metrics/BUILD.gn
+++ b/src/trace_processor/metrics/BUILD.gn
@@ -17,6 +17,7 @@
 sql_files = [
   "android/android_mem.sql",
   "android/android_mem_lmk.sql",
+  "android/android_mem_proc_counters.sql",
 ]
 
 config("gen_config") {
@@ -52,3 +53,16 @@
     "../../protozero:protozero",
   ]
 }
+
+source_set("unittests") {
+  testonly = true
+  sources = [
+    "metrics_unittest.cc",
+  ]
+  deps = [
+    ":lib",
+    "../../../buildtools:sqlite",
+    "../../../gn:default_deps",
+    "../../../gn:gtest_deps",
+  ]
+}
diff --git a/src/trace_processor/metrics/android/android_mem.sql b/src/trace_processor/metrics/android/android_mem.sql
index 7646381..4d44723 100644
--- a/src/trace_processor/metrics/android/android_mem.sql
+++ b/src/trace_processor/metrics/android/android_mem.sql
@@ -16,3 +16,15 @@
 
 -- Create all the views used to generate the Android Memory metrics proto.
 SELECT RUN_METRIC('android_mem_lmk.sql');
+
+-- Generate the process counter metrics.
+SELECT RUN_METRIC('android_mem_proc_counters.sql',
+                  'table_name',
+                  'file_rss',
+                  'counter_names',
+                  '("mem.rss.anon")');
+SELECT RUN_METRIC('android_mem_proc_counters.sql',
+                  'table_name',
+                  'anon_rss',
+                  'counter_names',
+                  '("mem.rss.anon")');
diff --git a/src/trace_processor/metrics/android/android_mem_proc_counters.sql b/src/trace_processor/metrics/android/android_mem_proc_counters.sql
new file mode 100644
index 0000000..241bbb2
--- /dev/null
+++ b/src/trace_processor/metrics/android/android_mem_proc_counters.sql
@@ -0,0 +1,36 @@
+--
+-- Copyright 2019 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://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.
+--
+
+CREATE VIEW {{table_name}}_span AS
+SELECT
+  ts,
+  LEAD(ts, 1, ts) OVER(PARTITION BY counter_id ORDER BY ts) - ts AS dur,
+  ref AS upid,
+  value
+FROM counters
+WHERE name IN {{counter_names}} AND ref IS NOT NULL AND ref_type = 'upid';
+
+CREATE VIEW {{table_name}} AS
+SELECT
+  process.name,
+  MIN(span.value),
+  MAX(span.value),
+  SUM(span.value * span.dur) / SUM(span.dur)
+FROM {{table_name}}_span as span JOIN process USING(upid)
+WHERE NOT (process.name IS NULL OR process.name = '')
+GROUP BY 1
+HAVING SUM(span.dur) > 0
+ORDER BY 1;
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index 48a6f19..2abd37d 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -16,6 +16,10 @@
 
 #include "src/trace_processor/metrics/metrics.h"
 
+#include <regex>
+#include <unordered_map>
+#include <vector>
+
 #include "perfetto/base/string_utils.h"
 #include "perfetto/metrics/android/mem_metric.pbzero.h"
 #include "perfetto/metrics/metrics.pbzero.h"
@@ -26,10 +30,44 @@
 namespace trace_processor {
 namespace metrics {
 
+namespace {
+// TODO(lalitm): delete this and use sqlite_utils when that is cleaned up of
+// trace processor dependencies.
+const char* ExtractSqliteValue(sqlite3_value* value) {
+  auto type = sqlite3_value_type(value);
+  PERFETTO_DCHECK(type == SQLITE_TEXT);
+  return reinterpret_cast<const char*>(sqlite3_value_text(value));
+}
+}  // namespace
+
+int TemplateReplace(
+    const std::string& raw_text,
+    const std::unordered_map<std::string, std::string>& substitutions,
+    std::string* out) {
+  std::regex re(R"(\{\{\s*(\w*)\s*\}\})", std::regex_constants::ECMAScript);
+
+  auto it = std::sregex_iterator(raw_text.begin(), raw_text.end(), re);
+  auto regex_end = std::sregex_iterator();
+  auto start = raw_text.begin();
+  for (; it != regex_end; ++it) {
+    out->insert(out->end(), start, raw_text.begin() + it->position(0));
+
+    auto value_it = substitutions.find(it->str(1));
+    if (value_it == substitutions.end())
+      return 1;
+
+    const auto& value = value_it->second;
+    std::copy(value.begin(), value.end(), std::back_inserter(*out));
+    start = raw_text.begin() + it->position(0) + it->length(0);
+  }
+  out->insert(out->end(), start, raw_text.end());
+  return 0;
+}
+
 void RunMetric(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
   auto* tp = static_cast<TraceProcessor*>(sqlite3_user_data(ctx));
   if (argc == 0 || sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
-    sqlite3_result_error(ctx, "Invalid call to RUN_METRIC", -1);
+    sqlite3_result_error(ctx, "RUN_METRIC: Invalid arguments", -1);
     return;
   }
 
@@ -37,20 +75,43 @@
       reinterpret_cast<const char*>(sqlite3_value_text(argv[0]));
   const char* sql = sql_metrics::GetBundledMetric(filename);
   if (!sql) {
-    sqlite3_result_error(ctx, "Unknown filename provided to RUN_METRIC", -1);
+    sqlite3_result_error(ctx, "RUN_METRIC: Unknown filename provided", -1);
     return;
   }
 
-  for (const auto& query : base::SplitString(sql, ";\n\n")) {
-    PERFETTO_DLOG("Executing query in RUN_METRIC: %s", query.c_str());
+  std::unordered_map<std::string, std::string> substitutions;
+  for (int i = 1; i < argc; i += 2) {
+    if (sqlite3_value_type(argv[i]) != SQLITE_TEXT) {
+      sqlite3_result_error(ctx, "RUN_METRIC: Invalid args", -1);
+      return;
+    }
 
-    auto it = tp->ExecuteQuery(query);
+    auto* key_str = ExtractSqliteValue(argv[i]);
+    auto* value_str = ExtractSqliteValue(argv[i + 1]);
+    substitutions[key_str] = value_str;
+  }
+
+  for (const auto& query : base::SplitString(sql, ";\n")) {
+    std::string buffer;
+    int ret = TemplateReplace(query, substitutions, &buffer);
+    if (ret) {
+      sqlite3_result_error(
+          ctx, "RUN_METRIC: Error when performing substitution", -1);
+      return;
+    }
+
+    PERFETTO_DLOG("RUN_METRIC: Executing query: %s", buffer.c_str());
+    auto it = tp->ExecuteQuery(buffer);
     if (auto opt_error = it.GetLastError()) {
-      sqlite3_result_error(ctx, "Error when running RUN_METRIC file", -1);
+      char* error =
+          sqlite3_mprintf("RUN_METRIC: Error when running file %s: %s",
+                          filename, opt_error->c_str());
+      sqlite3_result_error(ctx, error, -1);
+      sqlite3_free(error);
       return;
     } else if (it.Next()) {
       sqlite3_result_error(
-          ctx, "RUN_METRIC functions should not produce any output", -1);
+          ctx, "RUN_METRIC: functions should not produce any output", -1);
       return;
     }
   }
@@ -66,7 +127,7 @@
     return 1;
   }
 
-  auto queries = base::SplitString(sql_metrics::kAndroidMem, ";\n\n");
+  auto queries = base::SplitString(sql_metrics::kAndroidMem, ";\n");
   for (const auto& query : queries) {
     PERFETTO_DLOG("Executing query: %s", query.c_str());
     auto prep_it = tp->ExecuteQuery(query);
@@ -96,15 +157,32 @@
   PERFETTO_CHECK(has_next);
   PERFETTO_CHECK(it.Get(0).type == SqlValue::Type::kLong);
 
+  has_next = it.Next();
+  PERFETTO_DCHECK(!has_next);
+
   auto* memory = metrics.set_android_mem();
   memory->set_system_metrics()->set_lmks()->set_total_count(
       static_cast<int32_t>(it.Get(0).long_value));
+
+  it = tp->ExecuteQuery("SELECT * from anon_rss;");
+  while (it.Next()) {
+    const char* name = it.Get(0).string_value;
+
+    auto* process = memory->add_process_metrics();
+    process->set_process_name(name);
+
+    auto* anon = process->set_overall_counters()->set_anon_rss();
+    anon->set_min(it.Get(1).AsDouble());
+    anon->set_max(it.Get(2).AsDouble());
+    anon->set_avg(it.Get(3).AsDouble());
+  }
+  if (auto opt_error = it.GetLastError()) {
+    PERFETTO_ELOG("SQLite error: %s", opt_error->c_str());
+    return 1;
+  }
+
   metrics.Finalize();
-
   *metrics_proto = delegate.StitchSlices();
-
-  has_next = it.Next();
-  PERFETTO_DCHECK(!has_next);
   return 0;
 }
 
diff --git a/src/trace_processor/metrics/metrics.h b/src/trace_processor/metrics/metrics.h
index 324d436..4c85ecb 100644
--- a/src/trace_processor/metrics/metrics.h
+++ b/src/trace_processor/metrics/metrics.h
@@ -18,6 +18,7 @@
 #define SRC_TRACE_PROCESSOR_METRICS_METRICS_H_
 
 #include <sqlite3.h>
+#include <unordered_map>
 #include <vector>
 
 #include "perfetto/trace_processor/trace_processor.h"
@@ -26,6 +27,16 @@
 namespace trace_processor {
 namespace metrics {
 
+// Replaces templated variables inside |raw_text| using the substitution given
+// by |substitutions| writing the result to |out|.
+// The syntax followed is a cut-down variant of Jinja. This means variables that
+// are to be replaced use {{variable-name}} in the raw text with subsitutions
+// containing a mapping from (variable-name -> replacement).
+int TemplateReplace(
+    const std::string& raw_text,
+    const std::unordered_map<std::string, std::string>& substitutions,
+    std::string* out);
+
 // This function implements the RUN_METRIC SQL function.
 void RunMetric(sqlite3_context* ctx, int argc, sqlite3_value** argv);
 
diff --git a/src/trace_processor/metrics/metrics_unittest.cc b/src/trace_processor/metrics/metrics_unittest.cc
new file mode 100644
index 0000000..110ae26
--- /dev/null
+++ b/src/trace_processor/metrics/metrics_unittest.cc
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/metrics/metrics.h"
+
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace metrics {
+
+namespace {
+
+std::string RunTemplateReplace(
+    const std::string& str,
+    std::unordered_map<std::string, std::string> subs) {
+  std::string out;
+  EXPECT_EQ(TemplateReplace(str, subs, &out), 0);
+  return out;
+}
+
+TEST(MetricsTest, TemplateReplace) {
+  auto res = RunTemplateReplace("no templates here", {});
+  ASSERT_EQ(res, "no templates here");
+
+  res = RunTemplateReplace("{{justtemplate}}", {{"justtemplate", "result"}});
+  ASSERT_EQ(res, "result");
+
+  res = RunTemplateReplace("{{temp1}} {{temp2}}!",
+                           {{"temp1", "hello"}, {"temp2", "world"}});
+  ASSERT_EQ(res, "hello world!");
+
+  std::string unused;
+  ASSERT_NE(TemplateReplace("{{missing}}", {{}}, &unused), 0);
+}
+
+}  // namespace
+
+}  // namespace metrics
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/sqlite_utils.h b/src/trace_processor/sqlite_utils.h
index 7f5d732..5bd1e5f 100644
--- a/src/trace_processor/sqlite_utils.h
+++ b/src/trace_processor/sqlite_utils.h
@@ -136,12 +136,15 @@
 // uint64_t at all given that SQLite doesn't support it.
 
 template <>
-inline std::string ExtractSqliteValue(sqlite3_value* value) {
+inline const char* ExtractSqliteValue(sqlite3_value* value) {
   auto type = sqlite3_value_type(value);
   PERFETTO_DCHECK(type == SQLITE_TEXT);
-  const auto* extracted =
-      reinterpret_cast<const char*>(sqlite3_value_text(value));
-  return std::string(extracted);
+  return reinterpret_cast<const char*>(sqlite3_value_text(value));
+}
+
+template <>
+inline std::string ExtractSqliteValue(sqlite3_value* value) {
+  return ExtractSqliteValue<const char*>(value);
 }
 
 template <typename T>
diff --git a/tools/gen_merged_sql_metrics.py b/tools/gen_merged_sql_metrics.py
index 071423c..ba3b0c7 100755
--- a/tools/gen_merged_sql_metrics.py
+++ b/tools/gen_merged_sql_metrics.py
@@ -87,20 +87,19 @@
   args = parser.parse_args()
 
   # Extract the SQL output from each file.
-  escaped_sql_outputs = {}
+  sql_outputs = {}
   for file_name in args.sql_files:
     with open(file_name, 'r') as f:
       basename = os.path.basename(file_name)
-
-      # Escape any quote characters.
-      escaped_sql_outputs[basename] = "".join(f.readlines())
+      sql_outputs[basename] = "".join(
+        x for x in f.readlines() if not x.startswith('--'))
 
   with open(args.cpp_out, 'w+') as output:
     output.write(REPLACEMENT_HEADER)
     output.write(NAMESPACE_BEGIN)
 
     # Create the C++ variable for each SQL file.
-    for name, sql in escaped_sql_outputs.items():
+    for name, sql in sql_outputs.items():
       variable = filename_to_variable(os.path.splitext(name)[0])
       output.write('\nconst char {}[] = R"gendelimiter(\n{})gendelimiter";\n'
         .format(variable, sql))
@@ -109,7 +108,7 @@
 
     # Create mapping of filename to variable name for each variable.
     output.write("\nconst FileToSql kFileToSql[] = {")
-    for name in escaped_sql_outputs.keys():
+    for name in sql_outputs.keys():
       variable = filename_to_variable(os.path.splitext(name)[0])
       output.write('\n  {{"{}", {}}},\n'.format(name, variable))
     output.write("};\n")