Add support for tracking jvmti allocations

Adds a jvmti extension for getting the amount of memory allocated and
by jvmtiEnvs and currently live.

Bug: 62065509
Test: ./test.py --host -j40
Change-Id: I5d05b171260d91f9c415f7a5cb40cc01b48d7d07
diff --git a/test/1900-track-alloc/alloc.cc b/test/1900-track-alloc/alloc.cc
new file mode 100644
index 0000000..db5617c
--- /dev/null
+++ b/test/1900-track-alloc/alloc.cc
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2013 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 "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1900TrackAlloc {
+
+typedef jvmtiError (*GetGlobalState)(jvmtiEnv* env, jlong* allocated);
+
+struct AllocTrackingData {
+  GetGlobalState get_global_state;
+};
+
+template <typename T>
+static void Dealloc(T* t) {
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename ...Rest>
+static void Dealloc(T* t, Rest... rs) {
+  Dealloc(t);
+  Dealloc(rs...);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1900_doDeallocate(JNIEnv* env,
+                                                                 jclass,
+                                                                 jlong jvmti_env_ptr,
+                                                                 jlong ptr) {
+  JvmtiErrorToException(env,
+                        reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr),
+                        reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->Deallocate(
+                            reinterpret_cast<unsigned char*>(static_cast<intptr_t>(ptr))));
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_Test1900_doAllocate(JNIEnv* env,
+                                                                jclass,
+                                                                jlong jvmti_env_ptr,
+                                                                jlong size) {
+  unsigned char* res = nullptr;
+  JvmtiErrorToException(env,
+                        reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr),
+                        reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->Allocate(size, &res));
+  return static_cast<jlong>(reinterpret_cast<intptr_t>(res));
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_Test1900_getAmountAllocated(JNIEnv* env, jclass) {
+  AllocTrackingData* data = nullptr;
+  if (JvmtiErrorToException(
+      env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return -1;
+  }
+  if (data == nullptr || data->get_global_state == nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Alloc tracking data not initialized.");
+    return -1;
+  }
+  jlong allocated = -1;
+  JvmtiErrorToException(env, jvmti_env, data->get_global_state(jvmti_env, &allocated));
+  return allocated;
+}
+
+static void DeallocParams(jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(params[i].name);
+  }
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_Test1900_getDefaultJvmtiEnv(JNIEnv*, jclass) {
+  return static_cast<jlong>(reinterpret_cast<intptr_t>(jvmti_env));
+}
+
+extern "C" JNIEXPORT void Java_art_Test1900_destroyJvmtiEnv(JNIEnv* env,
+                                                            jclass,
+                                                            jlong jvmti_env_ptr) {
+  JvmtiErrorToException(env,
+                        jvmti_env,
+                        reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->DisposeEnvironment());
+}
+
+extern "C" JNIEXPORT jlong Java_art_Test1900_newJvmtiEnv(JNIEnv* env, jclass) {
+  JavaVM* vm = nullptr;
+  if (env->GetJavaVM(&vm) != 0) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Unable to get JavaVM");
+    return -1;
+  }
+  jvmtiEnv* new_env = nullptr;
+  if (vm->GetEnv(reinterpret_cast<void**>(&new_env), JVMTI_VERSION_1_0) != 0) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Unable to create new jvmtiEnv");
+    return -1;
+  }
+  return static_cast<jlong>(reinterpret_cast<intptr_t>(new_env));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1900_initializeTest(JNIEnv* env, jclass) {
+  void* old_data = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) {
+    return;
+  } else if (old_data != nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
+    return;
+  }
+  AllocTrackingData* data = nullptr;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->Allocate(sizeof(AllocTrackingData),
+                                                reinterpret_cast<unsigned char**>(&data)))) {
+    return;
+  }
+  memset(data, 0, sizeof(AllocTrackingData));
+  // Get the extensions.
+  jint n_ext = 0;
+  jvmtiExtensionFunctionInfo* infos = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) {
+    return;
+  }
+  for (jint i = 0; i < n_ext; i++) {
+    jvmtiExtensionFunctionInfo* cur_info = &infos[i];
+    if (strcmp("com.android.art.alloc.get_global_jvmti_allocation_state", cur_info->id) == 0) {
+      data->get_global_state = reinterpret_cast<GetGlobalState>(cur_info->func);
+    }
+    // Cleanup the cur_info
+    DeallocParams(cur_info->params, cur_info->param_count);
+    Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors);
+  }
+  // Cleanup the array.
+  Dealloc(infos);
+  if (data->get_global_state == nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Unable to find memory tracking extensions.");
+    return;
+  }
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data));
+  return;
+}
+
+}  // namespace Test1900TrackAlloc
+}  // namespace art
diff --git a/test/1900-track-alloc/expected.txt b/test/1900-track-alloc/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/1900-track-alloc/expected.txt
diff --git a/test/1900-track-alloc/info.txt b/test/1900-track-alloc/info.txt
new file mode 100644
index 0000000..e1d35ae
--- /dev/null
+++ b/test/1900-track-alloc/info.txt
@@ -0,0 +1 @@
+Tests the jvmti-extension to get allocated memory snapshot.
diff --git a/test/1900-track-alloc/run b/test/1900-track-alloc/run
new file mode 100755
index 0000000..c6e62ae
--- /dev/null
+++ b/test/1900-track-alloc/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-run "$@" --jvmti
diff --git a/test/1900-track-alloc/src/Main.java b/test/1900-track-alloc/src/Main.java
new file mode 100644
index 0000000..0dab4ef
--- /dev/null
+++ b/test/1900-track-alloc/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1900.run();
+  }
+}
diff --git a/test/1900-track-alloc/src/art/Test1900.java b/test/1900-track-alloc/src/art/Test1900.java
new file mode 100644
index 0000000..717999b
--- /dev/null
+++ b/test/1900-track-alloc/src/art/Test1900.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package art;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+
+public class Test1900 {
+  public static void checkLE(long exp, long o) {
+    if (exp > o) {
+      throw new Error("Expected: " + exp + " Got: " + o);
+    }
+  }
+  public static void checkEq(long exp, long o) {
+    if (exp != o) {
+      throw new Error("Expected: " + exp + " Got: " + o);
+    }
+  }
+
+  public static void runConcurrent(Runnable... rs) throws Exception {
+    final CountDownLatch latch = new CountDownLatch(rs.length);
+    Thread[] thrs = new Thread[rs.length];
+    for (int i = 0; i < rs.length; i++) {
+      final Runnable r = rs[i];
+      thrs[i] = new Thread(() -> {
+        latch.countDown();
+        r.run();
+      });
+      thrs[i].start();
+    }
+    for (Thread thr : thrs) {
+      thr.join();
+    }
+  }
+  static class Holder {
+    public long val;
+  }
+
+  public static void run() throws Exception {
+    initializeTest();
+    // Get the overhead for the native part of this test.
+    final long base_state = getAmountAllocated();
+
+    // Basic alloc-dealloc
+    checkEq(base_state + 0, getAmountAllocated());
+    long abc = doAllocate(10);
+    checkLE(base_state + 10, getAmountAllocated());
+    long def = doAllocate(10);
+    checkLE(base_state + 20, getAmountAllocated());
+    doDeallocate(abc);
+    checkLE(base_state + 10, getAmountAllocated());
+
+    doDeallocate(def);
+
+    checkEq(base_state + 0, getAmountAllocated());
+
+    // Try doing it concurrently.
+    Runnable add10 = () -> { long x = doAllocate(10); doDeallocate(x); };
+    Runnable[] rs = new Runnable[100];
+    Arrays.fill(rs, add10);
+    runConcurrent(rs);
+    checkEq(base_state + 0, getAmountAllocated());
+
+    // Try doing it concurrently with different threads to allocate and deallocate.
+    final Semaphore sem = new Semaphore(0);
+    final Holder h = new Holder();
+    runConcurrent(
+        () -> {
+          try {
+            h.val = doAllocate(100);
+            checkLE(base_state + 100, getAmountAllocated());
+            sem.release();
+          } catch (Exception e) { throw new Error("exception!", e); }
+        },
+        () -> {
+          try {
+            sem.acquire();
+            long after_acq = getAmountAllocated();
+            doDeallocate(h.val);
+            checkLE(base_state + 100, after_acq);
+          } catch (Exception e) { throw new Error("exception!", e); }
+        }
+    );
+    checkEq(base_state + 0, getAmountAllocated());
+
+    // Try doing it with multiple jvmtienvs.
+    long env1 = newJvmtiEnv();
+    long env2 = newJvmtiEnv();
+
+    final long new_base_state = getAmountAllocated();
+    // new jvmtienvs shouldn't save us memory.
+    checkLE(base_state, new_base_state);
+    // Make sure we track both.
+    abc = doAllocate(env1, 10);
+    checkLE(new_base_state + 10, getAmountAllocated());
+    def = doAllocate(env2, 10);
+    checkLE(new_base_state + 20, getAmountAllocated());
+    doDeallocate(env1, abc);
+    checkLE(new_base_state + 10, getAmountAllocated());
+
+    doDeallocate(env2, def);
+
+    checkEq(new_base_state + 0, getAmountAllocated());
+
+    destroyJvmtiEnv(env1);
+    destroyJvmtiEnv(env2);
+
+    // Back to normal after getting rid of the envs.
+    checkEq(base_state + 0, getAmountAllocated());
+  }
+
+  private static native long doAllocate(long jvmtienv, long size);
+  private static long doAllocate(long size) {
+    return doAllocate(getDefaultJvmtiEnv(), size);
+  }
+
+  private static native void doDeallocate(long jvmtienv, long ptr);
+  private static void doDeallocate(long size) {
+    doDeallocate(getDefaultJvmtiEnv(), size);
+  }
+
+  private static native long getDefaultJvmtiEnv();
+  private static native long newJvmtiEnv();
+  private static native void destroyJvmtiEnv(long jvmtienv);
+  private static native long getAmountAllocated();
+  private static native void initializeTest();
+}
diff --git a/test/Android.bp b/test/Android.bp
index e5d2ca0..bf3df59 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -286,6 +286,7 @@
         "992-source-data/source_file.cc",
         "993-breakpoints/breakpoints.cc",
         "996-breakpoint-obsolete/obsolete_breakpoints.cc",
+        "1900-track-alloc/alloc.cc",
         "1901-get-bytecodes/bytecodes.cc",
     ],
     shared_libs: [
diff --git a/test/knownfailures.json b/test/knownfailures.json
index d3d92c6..3ee4299 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -538,7 +538,8 @@
             "595-profile-saving",
             "900-hello-plugin",
             "909-attach-agent",
-            "981-dedup-original-dex"
+            "981-dedup-original-dex",
+            "1900-track-alloc"
         ],
         "description": ["Tests that require exact knowledge of the number of plugins and agents."],
         "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress | step-stress"