hprof converter skeleton

This is just skeleton code:
- New trace converter call
- Utility functions for writing big-endian outputs
- Utility functions for writing hprof records and avoid manual size
calculations

Bug: 162833724
Change-Id: If76e3d968439a21f3d746689f51c804115790375
diff --git a/Android.bp b/Android.bp
index 0f0cc57..41a6e47 100644
--- a/Android.bp
+++ b/Android.bp
@@ -7847,6 +7847,7 @@
     "tools/trace_to_text/deobfuscate_profile.cc",
     "tools/trace_to_text/main.cc",
     "tools/trace_to_text/symbolize_profile.cc",
+    "tools/trace_to_text/trace_to_hprof.cc",
     "tools/trace_to_text/trace_to_json.cc",
     "tools/trace_to_text/trace_to_profile.cc",
     "tools/trace_to_text/trace_to_systrace.cc",
diff --git a/BUILD b/BUILD
index b50e0da..7d2c74e 100644
--- a/BUILD
+++ b/BUILD
@@ -1503,6 +1503,8 @@
         "tools/trace_to_text/main.cc",
         "tools/trace_to_text/symbolize_profile.cc",
         "tools/trace_to_text/symbolize_profile.h",
+        "tools/trace_to_text/trace_to_hprof.cc",
+        "tools/trace_to_text/trace_to_hprof.h",
         "tools/trace_to_text/trace_to_json.cc",
         "tools/trace_to_text/trace_to_json.h",
         "tools/trace_to_text/trace_to_profile.cc",
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index bb0cf7b..fe71f33 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -107,6 +107,8 @@
     "main.cc",
     "symbolize_profile.cc",
     "symbolize_profile.h",
+    "trace_to_hprof.cc",
+    "trace_to_hprof.h",
     "trace_to_json.cc",
     "trace_to_json.h",
     "trace_to_profile.cc",
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index e3c084c..3e7cb1a 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -25,6 +25,7 @@
 #include "perfetto/ext/base/string_utils.h"
 #include "tools/trace_to_text/deobfuscate_profile.h"
 #include "tools/trace_to_text/symbolize_profile.h"
+#include "tools/trace_to_text/trace_to_hprof.h"
 #include "tools/trace_to_text/trace_to_json.h"
 #include "tools/trace_to_text/trace_to_profile.h"
 #include "tools/trace_to_text/trace_to_systrace.h"
@@ -144,9 +145,11 @@
 
   std::string format(positional_args[0]);
 
-  if (format != "profile" && (pid != 0 || !timestamps.empty())) {
+  if ((format != "profile" && format != "hprof") &&
+      (pid != 0 || !timestamps.empty())) {
     PERFETTO_ELOG(
-        "--pid and --timestamps are supported only for profile format.");
+        "--pid and --timestamps are supported only for profile "
+        "formats.");
     return 1;
   }
 
@@ -180,6 +183,9 @@
   if (format == "profile")
     return TraceToProfile(input_stream, output_stream, pid, timestamps);
 
+  if (format == "hprof")
+    return TraceToHprof(input_stream, output_stream, pid, timestamps);
+
   if (format == "symbolize")
     return SymbolizeProfile(input_stream, output_stream);
 
diff --git a/tools/trace_to_text/trace_to_hprof.cc b/tools/trace_to_text/trace_to_hprof.cc
new file mode 100644
index 0000000..df929ab
--- /dev/null
+++ b/tools/trace_to_text/trace_to_hprof.cc
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2020 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/trace_to_text/trace_to_hprof.h"
+
+#include <endian.h>
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "tools/trace_to_text/utils.h"
+
+// Spec
+// http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share/demo/jvmti/hprof/manual.html#Basic_Type
+// Parser
+// https://cs.android.com/android/platform/superproject/+/master:art/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java
+
+namespace perfetto {
+namespace trace_to_text {
+
+namespace {
+constexpr char HEADER[] = "PERFETTO_JAVA_HEAP";
+constexpr uint32_t ID_SZ = 8;
+
+class BigEndianBuffer {
+ public:
+  void WriteId(uint64_t val) { WriteU8(val); }
+
+  void WriteU8(uint64_t val) {
+    val = htobe64(val);
+    Write(reinterpret_cast<char*>(&val), sizeof(uint64_t));
+  }
+
+  void WriteU4(uint32_t val) {
+    val = htobe32(val);
+    Write(reinterpret_cast<char*>(&val), sizeof(uint32_t));
+  }
+
+  void SetU4(uint32_t val, size_t pos) {
+    val = htobe32(val);
+    memcpy(buf_.data() + pos, &val, sizeof(uint32_t));
+  }
+
+  // Uncomment when needed
+  // void WriteU2(uint16_t val) {
+  //   val = htobe16(val);
+  //   Write(reinterpret_cast<char*>(&val), sizeof(uint16_t));
+  // }
+
+  void WriteByte(uint8_t val) { buf_.emplace_back(val); }
+
+  void Write(const char* val, uint32_t sz) {
+    const char* end = val + sz;
+    while (val < end) {
+      WriteByte(static_cast<uint8_t>(*val));
+      val++;
+    }
+  }
+
+  size_t written() const { return buf_.size(); }
+
+  void Flush(std::ostream* out) const {
+    out->write(buf_.data(), static_cast<std::streamsize>(buf_.size()));
+  }
+
+ private:
+  std::vector<char> buf_;
+};
+
+class HprofWriter {
+ public:
+  HprofWriter(std::ostream* output) : output_(output) {}
+
+  void WriteBuffer(const BigEndianBuffer& buf) { buf.Flush(output_); }
+
+  void WriteRecord(const uint8_t type,
+                   const std::function<void(BigEndianBuffer*)>&& writer) {
+    BigEndianBuffer buf;
+    buf.WriteByte(type);
+    // ts offset
+    buf.WriteU4(0);
+    // size placeholder
+    buf.WriteU4(0);
+    writer(&buf);
+    uint32_t record_sz = static_cast<uint32_t>(buf.written() - 9);
+    buf.SetU4(record_sz, 5);
+    WriteBuffer(buf);
+  }
+
+ private:
+  std::ostream* output_;
+};
+
+// TODO: sample code really, rewrite this
+std::unordered_map<std::string, uint32_t> WriteStrings(
+    trace_processor::TraceProcessor* tp,
+    HprofWriter* writer) {
+  auto it = tp->ExecuteQuery(R"(
+      SELECT DISTINCT str FROM (
+        SELECT CASE
+          WHEN str LIKE 'java.lang.Class<%' THEN rtrim(substr(str, 17), '>')
+          ELSE str
+        END str
+        FROM (SELECT IFNULL(deobfuscated_name, name) str FROM heap_graph_class)
+        UNION ALL
+        SELECT IFNULL(deobfuscated_field_name, field_name) str
+        FROM heap_graph_reference
+      ))");
+
+  std::unordered_map<std::string, uint32_t> strings;
+  uint32_t id = 1;
+  while (it.Next()) {
+    std::string name(it.Get(0).AsString());
+    strings[name] = id;
+
+    // Size of record is the id + the string length
+    writer->WriteRecord(0x01, [id, &name](BigEndianBuffer* buf) {
+      buf->WriteId(id);
+      buf->Write(name.c_str(), static_cast<uint32_t>(name.length()));
+    });
+
+    ++id;
+  }
+  return strings;
+}
+}  // namespace
+
+int TraceToHprof(trace_processor::TraceProcessor* tp,
+                 std::ostream* output,
+                 uint64_t pid,
+                 uint64_t ts) {
+  PERFETTO_DCHECK(tp != nullptr && pid != 0 && ts != 0);
+  HprofWriter hprof(output);
+  BigEndianBuffer header;
+  header.Write(HEADER, sizeof(HEADER));
+  // Identifier size
+  header.WriteU4(ID_SZ);
+  // walltime high (unused)
+  header.WriteU4(0);
+  // walltime low (unused)
+  header.WriteU4(0);
+  hprof.WriteBuffer(header);
+
+  const auto interned = WriteStrings(tp, &hprof);
+  // Add placeholder stack trace (required by the format).
+  hprof.WriteRecord(0x05, [](BigEndianBuffer* buf) {
+    buf->WriteU4(0);
+    buf->WriteU4(0);
+    buf->WriteU4(0);
+  });
+  return 0;
+}
+
+int TraceToHprof(std::istream* input,
+                 std::ostream* output,
+                 uint64_t pid,
+                 std::vector<uint64_t> timestamps) {
+  // TODO: Simplify this for cmdline users. For example, if there is a single
+  // heap graph, use this, and only fail when there is ambiguity.
+  if (pid == 0) {
+    PERFETTO_ELOG("Must specify pid");
+    return -1;
+  }
+  if (timestamps.size() != 1) {
+    PERFETTO_ELOG("Must specify single timestamp");
+    return -1;
+  }
+  trace_processor::Config config;
+  std::unique_ptr<trace_processor::TraceProcessor> tp =
+      trace_processor::TraceProcessor::CreateInstance(config);
+  if (!ReadTrace(tp.get(), input))
+    return false;
+  tp->NotifyEndOfFile();
+  return TraceToHprof(tp.get(), output, pid, timestamps[0]);
+}
+
+}  // namespace trace_to_text
+}  // namespace perfetto
diff --git a/tools/trace_to_text/trace_to_hprof.h b/tools/trace_to_text/trace_to_hprof.h
new file mode 100644
index 0000000..acc142a
--- /dev/null
+++ b/tools/trace_to_text/trace_to_hprof.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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_TRACE_TO_TEXT_TRACE_TO_HPROF_H_
+#define TOOLS_TRACE_TO_TEXT_TRACE_TO_HPROF_H_
+
+#include <iostream>
+#include <vector>
+#include "perfetto/trace_processor/trace_processor.h"
+
+namespace perfetto {
+namespace trace_to_text {
+
+int TraceToHprof(trace_processor::TraceProcessor* tp,
+                 std::ostream* output,
+                 uint64_t pid,
+                 uint64_t timestamp);
+
+int TraceToHprof(std::istream* input,
+                 std::ostream* output,
+                 uint64_t pid = 0,
+                 std::vector<uint64_t> timestamps = {});
+
+}  // namespace trace_to_text
+}  // namespace perfetto
+
+#endif  // TOOLS_TRACE_TO_TEXT_TRACE_TO_HPROF_H_