/* * Copyright (c) 2017, 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 "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 const char *EXC_CNAME = "java/lang/Exception"; static jvmtiEnv *jvmti = NULL; 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; } 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; } jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(callbacks)); jvmtiCapabilities caps; memset(&caps, 0, sizeof(caps)); // Get line numbers, sample heap, and filename for the test. caps.can_get_line_numbers = 1; caps.can_sample_heap = 1; caps.can_get_source_file_name = 1; if (check_error((*jvmti)->AddCapabilities(jvmti, &caps), "Add capabilities\n")){ return JNI_ERR; } if (check_error((*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(jvmtiEventCallbacks)), " Set Event Callbacks")) { return JNI_ERR; } return JNI_OK; } // 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; } } typedef struct _ExpectedContentFrame { const char *name; const char *signature; const char *file_name; int line_number; } ExpectedContentFrame; static jboolean check_sample_content(JNIEnv *env, jvmtiStackTrace *trace, ExpectedContentFrame *expected, int expected_count, int print_out_comparisons) { int i; if (expected_count > trace->frame_count) { return FALSE; } for (i = 0; i < expected_count; i++) { // Get basic information out of the trace. int bci = trace->frames[i].location; jmethodID methodid = trace->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, "Comparing:\n"); fprintf(stderr, "\tNames: %s and %s\n", name, expected[i].name); fprintf(stderr, "\tSignatures: %s and %s\n", signature, expected[i].signature); fprintf(stderr, "\tFile name: %s and %s\n", file_name, expected[i].file_name); fprintf(stderr, "\tLines: %d and %d\n", line_number, expected[i].line_number); fprintf(stderr, "\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 jboolean compare_samples(JNIEnv* env, jvmtiStackTrace* traces, int trace_count, ExpectedContentFrame* expected_content, size_t size, int print_out_comparisons) { // We expect the code to record correctly the bci, retrieve the line // number, have the right method and the class name of the first frames. int i; for (i = 0; i < trace_count; i++) { jvmtiStackTrace *trace = traces + i; if (check_sample_content(env, trace, expected_content, size, print_out_comparisons)) { // At least one frame matched what we were looking for. return TRUE; } } return FALSE; } static jboolean check_samples(JNIEnv* env, ExpectedContentFrame* expected, size_t size, jvmtiError (*const get_traces)(jvmtiEnv*, jvmtiStackTraces*), int print_out_comparisons) { jvmtiStackTraces traces; jvmtiError error = get_traces(jvmti, &traces); if (error != JVMTI_ERROR_NONE) { return FALSE; } int result = compare_samples(env, traces.stack_traces, traces.trace_count, expected, size, print_out_comparisons); (*jvmti)->ReleaseTraces(jvmti, &traces); return result; } static jboolean frames_exist_live(JNIEnv* env, ExpectedContentFrame* expected, size_t size, int print_out_comparisons) { return check_samples(env, expected, size, (*jvmti)->GetLiveTraces, print_out_comparisons); } static jboolean frames_exist_recent(JNIEnv* env, ExpectedContentFrame* expected, size_t size, int print_out_comparisons) { return check_samples(env, expected, size, (*jvmti)->GetGarbageTraces, print_out_comparisons); } static jboolean frames_exist_frequent(JNIEnv* env, ExpectedContentFrame* expected, size_t size, int print_out_comparisons) { return check_samples(env, expected, size, (*jvmti)->GetFrequentGarbageTraces, print_out_comparisons); } // 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; } } static jboolean check_and(JNIEnv *env, jobjectArray frames, int live, int recent, int frequent, int print_out) { jobject loader = NULL; if (frames == NULL) { return FALSE; } // Start by transforming the frames into a C-friendly structure. jsize size = (*env)->GetArrayLength(env, frames); ExpectedContentFrame native_frames[size]; fill_native_frames(env, frames, native_frames, size); if (jvmti == NULL) { throw_exc(env, "JVMTI client was not properly loaded!\n"); return FALSE; } int result = TRUE; if (live) { result = frames_exist_live(env, native_frames, size, print_out); } if (recent) { result = result && frames_exist_recent(env, native_frames, size, print_out); } if (frequent) { result = result && frames_exist_frequent(env, native_frames, size, print_out); } return result; } static jboolean check_or(JNIEnv *env, jobjectArray frames, int live, int recent, int frequent, int print_out) { jobject loader = NULL; if (frames == NULL) { return FALSE; } // Start by transforming the frames into a C-friendly structure. jsize size = (*env)->GetArrayLength(env, frames); ExpectedContentFrame native_frames[size]; fill_native_frames(env, frames, native_frames, size); if (jvmti == NULL) { throw_exc(env, "JVMTI client was not properly loaded!\n"); return FALSE; } jboolean result = FALSE; if (live) { result = frames_exist_live(env, native_frames, size, print_out); } if (recent) { result = result || frames_exist_recent(env, native_frames, size, print_out); } if (frequent) { result = result || frames_exist_frequent(env, native_frames, size, print_out); } return result; } static jboolean checkAll(JNIEnv *env, jobjectArray frames, int print_out) { return check_and(env, frames, 1, 1, 1, print_out); } static jboolean checkNone(JNIEnv *env, jobjectArray frames, int print_out) { jobject loader = NULL; if (frames == NULL) { return FALSE; } // Start by transforming the frames into a C-friendly structure. jsize size = (*env)->GetArrayLength(env, frames); ExpectedContentFrame native_frames[size]; fill_native_frames(env, frames, native_frames, size); if (jvmti == NULL) { throw_exc(env, "JVMTI client was not properly loaded!\n"); return FALSE ; } if ((!frames_exist_live(env, native_frames, size, print_out)) && (!frames_exist_recent(env, native_frames, size, print_out)) && (!frames_exist_frequent(env, native_frames, size, print_out))) { return TRUE; } return FALSE; } JNIEXPORT void JNICALL Java_MyPackage_HeapMonitor_enableSampling(JNIEnv *env, jclass cls, int rate, int max_traces) { check_error((*jvmti)->StartHeapSampling(jvmti, rate, max_traces), "Start Heap Sampling"); } JNIEXPORT void JNICALL Java_MyPackage_HeapMonitor_disableSampling(JNIEnv *env, jclass cls) { check_error((*jvmti)->StopHeapSampling(jvmti), "Stop Heap Sampling"); } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitor_areSamplingStatisticsZero(JNIEnv *env, jclass cls) { jvmtiHeapSamplingStats stats; check_error((*jvmti)->GetHeapSamplingStats(jvmti, &stats), "Heap Sampling Statistics"); jvmtiHeapSamplingStats zero; memset(&zero, 0, sizeof(zero)); return memcmp(&stats, &zero, sizeof(zero)) == 0; } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitor_framesExistEverywhere(JNIEnv *env, jclass cls, jobjectArray frames) { // We want the frames in each part. return checkAll(env, frames, PRINT_OUT); } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitor_framesExistNowhere(JNIEnv *env, jclass cls, jobjectArray frames) { // We want the frames in none of the parts. return checkNone(env, frames, PRINT_OUT); } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitorRecentTest_framesNotInLiveOrRecent(JNIEnv *env, jclass cls, jobjectArray frames) { return !check_or(env, frames, TRUE, TRUE, FALSE, PRINT_OUT); } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitorRecentTest_framesExistInLiveAndRecent(JNIEnv *env, jclass cls, jobjectArray frames) { return check_and(env, frames, TRUE, TRUE, FALSE, PRINT_OUT); } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitorFrequentTest_framesExistInFrequent(JNIEnv *env, jclass cls, jobjectArray frames) { return check_and(env, frames, FALSE, FALSE, TRUE, PRINT_OUT); } 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)->StartHeapSampling(jvmti, 1<<19, MAX_TRACES), "Start Heap Sampling")) { return FALSE; } if (check_capability_error((*jvmti)->StopHeapSampling(jvmti), "Stop Heap Sampling")) { return FALSE; } if (check_capability_error((*jvmti)->ReleaseTraces(jvmti, NULL), "Release Traces")) { return FALSE; } if (check_capability_error((*jvmti)->GetHeapSamplingStats(jvmti, NULL), "Get Heap Sampling Stats")) { return FALSE; } if (check_capability_error((*jvmti)->GetGarbageTraces(jvmti, NULL), "Get Garbage Traces")) { return FALSE; } if (check_capability_error((*jvmti)->GetFrequentGarbageTraces(jvmti, NULL), "Get Frequent Garbage Traces")) { return FALSE; } if (check_capability_error((*jvmti)->GetLiveTraces(jvmti, NULL), "Get Live Traces")) { return FALSE; } return TRUE; } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitor_statsHaveExpectedNumberSamples(JNIEnv *env, jclass cls, int expected, int percent_error) { jvmtiHeapSamplingStats stats; check_error((*jvmti)->GetHeapSamplingStats(jvmti, &stats), "Heap Sampling Statistics"); fprintf(stderr, "Sample data count %ld, rate %ld, rate count %ld\n", stats.sample_count, stats.sample_rate_accumulation, stats.sample_rate_count); double diff_ratio = (stats.sample_count - expected); diff_ratio = (diff_ratio < 0) ? -diff_ratio : diff_ratio; diff_ratio /= expected; return diff_ratio * 100 < percent_error; } JNIEXPORT jdouble JNICALL Java_MyPackage_HeapMonitorStatRateTest_getAverageRate(JNIEnv *env, jclass cls) { jvmtiHeapSamplingStats stats; check_error((*jvmti)->GetHeapSamplingStats(jvmti, &stats), "Heap Sampling Statistics"); return ((double) stats.sample_rate_accumulation) / stats.sample_rate_count; } JNIEXPORT jdouble JNICALL Java_MyPackage_HeapMonitorStackDepthTest_getAverageStackDepth(JNIEnv *env, jclass cls) { jvmtiStackTraces traces; jvmtiError error = (*jvmti)->GetLiveTraces(jvmti, &traces);; if (error != JVMTI_ERROR_NONE) { return 0; } int trace_count = traces.trace_count; if (trace_count == 0) { return 0; } int i; jvmtiStackTrace* stack_traces = traces.stack_traces; double sum = 0; for (i = 0; i < trace_count; i++) { jvmtiStackTrace *stack_trace = stack_traces + i; sum += stack_trace->frame_count; } return sum / i; } typedef struct sThreadsFound { jint *threads; int num_threads; } ThreadsFound; static void find_threads_in_traces(jvmtiStackTraces* traces, ThreadsFound* thread_data) { int i; jvmtiStackTrace* stack_traces = traces->stack_traces; int trace_count = traces->trace_count; jint *threads = thread_data->threads; int num_threads = thread_data->num_threads; // We are looking for at last expected_num_threads different traces. for (i = 0; i < trace_count; i++) { jvmtiStackTrace *stack_trace = stack_traces + i; jlong thread_id = stack_trace->thread_id; // Check it is the right frame: only accept helper top framed traces. jmethodID methodid = stack_trace->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_id == threads[j]) { found = 1; break; } } if (!found) { threads[num_threads] = thread_id; num_threads++; } } thread_data->num_threads = num_threads; } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitorThreadTest_checkSamples(JNIEnv* env, jclass cls, jintArray threads) { jvmtiStackTraces traces; ThreadsFound thread_data; thread_data.threads = (*env)->GetIntArrayElements(env, threads, 0); thread_data.num_threads = 0; // Get live and garbage traces to ensure we capture all the threads that have // been sampled. if ((*jvmti)->GetLiveTraces(jvmti, &traces) != JVMTI_ERROR_NONE) { return FALSE; } find_threads_in_traces(&traces, &thread_data); if ((*jvmti)->ReleaseTraces(jvmti, &traces) != JVMTI_ERROR_NONE) { return FALSE; } if ((*jvmti)->GetGarbageTraces(jvmti, &traces) != JVMTI_ERROR_NONE) { return FALSE; } find_threads_in_traces(&traces, &thread_data); if ((*jvmti)->ReleaseTraces(jvmti, &traces) != JVMTI_ERROR_NONE) { return FALSE; } (*env)->ReleaseIntArrayElements(env, threads, thread_data.threads, 0); return TRUE; } JNIEXPORT void JNICALL Java_MyPackage_HeapMonitorCachedTest_getLiveTracesToForceGc(JNIEnv *env, jclass cls) { jvmtiStackTraces live_traces; jvmtiError error = (*jvmti)->GetLiveTraces(jvmti, &live_traces); if (error != JVMTI_ERROR_NONE) { return; } (*jvmti)->ReleaseTraces(jvmti, &live_traces); } static jboolean compare_traces(jvmtiStackTraces* traces, jvmtiStackTraces* other_traces, int print_out_comparisons) { int trace_count = traces->trace_count; if (trace_count != other_traces->trace_count) { return FALSE; } int i; for (i = 0; i < trace_count; i++) { jvmtiStackTrace* trace = traces->stack_traces + i; jvmtiStackTrace* other_trace = other_traces->stack_traces + i; if (trace->frame_count != other_trace->frame_count) { return FALSE; } if (trace->size != other_trace->size) { return FALSE; } if (trace->thread_id != other_trace->thread_id) { return FALSE; } jvmtiFrameInfo* frames = trace->frames; jvmtiFrameInfo* other_frames = other_trace->frames; if (memcmp(frames, other_frames, sizeof(*frames) * trace->frame_count)) { return FALSE; } } return TRUE; } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitorCachedTest_cachedAndLiveAreSame(JNIEnv *env, jclass cls) { // Get cached first, then get live (since live performs a GC). jvmtiStackTraces cached_traces; jvmtiError error = (*jvmti)->GetCachedTraces(jvmti, &cached_traces); if (error != JVMTI_ERROR_NONE) { return FALSE; } jvmtiStackTraces live_traces; error = (*jvmti)->GetLiveTraces(jvmti, &live_traces); if (error != JVMTI_ERROR_NONE) { return FALSE; } int result = compare_traces(&cached_traces, &live_traces, PRINT_OUT); (*jvmti)->ReleaseTraces(jvmti, &cached_traces); (*jvmti)->ReleaseTraces(jvmti, &live_traces); return result; } static long hash(long hash_code, long value) { return hash_code * 31 + value; } static long get_hash_code(jvmtiStackTraces* traces) { int trace_count = traces->trace_count; int hash_code = 17; int i; hash_code = hash(hash_code, trace_count); for (i = 0; i < trace_count; i++) { jvmtiStackTrace* trace = traces->stack_traces + i; hash_code = hash(hash_code, trace->frame_count); hash_code = hash(hash_code, trace->size); hash_code = hash(hash_code, trace->thread_id); int j; int frame_count = trace->frame_count; jvmtiFrameInfo* frames = trace->frames; hash_code = hash(hash_code, frame_count); for (j = 0; j < frame_count; j++) { hash_code = hash(hash_code, (long) frames[i].method); hash_code = hash(hash_code, frames[i].location); } } return TRUE; } JNIEXPORT jlong JNICALL Java_MyPackage_HeapMonitorCachedTest_getCachedHashCode(JNIEnv *env, jclass cls) { // Get cached first, then get live. jvmtiStackTraces cached_traces; jvmtiError error = (*jvmti)->GetCachedTraces(jvmti, &cached_traces); if (error != JVMTI_ERROR_NONE) { return 0; } long hash_code = get_hash_code(&cached_traces); (*jvmti)->ReleaseTraces(jvmti, &cached_traces); return hash_code; } JNIEXPORT jboolean JNICALL Java_MyPackage_HeapMonitorTest_framesAreNotLive(JNIEnv *env, jclass cls, jobjectArray frames) { return !check_and(env, frames, FALSE, FALSE, TRUE, PRINT_OUT); } #ifdef __cplusplus } #endif