/* * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include #include #include #include #include #include "jvmti.h" #ifdef __cplusplus extern "C" { #endif #ifndef JNI_ENV_ARG #ifdef __cplusplus #define JNI_ENV_ARG(x, y) y #define JNI_ENV_PTR(x) x #else #define JNI_ENV_ARG(x,y) x, y #define JNI_ENV_PTR(x) (*x) #endif #endif #define TRUE 1 #define FALSE 0 #define PRINT_OUT 1 #define MAX_TRACES 400 static jvmtiEnv *jvmti = NULL; typedef struct _ObjectTrace{ jweak object; size_t size; jvmtiFrameInfo* frames; size_t frame_count; jthread thread; } ObjectTrace; typedef struct _EventStorage { int compaction_required; int live_object_size; int live_object_count; ObjectTrace** live_objects; int garbage_history_size; int garbage_history_index; ObjectTrace** garbage_collected_objects; pthread_mutex_t mutex; } EventStorage; typedef struct _ExpectedContentFrame { const char *name; const char *signature; const char *file_name; int line_number; } ExpectedContentFrame; // Given a method and a location, this method gets the line number. static jint get_line_number(jvmtiEnv *jvmti, jmethodID method, jlocation location) { // Read the line number table. jvmtiLineNumberEntry *table_ptr = 0; jint line_number_table_entries; int jvmti_error = (*jvmti)->GetLineNumberTable(jvmti, method, &line_number_table_entries, &table_ptr); if (JVMTI_ERROR_NONE != jvmti_error) { return -1; } if (line_number_table_entries <= 0) { return -1; } if (line_number_table_entries == 1) { return table_ptr[0].line_number; } // Go through all the line numbers... jint last_location = table_ptr[0].start_location; int l; for (l = 1; l < line_number_table_entries; l++) { // ... and if you see one that is in the right place for your // location, you've found the line number! if ((location < table_ptr[l].start_location) && (location >= last_location)) { return table_ptr[l - 1].line_number; } last_location = table_ptr[l].start_location; } if (location >= last_location) { return table_ptr[line_number_table_entries - 1].line_number; } else { return -1; } } static jboolean check_sample_content(JNIEnv *env, ObjectTrace* trace, ExpectedContentFrame *expected, size_t expected_count, int print_out_comparisons) { if (expected_count > trace->frame_count) { return FALSE; } jvmtiFrameInfo* frames = trace->frames; size_t i; for (i = 0; i < expected_count; i++) { // Get basic information out of the trace. int bci = frames[i].location; jmethodID methodid = frames[i].method; char *name = NULL, *signature = NULL, *file_name = NULL; if (bci < 0) { return FALSE; } // Transform into usable information. int line_number = get_line_number(jvmti, methodid, bci); (*jvmti)->GetMethodName(jvmti, methodid, &name, &signature, 0); jclass declaring_class; if (JVMTI_ERROR_NONE != (*jvmti)->GetMethodDeclaringClass(jvmti, methodid, &declaring_class)) { return FALSE; } jvmtiError err = (*jvmti)->GetSourceFileName(jvmti, declaring_class, &file_name); if (err != JVMTI_ERROR_NONE) { return FALSE; } // Compare now, none should be NULL. if (name == NULL) { return FALSE; } if (file_name == NULL) { return FALSE; } if (signature == NULL) { return FALSE; } if (print_out_comparisons) { fprintf(stderr, "\tComparing:\n"); fprintf(stderr, "\t\tNames: %s and %s\n", name, expected[i].name); fprintf(stderr, "\t\tSignatures: %s and %s\n", signature, expected[i].signature); fprintf(stderr, "\t\tFile name: %s and %s\n", file_name, expected[i].file_name); fprintf(stderr, "\t\tLines: %d and %d\n", line_number, expected[i].line_number); fprintf(stderr, "\t\tResult is %d\n", (strcmp(name, expected[i].name) || strcmp(signature, expected[i].signature) || strcmp(file_name, expected[i].file_name) || line_number != expected[i].line_number)); } if (strcmp(name, expected[i].name) || strcmp(signature, expected[i].signature) || strcmp(file_name, expected[i].file_name) || line_number != expected[i].line_number) { return FALSE; } } return TRUE; } // Static native API for various tests. static void fill_native_frames(JNIEnv* env, jobjectArray frames, ExpectedContentFrame* native_frames, size_t size) { size_t i; for (i = 0; i < size; i++) { jobject obj = (*env)->GetObjectArrayElement(env, frames, i); jclass frame_class = (*env)->GetObjectClass(env, obj); jfieldID line_number_field_id = (*env)->GetFieldID(env, frame_class, "lineNumber", "I"); int line_number = (*env)->GetIntField(env, obj, line_number_field_id); jfieldID string_id = (*env)->GetFieldID(env, frame_class, "method", "Ljava/lang/String;"); jstring string_object = (jstring) (*env)->GetObjectField(env, obj, string_id); const char* method = (*env)->GetStringUTFChars(env, string_object, 0); string_id = (*env)->GetFieldID(env, frame_class, "fileName", "Ljava/lang/String;"); string_object = (jstring) (*env)->GetObjectField(env, obj, string_id); const char* file_name = (*env)->GetStringUTFChars(env, string_object, 0); string_id = (*env)->GetFieldID(env, frame_class, "signature", "Ljava/lang/String;"); string_object = (jstring) (*env)->GetObjectField(env, obj, string_id); const char* signature= (*env)->GetStringUTFChars(env, string_object, 0); native_frames[i].name = method; native_frames[i].file_name = file_name; native_frames[i].signature = signature; native_frames[i].line_number = line_number; } } // Internal storage system implementation. static EventStorage global_event_storage; static void event_storage_set_compaction_required(EventStorage* storage) { pthread_mutex_lock(&storage->mutex); storage->compaction_required = 1; pthread_mutex_unlock(&storage->mutex); } static int event_storage_get_compaction_required(EventStorage* storage) { pthread_mutex_lock(&storage->mutex); int result = storage->compaction_required; pthread_mutex_unlock(&storage->mutex); return result; } static void event_storage_set_garbage_history(EventStorage* storage, int value) { pthread_mutex_lock(&storage->mutex); global_event_storage.garbage_history_size = value; free(global_event_storage.garbage_collected_objects); size_t size = sizeof(*global_event_storage.garbage_collected_objects) * value; global_event_storage.garbage_collected_objects = malloc(size); memset(global_event_storage.garbage_collected_objects, 0, size); pthread_mutex_unlock(&storage->mutex); } // No mutex here, it is handled by the caller. static void event_storage_add_garbage_collected_object(EventStorage* storage, ObjectTrace* object) { int idx = storage->garbage_history_index; free(storage->garbage_collected_objects[idx]); storage->garbage_collected_objects[idx] = object; storage->garbage_history_index = (idx + 1) % storage->garbage_history_size; } static int event_storage_get_count(EventStorage* storage) { pthread_mutex_lock(&storage->mutex); int result = storage->live_object_count; pthread_mutex_unlock(&storage->mutex); return result; } static double event_storage_get_average_rate(EventStorage* storage) { double accumulation = 0; pthread_mutex_lock(&storage->mutex); int max_size = storage->live_object_count; for (int i = 0; i < max_size; i++) { accumulation += storage->live_objects[i]->size; } pthread_mutex_unlock(&storage->mutex); return accumulation / max_size; } static jboolean event_storage_contains(JNIEnv* env, EventStorage* storage, ExpectedContentFrame* frames, size_t size) { pthread_mutex_lock(&storage->mutex); fprintf(stderr, "Checking storage count %d\n", storage->live_object_count); int i; for (i = 0; i < storage->live_object_count; i++) { ObjectTrace* trace = storage->live_objects[i]; if (check_sample_content(env, trace, frames, size, PRINT_OUT)) { pthread_mutex_unlock(&storage->mutex); return TRUE; } } pthread_mutex_unlock(&storage->mutex); return FALSE; } static jboolean event_storage_garbage_contains(JNIEnv* env, EventStorage* storage, ExpectedContentFrame* frames, size_t size) { pthread_mutex_lock(&storage->mutex); fprintf(stderr, "Checking garbage storage count %d\n", storage->garbage_history_size); int i; for (i = 0; i < storage->garbage_history_size; i++) { ObjectTrace* trace = storage->garbage_collected_objects[i]; if (trace == NULL) { continue; } if (check_sample_content(env, trace, frames, size, PRINT_OUT)) { pthread_mutex_unlock(&storage->mutex); return TRUE; } } pthread_mutex_unlock(&storage->mutex); return FALSE; } // No mutex here, handled by the caller. static void event_storage_augment_storage(EventStorage* storage) { int new_max = (storage->live_object_size * 2) + 1; ObjectTrace** new_objects = malloc(new_max * sizeof(*new_objects)); int current_count = storage->live_object_count; memcpy(new_objects, storage->live_objects, current_count * sizeof(*new_objects)); free(storage->live_objects); storage->live_objects = new_objects; storage->live_object_size = new_max; } static void event_storage_add(EventStorage* storage, JNIEnv* jni, jthread thread, jobject object, jclass klass, jlong size) { jvmtiFrameInfo frames[64]; jint count; jvmtiError err; err = (*jvmti)->GetStackTrace(jvmti, thread, 0, 64, frames, &count); if (err == JVMTI_ERROR_NONE && count >= 1) { pthread_mutex_lock(&storage->mutex); if (storage->live_object_count >= storage->live_object_size) { event_storage_augment_storage(storage); } assert(storage->live_object_count < storage->live_object_size); jvmtiFrameInfo* allocated_frames = malloc(count * sizeof(*allocated_frames)); memcpy(allocated_frames, frames, count * sizeof(*allocated_frames)); ObjectTrace* live_object = malloc(sizeof(*live_object)); live_object->frames = allocated_frames; live_object->frame_count = count; live_object->size = size; live_object->thread = thread; live_object->object = (*jni)->NewWeakGlobalRef(jni, object); storage->live_objects[storage->live_object_count] = live_object; storage->live_object_count++; pthread_mutex_unlock(&storage->mutex); } } static void event_storage_compact(EventStorage* storage, JNIEnv* jni) { pthread_mutex_lock(&storage->mutex); storage->compaction_required = 0; int max = storage->live_object_count; int i, dest; ObjectTrace** live_objects = storage->live_objects; for (i = 0, dest = 0; i < max; i++) { ObjectTrace* live_object = live_objects[i]; jweak object = live_object->object; if (!(*jni)->IsSameObject(jni, object, NULL)) { if (dest != i) { live_objects[dest] = live_object; dest++; } } else { event_storage_add_garbage_collected_object(storage, live_object); } } storage->live_object_count = dest; pthread_mutex_unlock(&storage->mutex); } static void event_storage_free_objects(ObjectTrace** array, int max) { int i; for (i = 0; i < max; i++) { free(array[i]), array[i] = NULL; } } static void event_storage_reset(EventStorage* storage) { pthread_mutex_lock(&storage->mutex); // Reset everything except the mutex and the garbage collection. event_storage_free_objects(storage->live_objects, storage->live_object_count); storage->live_object_size = 0; storage->live_object_count = 0; free(storage->live_objects), storage->live_objects = NULL; event_storage_free_objects(storage->garbage_collected_objects, storage->garbage_history_size); storage->compaction_required = 0; storage->garbage_history_index = 0; pthread_mutex_unlock(&storage->mutex); } // Start of the JVMTI agent code. static const char *EXC_CNAME = "java/lang/Exception"; static int check_error(jvmtiError err, const char *s) { if (err != JVMTI_ERROR_NONE) { printf(" ## %s error: %d\n", s, err); return 1; } return 0; } static int check_capability_error(jvmtiError err, const char *s) { if (err != JVMTI_ERROR_NONE) { if (err == JVMTI_ERROR_MUST_POSSESS_CAPABILITY) { return 0; } printf(" ## %s error: %d\n", s, err); return 1; } return 1; } static jint throw_exc(JNIEnv *env, char *msg) { jclass exc_class = JNI_ENV_PTR(env)->FindClass(JNI_ENV_ARG(env, EXC_CNAME)); if (exc_class == NULL) { printf("throw_exc: Error in FindClass(env, %s)\n", EXC_CNAME); return -1; } return JNI_ENV_PTR(env)->ThrowNew(JNI_ENV_ARG(env, exc_class), msg); } static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved); JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { return Agent_Initialize(jvm, options, reserved); } JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *jvm, char *options, void *reserved) { return Agent_Initialize(jvm, options, reserved); } JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { return JNI_VERSION_1_8; } JNIEXPORT void JNICALL SampledObjectAlloc(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jobject object, jclass object_klass, jlong size) { if (event_storage_get_compaction_required(&global_event_storage)) { event_storage_compact(&global_event_storage, jni_env); } event_storage_add(&global_event_storage, jni_env, thread, object, object_klass, size); } JNIEXPORT void JNICALL GarbageCollectionFinish(jvmtiEnv *jvmti_env) { event_storage_set_compaction_required(&global_event_storage); } static int enable_notifications() { if (check_error((*jvmti)->SetEventNotificationMode( jvmti, JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, NULL), "Set event notifications")) { return 1; } return check_error((*jvmti)->SetEventNotificationMode( jvmti, JVMTI_ENABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL), "Set event notifications"); } static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { jint res; res = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (void **) &jvmti), JVMTI_VERSION_9); if (res != JNI_OK || jvmti == NULL) { printf(" Error: wrong result of a valid call to GetEnv!\n"); return JNI_ERR; } pthread_mutex_init(&global_event_storage.mutex, 0); event_storage_set_garbage_history(&global_event_storage, 200); jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(callbacks)); callbacks.SampledObjectAlloc = &SampledObjectAlloc; callbacks.GarbageCollectionFinish = &GarbageCollectionFinish; jvmtiCapabilities caps; memset(&caps, 0, sizeof(caps)); // Get line numbers, sample heap, sample events, and filename for the test. caps.can_get_line_numbers = 1; caps.can_sample_heap = 1; caps.can_get_source_file_name = 1; caps.can_generate_garbage_collection_events = 1; if (check_error((*jvmti)->AddCapabilities(jvmti, &caps), "Add capabilities")){ return JNI_ERR; } if (enable_notifications()) { return JNI_ERR; } if (check_error((*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(jvmtiEventCallbacks)), " Set Event Callbacks")) { return JNI_ERR; } return JNI_OK; } JNIEXPORT void JNICALL Java_MyPackage_HeapMonitor_setSamplingRate(JNIEnv* env, jclass cls, jint value) { (*jvmti)->SetHeapSamplingRate(jvmti, value); } JNIEXPORT void JNICALL Java_MyPackage_HeapMonitor_setGarbageHistory(JNIEnv* env, jclass cls, jint value) { event_storage_set_garbage_history(&global_event_storage, value); } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitor_eventStorageIsEmpty(JNIEnv* env, jclass cls) { return event_storage_get_count(&global_event_storage) == 0; } JNIEXPORT jint JNICALL Java_MyPackage_HeapMonitor_getEventStorageElementCount(JNIEnv* env, jclass cls) { return event_storage_get_count(&global_event_storage); } JNIEXPORT void JNICALL Java_MyPackage_HeapMonitor_enableSamplingEvents(JNIEnv* env, jclass cls) { enable_notifications(); } JNIEXPORT void JNICALL Java_MyPackage_HeapMonitor_disableSamplingEvents(JNIEnv* env, jclass cls) { check_error((*jvmti)->SetEventNotificationMode( jvmti, JVMTI_DISABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL), "Set event notifications"); check_error((*jvmti)->SetEventNotificationMode( jvmti, JVMTI_DISABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, NULL), "Garbage Collection Finish"); } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitor_obtainedEvents(JNIEnv* env, jclass cls, jobjectArray frames) { jsize size = (*env)->GetArrayLength(env, frames); ExpectedContentFrame native_frames[size]; fill_native_frames(env, frames, native_frames, size); return event_storage_contains(env, &global_event_storage, native_frames, size); } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitor_garbageContains(JNIEnv* env, jclass cls, jobjectArray frames) { jsize size = (*env)->GetArrayLength(env, frames); ExpectedContentFrame native_frames[size]; fill_native_frames(env, frames, native_frames, size); return event_storage_garbage_contains(env, &global_event_storage, native_frames, size); } JNIEXPORT void JNICALL Java_MyPackage_HeapMonitor_forceGarbageCollection(JNIEnv* env, jclass cls) { check_error((*jvmti)->ForceGarbageCollection(jvmti), "Forced Garbage Collection"); } JNIEXPORT void JNICALL Java_MyPackage_HeapMonitor_resetEventStorage(JNIEnv* env, jclass cls) { return event_storage_reset(&global_event_storage); } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitorNoCapabilityTest_allSamplingMethodsFail(JNIEnv *env, jclass cls) { jvmtiCapabilities caps; memset(&caps, 0, sizeof(caps)); caps.can_sample_heap= 1; if (check_error((*jvmti)->RelinquishCapabilities(jvmti, &caps), "Add capabilities\n")){ return FALSE; } if (check_capability_error((*jvmti)->SetHeapSamplingRate(jvmti, 1<<19), "Set Heap Sampling Rate")) { return FALSE; } return TRUE; } JNIEXPORT jdouble JNICALL Java_MyPackage_HeapMonitorStatRateTest_getAverageRate(JNIEnv *env, jclass cls) { return event_storage_get_average_rate(&global_event_storage); } typedef struct sThreadsFound { jthread* threads; int num_threads; } ThreadsFound; static void find_threads_in_array(ThreadsFound* thread_data, ObjectTrace** array, int array_size) { int i; jthread* threads = thread_data->threads; int num_threads = thread_data->num_threads; for (i = 0; i < array_size; i++) { ObjectTrace* object = array[i]; if (object == NULL) { continue; } // Check it is the right frame: only accept helper top framed traces. if (object->frame_count == 0) { continue; } jvmtiFrameInfo* frames = object->frames; jthread thread = object->thread; jmethodID methodid = frames[0].method; char *name = NULL, *signature = NULL, *file_name = NULL; (*jvmti)->GetMethodName(jvmti, methodid, &name, &signature, 0); if (strcmp(name, "helper")) { continue; } // Really not efficient look-up but it's for a test... int found = 0; int j; for (j = 0; j < num_threads; j++) { if (thread == threads[j]) { found = 1; break; } } if (!found) { threads[num_threads] = thread; num_threads++; } } thread_data->num_threads = num_threads; } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitorThreadTest_checkSamples(JNIEnv* env, jclass cls, jint num_threads) { pthread_mutex_lock(&global_event_storage.mutex); jint trace_counter; ThreadsFound thread_data; thread_data.num_threads = 0; thread_data.threads = malloc(sizeof(jthread) * num_threads); memset(thread_data.threads, 0, sizeof(jthread) * num_threads); find_threads_in_array(&thread_data, global_event_storage.live_objects, global_event_storage.live_object_count); find_threads_in_array(&thread_data, global_event_storage.garbage_collected_objects, global_event_storage.garbage_history_size); free(thread_data.threads); pthread_mutex_unlock(&global_event_storage.mutex); return thread_data.num_threads == num_threads; } #ifdef __cplusplus } #endif