Add new malloc benchmarks.

This runs through the trace of the allocations in a sql benchmark app
executed in the benchmark thread.

Add one benchmark with decay time set to 0 and another with decay time
set to 1.

Include a script that can generate a header file that can be used to
regenerate the data.

Bug: 112317428

Test: Builds, ran unit tests, ran benchmarks.
Change-Id: I62e287cc06b74b74bcc5a4bbee71b0fac0a196fd
diff --git a/libc/malloc_debug/tools/gen_malloc.pl b/libc/malloc_debug/tools/gen_malloc.pl
new file mode 100755
index 0000000..3fbb544
--- /dev/null
+++ b/libc/malloc_debug/tools/gen_malloc.pl
@@ -0,0 +1,334 @@
+#!/usr/bin/perl -w
+# Copyright (C) 2018 The Android Open Source Project
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#  * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#  * Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in
+#    the documentation and/or other materials provided with the
+#    distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+use strict;
+
+sub PrintHeader() {
+  print <<EOT;
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+// Generated by gen_malloc.pl, do not modify.
+
+EOT
+}
+
+sub PrintMainloop() {
+  print <<EOT;
+void BenchmarkMalloc(MallocEntry entries[], size_t total_entries, size_t max_allocs) {
+  void* ptrs[max_allocs];
+
+  for (size_t i = 0; i < total_entries; i++) {
+    switch (entries[i].type) {
+    case MALLOC:
+      ptrs[entries[i].idx] = malloc(entries[i].size);
+      // Touch at least one byte of the allocation to make sure that
+      // PSS for this allocation is counted.
+      reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 10;
+      break;
+    case CALLOC:
+      ptrs[entries[i].idx] = calloc(entries[i].arg2, entries[i].size);
+      // Touch at least one byte of the allocation to make sure that
+      // PSS for this allocation is counted.
+      reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 20;
+      break;
+    case MEMALIGN:
+      ptrs[entries[i].idx] = memalign(entries[i].arg2, entries[i].size);
+      // Touch at least one byte of the allocation to make sure that
+      // PSS for this allocation is counted.
+      reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 30;
+      break;
+    case REALLOC:
+      if (entries[i].arg2 == 0) {
+        ptrs[entries[i].idx] = realloc(nullptr, entries[i].size);
+      } else {
+        ptrs[entries[i].idx] = realloc(ptrs[entries[i].arg2 - 1], entries[i].size);
+      }
+      // Touch at least one byte of the allocation to make sure that
+      // PSS for this allocation is counted.
+      reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 40;
+      break;
+    case FREE:
+      free(ptrs[entries[i].idx]);
+      break;
+    }
+  }
+}
+
+EOT
+}
+
+sub PrintDefinitions() {
+  print <<EOT;
+enum AllocEnum : uint8_t {
+  MALLOC = 0,
+  CALLOC,
+  MEMALIGN,
+  REALLOC,
+  FREE,
+};
+
+struct MallocEntry {
+  AllocEnum type;
+  size_t idx;
+  size_t size;
+  size_t arg2;
+};
+
+EOT
+}
+
+sub PrintUsageAndExit() {
+  print "USAGE: gen_malloc.pl [-d][-i][-m] THREAD_ID STRUCT_NAME MAX_SLOT_NAME < ALLOCS.txt\n";
+  print "  -d\n";
+  print "    Print the structure definitions.\n";
+  print "  -i\n";
+  print "    Ignore missing allocations.\n";
+  print "  -m\n";
+  print "    Print the main loop code that can reproduce the trace.\n";
+  print "  THREAD_ID\n";
+  print "    The thread for which entries will be printed.\n";
+  print "  STRUCT_NAME\n";
+  print "    The name of the structure containing all of the entries.\n";
+  print "  MAX_SLOT_NAME\n";
+  print "    The name of the name of the maximum slots variable.\n";
+  print "  ALLOCS.txt\n";
+  print "    A file generated by the malloc debug option record_allocs\n";
+  exit(1);
+}
+
+sub GetSlot($) {
+  my ($opts) = @_;
+
+  if (scalar(@{$opts->{empty_slots}}) == 0) {
+    return $opts->{last_slot}++;
+  } else {
+    return pop(@{$opts->{empty_slots}});
+  }
+}
+
+sub PrintFreeSlots($) {
+  my ($opts) = @_;
+
+  if (scalar(@{$opts->{empty_slots}}) == $opts->{last_slot}) {
+    return;
+  }
+
+  print "\n    // Free rest of the allocs.\n";
+  my @sorted_empty_slots = sort({$a <=> $b} @{$opts->{empty_slots}});
+  my $slot = 0;
+  my $last_slot = $opts->{last_slot};
+  while ($slot < $last_slot) {
+    my $empty_slot = $last_slot;
+    if (scalar(@sorted_empty_slots) != 0) {
+      $empty_slot = shift(@sorted_empty_slots);
+    }
+    for (; $slot < $empty_slot; $slot++) {
+      print "  {FREE, $slot, 0, 0},\n";
+    }
+    $slot++;
+  }
+}
+
+sub PrintAlloc($$$$$$) {
+  my ($opts, $cur_thread, $pointer, $name, $size, $arg2) = @_;
+
+  if ($opts->{thread} eq $cur_thread) {
+    my $slot = GetSlot($opts);
+    $opts->{pointers}->{$pointer} = $slot;
+    print "  {$name, $slot, $size, $arg2},\n";
+  } else {
+    $opts->{pointers}->{$pointer} = -1;
+  }
+}
+
+sub PrintEntries($$) {
+  my ($thread, $ignore_missing_allocations) = @_;
+
+  my $opts = {};
+  $opts->{thread} = $thread;
+  $opts->{empty_slots} = [];
+  $opts->{last_slot} = 0;
+  $opts->{pointers} = {};
+
+  while (<>) {
+    if (!/^(\d+):\s*/) {
+      continue
+    }
+    my $cur_thread = $1;
+
+    $_ = $';
+    if (/^malloc\s+(\S+)\s+(\d+)/) {
+      my $pointer = $1;
+      my $size = $2;
+      PrintAlloc($opts, $cur_thread, $pointer, "MALLOC", $size, 0);
+    } elsif (/^calloc\s+(\S+)\s+(\d+)\s+(\d+)/) {
+      my $pointer = $1;
+      my $nmemb = $2;
+      my $size = $3;
+      PrintAlloc($opts, $cur_thread, $pointer, "CALLOC", $size, $nmemb);
+    } elsif (/^memalign\s+(\S+)\s+(\d+)\s+(\d+)/) {
+      my $pointer = $1;
+      my $align = $2;
+      my $size = $3;
+      PrintAlloc($opts, $cur_thread, $pointer, "MEMALIGN", $size, $align);
+    } elsif (/^free\s+(\S+)/) {
+      my $pointer = $1;
+      if (!exists $opts->{pointers}->{$pointer}) {
+        if ($ignore_missing_allocations) {
+          warn "WARNING: $.: Unknown allocation $pointer ignored on $cur_thread\n";
+          next;
+        } else {
+          die "$.: Unknown allocation $pointer on $cur_thread\n";
+        }
+      } elsif ($opts->{pointers}->{$pointer} != -1) {
+        print "  {FREE, $opts->{pointers}->{$pointer}, 0, 0},\n";
+        push @{$opts->{empty_slots}}, $opts->{pointers}->{$pointer};
+      }
+    } elsif (/^realloc\s+(\S+)\s+(\S+)\s+(\d+)/) {
+      my $new_pointer = $1;
+      my $old_pointer = $2;
+      my $size = $3;
+
+      if ($thread ne $cur_thread) {
+        if ($new_pointer ne $old_pointer) {
+          $opts->{pointers}->{$new_pointer} = -1;
+          delete $opts->{pointers}->{$old_pointer};
+        }
+      } elsif ($old_pointer eq "0x0") {
+        my $slot = GetSlot($opts);
+        # This was a realloc(nullptr, size) call.
+        print "  {REALLOC, $slot, $size, 0},\n";
+        $opts->{pointers}->{$new_pointer} = $slot;
+      } else {
+        if (!exists $opts->{pointers}->{$old_pointer}) {
+          if ($ignore_missing_allocations) {
+            warn "WARNING: $.: Unknown realloc allocation $old_pointer ignored on $cur_thread\n";
+            next;
+          } else {
+            die "Unknown realloc allocation $old_pointer on $cur_thread\n";
+          }
+        }
+
+        if ($opts->{pointers}->{$old_pointer} != -1) {
+          # Reuse the same slot, no need to get a new one.
+          my $slot = $opts->{pointers}->{$old_pointer};
+          printf("    {REALLOC, $slot, $size, %d},\n", $slot + 1);
+
+          # NOTE: It is possible that old pointer and new pointer are the
+          # same (a realloc returns the same pointer).
+          if ($new_pointer ne $old_pointer) {
+            $opts->{pointers}->{$new_pointer} = $slot;
+            delete $opts->{pointers}->{$old_pointer};
+          }
+        }
+      }
+    } elsif (!/^thread_done/) {
+      die "$.: Unknown line $_\n";
+    }
+  }
+
+  PrintFreeSlots($opts);
+
+  return $opts->{last_slot};
+}
+
+sub ProcessArgs($) {
+  my ($opts) = @_;
+
+  $opts->{print_definitions} = 0;
+  $opts->{ignore_missing_allocations} = 0;
+  $opts->{print_mainloop} = 0;
+  my @args = ();
+  while (scalar(@ARGV)) {
+    my $arg = pop(@ARGV);
+    if ($arg =~ /^-/) {
+      if ($arg eq "-d") {
+        $opts->{print_definitions} = 1;
+      } elsif ($arg eq "-i") {
+        $opts->{ignore_missing_allocations} = 1;
+      } elsif ($arg eq "-m") {
+        $opts->{print_mainloop} = 1;
+      } else {
+        print "Unknown option $arg\n";
+        PrintUsageAndExit();
+      }
+    } else {
+      unshift @args, $arg;
+    }
+  }
+
+  return @args;
+}
+
+my $opts = {};
+my @args = ProcessArgs($opts);
+if (scalar(@args) != 3) {
+  PrintUsageAndExit();
+}
+
+my $thread = $args[0];
+my $struct_name = $args[1];
+my $max_slot_name = $args[2];
+
+PrintHeader();
+if ($opts->{print_definitions}) {
+  PrintDefinitions();
+}
+if ($opts->{print_mainloop}) {
+  PrintMainloop();
+}
+
+print "static MallocEntry ${struct_name}[] = {\n";
+my $total_slots = PrintEntries($thread, $opts->{ignore_missing_allocations});
+print "};\n";
+print "static constexpr size_t ${max_slot_name} = $total_slots;\n";