Add format parser

$ cat tools/ftrace_proto_gen/ion_alloc_buffer_end.format | ./out/linux/ftrace_proto_gen /dev/stdin /dev/stdout
// Autogenerated, do not edit.
syntax = "proto3";
message ion_alloc_buffer_end {
  string client_name = 1;
  string heap_name = 2;
  uint32 len = 3;
  uint32 mask = 4;
  uint32 flags = 5;
}

To test on a large corpus:
$ wget https://hjd.users.x20web.corp.google.com/www/2017-10-10-perfetto-format-files/testdata.tar.gz
$ tar -zxvf testdata.tar.gz
$ rm testdata.tar.gz
$ find testdata -type f | grep 'format' | while read f; do echo $f; python testdata/test_proto_gen.py out/linux/ftrace_proto_gen out/linux/gcc_like_host/protoc "$f"; done

Change-Id: I8afa0097e681e5bbf4cbbf33501be006e14dc973
diff --git a/BUILD.gn b/BUILD.gn
index 28a22f4..9a222ed 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -17,6 +17,7 @@
   deps = [
     ":tests",
     "//buildtools:protobuf_lite",
+    "//tools/ftrace_proto_gen:ftrace_proto_gen",
   ]
   deps += [ "//buildtools:protoc($host_toolchain)" ]
 }
@@ -27,5 +28,6 @@
     "//libtracing:libtracing_benchmarks",
     "//libtracing:libtracing_unittests",
     "//libtracing:sanitizers_unittests",
+    "//tools/ftrace_proto_gen:ftrace_proto_gen_unittests",
   ]
 }
diff --git a/testdata/.gitignore b/testdata/.gitignore
new file mode 100644
index 0000000..77c3bb2
--- /dev/null
+++ b/testdata/.gitignore
@@ -0,0 +1,2 @@
+androidone/
+linux/
diff --git a/testdata/test_proto_gen.py b/testdata/test_proto_gen.py
new file mode 100755
index 0000000..104a3a2
--- /dev/null
+++ b/testdata/test_proto_gen.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# 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.
+
+from __future__ import print_function
+import sys
+import os
+import subprocess
+import tempfile
+
+def test_command(*args):
+  subprocess.check_call(args, stdout=sys.stdout, stderr=sys.stderr)
+
+if __name__ == '__main__':
+  if len(sys.argv) != 4:
+    print('Usage: test_proto_gen.py to/ftrace_proto_gen to/protoc to/format')
+    exit(1)
+  ftrace_proto_gen_path = sys.argv[1]
+  protoc_path = sys.argv[2]
+  format_path = sys.argv[3]
+  tmpdir = tempfile.mkdtemp()
+  proto_path = os.path.join(tmpdir, 'format.proto')
+  test_command(ftrace_proto_gen_path, format_path, proto_path)
+  test_command(
+      protoc_path,
+      proto_path,
+      '--proto_path='+tmpdir,
+      '--cpp_out='+tmpdir)
diff --git a/tools/ftrace_proto_gen/BUILD.gn b/tools/ftrace_proto_gen/BUILD.gn
new file mode 100644
index 0000000..ed6c246
--- /dev/null
+++ b/tools/ftrace_proto_gen/BUILD.gn
@@ -0,0 +1,43 @@
+# 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.
+
+executable("ftrace_proto_gen_unittests") {
+  testonly = true
+  deps += [
+    ":lib",
+    "//buildtools:gmock",
+    "//buildtools:gtest",
+    "//buildtools:gtest_main",
+  ]
+  sources = [
+    "format_parser_unittest.cc",
+    "ftrace_to_proto_unittest.cc",
+  ]
+}
+
+executable("ftrace_proto_gen") {
+  sources = [
+    "main.cc",
+  ]
+  deps += [ ":lib" ]
+}
+
+source_set("lib") {
+  sources = [
+    "format_parser.cc",
+    "format_parser.h",
+    "ftrace_to_proto.cc",
+    "ftrace_to_proto.h",
+  ]
+}
diff --git a/tools/ftrace_proto_gen/format_parser.cc b/tools/ftrace_proto_gen/format_parser.cc
new file mode 100644
index 0000000..0ce1519
--- /dev/null
+++ b/tools/ftrace_proto_gen/format_parser.cc
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+#include "tools/ftrace_proto_gen/format_parser.h"
+
+#include <string.h>
+
+#include <iosfwd>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+#include "tools/ftrace_proto_gen/ftrace_to_proto.h"
+
+namespace perfetto {
+namespace {
+
+#define MAX_FIELD_LENGTH 127
+#define STRINGIFY(x) STRINGIFY2(x)
+#define STRINGIFY2(x) #x
+
+const char* kCommonFieldPrefix = "common_";
+
+}  // namespace
+
+bool ParseFtraceEvent(const std::string& input, FtraceEvent* output) {
+  std::unique_ptr<char[]> input_copy(strdup(input.c_str()));
+  char* s = input_copy.get();
+
+  char buffer[MAX_FIELD_LENGTH + 1];
+
+  bool has_id = false;
+  bool has_name = false;
+
+  int id = 0;
+  std::string name;
+  std::vector<FtraceEvent::Field> fields;
+
+  for (char* line = strtok(s, "\n"); line; line = strtok(nullptr, "\n")) {
+    if (!has_id && sscanf(line, "ID: %d", &id) == 1) {
+      has_id = true;
+      continue;
+    }
+
+    if (!has_name &&
+        sscanf(line, "name: %" STRINGIFY(MAX_FIELD_LENGTH) "s", buffer) == 1) {
+      name = std::string(buffer);
+      has_name = true;
+      continue;
+    }
+
+    if (strcmp("format:", line) == 0) {
+      continue;
+    }
+
+    int offset = 0;
+    int size = 0;
+    int is_signed = 0;
+    if (sscanf(
+            line,
+            "\tfield:%" STRINGIFY(
+                MAX_FIELD_LENGTH) "[^;];\toffset: %d;\tsize: %d;\tsigned: %d;",
+            buffer, &offset, &size, &is_signed) == 4) {
+      std::string type_and_name(buffer);
+
+      // Don't add common fields.
+      if (GetNameFromTypeAndName(type_and_name)
+              .compare(0, strlen(kCommonFieldPrefix), kCommonFieldPrefix) == 0)
+        continue;
+
+      FtraceEvent::Field field{type_and_name, offset, size, is_signed == 1};
+      fields.push_back(field);
+      continue;
+    }
+
+    if (strncmp(line, "print fmt:", 10) == 0) {
+      break;
+    }
+
+    if (output)
+      fprintf(stderr, "Cannot parse line: \"%s\"\n", line);
+    return false;
+  }
+
+  if (!has_id || !has_name || fields.size() == 0) {
+    if (output)
+      fprintf(stderr, "Could not parse format file: %s.\n",
+              !has_id ? "no ID found"
+                      : !has_name ? "no name found" : "no fields found");
+    return false;
+  }
+
+  if (!output)
+    return true;
+
+  output->id = id;
+  output->name = name;
+  output->fields = std::move(fields);
+
+  return true;
+}
+
+::std::ostream& operator<<(::std::ostream& os,
+                           const FtraceEvent::Field& field) {
+  PrintTo(field, &os);
+  return os;
+}
+
+// Allow gtest to pretty print FtraceEvent::Field.
+void PrintTo(const FtraceEvent::Field& field, ::std::ostream* os) {
+  *os << "FtraceEvent::Field(" << field.type_and_name << ", " << field.offset
+      << ", " << field.size << ", " << field.is_signed << ")";
+}
+
+}  // namespace perfetto
diff --git a/tools/ftrace_proto_gen/format_parser.h b/tools/ftrace_proto_gen/format_parser.h
new file mode 100644
index 0000000..317c30a
--- /dev/null
+++ b/tools/ftrace_proto_gen/format_parser.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#ifndef TOOLS_FTRACE_PROTO_GEN_FORMAT_PARSER_H_
+#define TOOLS_FTRACE_PROTO_GEN_FORMAT_PARSER_H_
+
+#include <string>
+
+namespace perfetto {
+
+struct FtraceEvent;
+
+bool ParseFtraceEvent(const std::string& input, FtraceEvent* output = nullptr);
+
+}  // namespace perfetto
+
+#endif  // TOOLS_FTRACE_PROTO_GEN_FORMAT_PARSER_H_
diff --git a/tools/ftrace_proto_gen/format_parser_unittest.cc b/tools/ftrace_proto_gen/format_parser_unittest.cc
new file mode 100644
index 0000000..4238ece
--- /dev/null
+++ b/tools/ftrace_proto_gen/format_parser_unittest.cc
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+#include "tools/ftrace_proto_gen/format_parser.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tools/ftrace_proto_gen/ftrace_to_proto.h"
+
+namespace perfetto {
+namespace {
+
+using testing::ElementsAre;
+using testing::Eq;
+
+TEST(FtraceEventParser, HappyPath) {
+  const std::string input = R"(name: the_name
+ID: 42
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:char client_name[64];	offset:8;	size:64;	signed:0;
+	field:const char * heap_name;	offset:72;	size:4;	signed:1;
+
+print fmt: "client_name=%s heap_name=%s len=%zu mask=0x%x flags=0x%x", REC->client_name, REC->heap_name, REC->len, REC->mask, REC->flags
+)";
+
+  FtraceEvent output;
+  EXPECT_TRUE(ParseFtraceEvent(input));
+  EXPECT_TRUE(ParseFtraceEvent(input, &output));
+  EXPECT_EQ(output.name, "the_name");
+  EXPECT_EQ(output.id, 42);
+  EXPECT_THAT(
+      output.fields,
+      ElementsAre(
+          Eq(FtraceEvent::Field{"char client_name[64]", 8, 64, false}),
+          Eq(FtraceEvent::Field{"const char * heap_name", 72, 4, true})));
+}
+
+TEST(FtraceEventParser, MissingName) {
+  const std::string input = R"(ID: 42
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+print fmt: "client_name=%s heap_name=%s len=%zu mask=0x%x flags=0x%x", REC->client_name, REC->heap_name, REC->len, REC->mask, REC->flags
+)";
+
+  EXPECT_FALSE(ParseFtraceEvent(input));
+}
+
+TEST(FtraceEventParser, MissingID) {
+  const std::string input = R"(name: the_name
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+print fmt: "client_name=%s heap_name=%s len=%zu mask=0x%x flags=0x%x", REC->client_name, REC->heap_name, REC->len, REC->mask, REC->flags
+)";
+
+  EXPECT_FALSE(ParseFtraceEvent(input));
+}
+
+TEST(FtraceEventParser, NoFeilds) {
+  const std::string input = R"(name: the_name
+ID: 10
+print fmt: "client_name=%s heap_name=%s len=%zu mask=0x%x flags=0x%x", REC->client_name, REC->heap_name, REC->len, REC->mask, REC->flags
+)";
+
+  EXPECT_FALSE(ParseFtraceEvent(input));
+}
+
+TEST(FtraceEventParser, BasicFuzzing) {
+  const std::string input = R"(name: the_name
+ID: 42
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:char client_name[64];	offset:8;	size:64;	signed:0;
+	field:const char * heap_name;	offset:72;	size:4;	signed:0;
+	field:size_t len;	offset:76;	size:4;	signed:0;
+	field:unsigned int mask;	offset:80;	size:4;	signed:0;
+	field:unsigned int flags;	offset:84;	size:4;	signed:0;
+
+print fmt: "client_name=%s heap_name=%s len=%zu mask=0x%x flags=0x%x", REC->client_name, REC->heap_name, REC->len, REC->mask, REC->flags
+)";
+
+  for (size_t i = 0; i < input.length(); i++) {
+    for (size_t j = 1; j < 10 && i + j < input.length(); j++) {
+      std::string copy = input;
+      copy.erase(i, j);
+      ParseFtraceEvent(copy);
+    }
+  }
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/tools/ftrace_proto_gen/ftrace_to_proto.cc b/tools/ftrace_proto_gen/ftrace_to_proto.cc
new file mode 100644
index 0000000..0d1c81c
--- /dev/null
+++ b/tools/ftrace_proto_gen/ftrace_to_proto.cc
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+#include "tools/ftrace_proto_gen/ftrace_to_proto.h"
+
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace perfetto {
+namespace {
+
+bool IsCIdentifier(const std::string& s) {
+  for (const char c : s) {
+    if (!(std::isalnum(c) || c == '_'))
+      return false;
+  }
+  return s.size() > 0 && !std::isdigit(s[0]);
+}
+
+}  // namespace
+
+// For example:
+// "int foo" -> "foo"
+// "u8 foo[(int)sizeof(struct blah)]" -> "foo"
+// "char[] foo[16]" -> "foo"
+// "something_went_wrong" -> ""
+// "" -> ""
+std::string GetNameFromTypeAndName(const std::string& type_and_name) {
+  size_t right = type_and_name.size();
+  if (right == 0)
+    return "";
+
+  if (type_and_name[type_and_name.size() - 1] == ']') {
+    right = type_and_name.rfind('[');
+    if (right == std::string::npos)
+      return "";
+  }
+
+  size_t left = type_and_name.rfind(' ', right);
+  if (left == std::string::npos)
+    return "";
+  left++;
+
+  std::string result = type_and_name.substr(left, right - left);
+  if (!IsCIdentifier(result))
+    return "";
+
+  return result;
+}
+
+std::string InferProtoType(const FtraceEvent::Field& field) {
+  if (field.type_and_name.find("char *") != std::string::npos)
+    return "string";
+  if (field.size <= 4 && field.is_signed)
+    return "int32";
+  if (field.size <= 4 && !field.is_signed)
+    return "uint32";
+  if (field.size <= 8 && field.is_signed)
+    return "int64";
+  if (field.size <= 8 && !field.is_signed)
+    return "uint64";
+  return "";
+}
+
+bool GenerateProto(const FtraceEvent& format, Proto* proto_out) {
+  proto_out->name = format.name;
+  proto_out->fields.reserve(format.fields.size());
+  std::set<std::string> seen;
+  // TODO(hjd): We should be cleverer about id assignment.
+  uint32_t i = 1;
+  for (const FtraceEvent::Field& field : format.fields) {
+    std::string name = GetNameFromTypeAndName(field.type_and_name);
+    // TODO(hjd): Handle dup names.
+    if (name == "" || seen.count(name))
+      continue;
+    seen.insert(name);
+    std::string type = InferProtoType(field);
+    // Check we managed to infer a type.
+    if (type == "")
+      continue;
+    proto_out->fields.emplace_back(Proto::Field{type, name, i});
+    i++;
+  }
+
+  return true;
+}
+
+std::string Proto::ToString() {
+  std::stringstream s;
+  s << "// Autogenerated by " << __FILE__ << " do not edit.\n";
+  s << "syntax = \"proto3\";\n";
+  s << "message " << name << " {\n";
+  for (const Proto::Field& field : fields) {
+    s << "   " << field.type << " " << field.name << " = " << field.number
+      << ";\n";
+  }
+  s << "}\n";
+  return s.str();
+}
+
+}  // namespace perfetto
diff --git a/tools/ftrace_proto_gen/ftrace_to_proto.h b/tools/ftrace_proto_gen/ftrace_to_proto.h
new file mode 100644
index 0000000..db5a72f
--- /dev/null
+++ b/tools/ftrace_proto_gen/ftrace_to_proto.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#ifndef TOOLS_FTRACE_PROTO_GEN_FTRACE_GEN_H_
+#define TOOLS_FTRACE_PROTO_GEN_FTRACE_GEN_H_
+
+#include <stdint.h>
+
+#include <iosfwd>
+#include <iostream>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace perfetto {
+
+struct FtraceEvent {
+  struct Field {
+    std::string type_and_name;
+    int offset;
+    int size;
+    bool is_signed;
+
+    bool operator==(const Field& other) const {
+      return std::tie(type_and_name, offset, size, is_signed) ==
+             std::tie(other.type_and_name, other.offset, other.size,
+                      other.is_signed);
+    }
+  };
+
+  std::string name;
+  int id;
+  std::vector<Field> fields;
+};
+
+struct Proto {
+  struct Field {
+    std::string type;
+    std::string name;
+    uint32_t number;
+  };
+  std::string name;
+  std::vector<Field> fields;
+
+  std::string ToString();
+};
+
+bool GenerateProto(const FtraceEvent& format, Proto* proto_out);
+std::string InferProtoType(const FtraceEvent::Field& field);
+std::string GetNameFromTypeAndName(const std::string& type_and_name);
+
+// Allow gtest to pretty print FtraceEvent::Field.
+::std::ostream& operator<<(::std::ostream& os, const FtraceEvent::Field&);
+void PrintTo(const FtraceEvent::Field& args, ::std::ostream* os);
+
+}  // namespace perfetto
+
+#endif  // TOOLS_FTRACE_PROTO_GEN_FTRACE_GEN_H_
diff --git a/tools/ftrace_proto_gen/ftrace_to_proto_unittest.cc b/tools/ftrace_proto_gen/ftrace_to_proto_unittest.cc
new file mode 100644
index 0000000..df592e8
--- /dev/null
+++ b/tools/ftrace_proto_gen/ftrace_to_proto_unittest.cc
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#include "tools/ftrace_proto_gen/ftrace_to_proto.h"
+#include "gtest/gtest.h"
+
+namespace perfetto {
+namespace {
+
+TEST(FtraceEventParser, GetNameFromTypeAndName) {
+  EXPECT_EQ(GetNameFromTypeAndName("int foo"), "foo");
+  EXPECT_EQ(GetNameFromTypeAndName("int foo_bar"), "foo_bar");
+  EXPECT_EQ(GetNameFromTypeAndName("const char * foo"), "foo");
+  EXPECT_EQ(GetNameFromTypeAndName("const char foo[64]"), "foo");
+  EXPECT_EQ(GetNameFromTypeAndName("char[] foo[16]"), "foo");
+  EXPECT_EQ(GetNameFromTypeAndName("u8 foo[(int)sizeof(struct blah)]"), "foo");
+
+  EXPECT_EQ(GetNameFromTypeAndName(""), "");
+  EXPECT_EQ(GetNameFromTypeAndName("]"), "");
+  EXPECT_EQ(GetNameFromTypeAndName("["), "");
+  EXPECT_EQ(GetNameFromTypeAndName(" "), "");
+  EXPECT_EQ(GetNameFromTypeAndName(" []"), "");
+  EXPECT_EQ(GetNameFromTypeAndName(" ]["), "");
+  EXPECT_EQ(GetNameFromTypeAndName("char"), "");
+  EXPECT_EQ(GetNameFromTypeAndName("char *"), "");
+  EXPECT_EQ(GetNameFromTypeAndName("char 42"), "");
+}
+
+TEST(FtraceEventParser, InferProtoType) {
+  EXPECT_EQ(InferProtoType(FtraceEvent::Field{"char * foo", 2, 0, false}),
+            "string");
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/tools/ftrace_proto_gen/main.cc b/tools/ftrace_proto_gen/main.cc
new file mode 100644
index 0000000..e965cd4
--- /dev/null
+++ b/tools/ftrace_proto_gen/main.cc
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include "tools/ftrace_proto_gen/format_parser.h"
+#include "tools/ftrace_proto_gen/ftrace_to_proto.h"
+
+int main(int argc, const char** argv) {
+  if (argc != 3) {
+    printf("Usage: ./%s in.format out.proto\n", argv[0]);
+    return 1;
+  }
+
+  const char* input_path = argv[1];
+  const char* output_path = argv[2];
+
+  std::ifstream fin(input_path, std::ios::in);
+  if (!fin) {
+    fprintf(stderr, "Failed to open %s\n", input_path);
+    return 1;
+  }
+  std::ostringstream stream;
+  stream << fin.rdbuf();
+  fin.close();
+  std::string contents = stream.str();
+
+  perfetto::FtraceEvent format;
+  if (!perfetto::ParseFtraceEvent(contents, &format)) {
+    fprintf(stderr, "Could not parse file %s.\n", input_path);
+    return 1;
+  }
+
+  perfetto::Proto proto;
+  if (!perfetto::GenerateProto(format, &proto)) {
+    fprintf(stderr, "Could not generate proto for file %s\n", input_path);
+    return 1;
+  }
+
+  std::ofstream fout(output_path, std::ios::out);
+  if (!fout) {
+    fprintf(stderr, "Failed to open %s\n", output_path);
+    return 1;
+  }
+
+  fout << proto.ToString();
+  fout.close();
+}