diff --git a/README.md b/README.md index 19ca7ff..8d11c6e 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,9 @@ You can add a comma separated list of options to `perf-java` (or the `AttachOnce - `unfold`: Create extra entries for every codeblock inside a method that was inlined from elsewhere (named <inlined_method> in <root_method>). Be aware of the effects of 'skid' in relation with unfolding. - See the section below. + See the section below. Also, see the below section about inaccurate inlining information. + - `unfoldall`: Similar to `unfold` but will include the complete inlined stack at a code location in the form + `root_method->inlined method 1->inlined method 2->...->inlined method on top`. - `unfoldsimple`: similar to `unfold`, however, the extra entries do not include the " in <root_method>" part - `msig`: include full method signature in the name string - `dottedclass`: convert class signature (`Ljava/lang/Class;`) to the usual class names with segments separated by dots @@ -94,6 +96,17 @@ tools that operate on the symbol level like the standard views of `perf report`, So, while it is tempting to enable unfolded entries for the perceived extra resolution, this extra information is sometimes just noise which will not only clutter the overall view but may also be misleading or wrong. +### Inaccurate mappings using the `unfold*` options + +Hotspot does not retain line number and other debug information for inlined code at other places than safepoints. This +makes sense because you don't usually observe code running between safepoints from the JVM's perspective. This is different +when observing a process from the outside like with `perf`. For observed code locations outside of safepoints, the JVM will +not report any inlining information and perf-map-agent will assign those areas to the host method of the inlining. + +For more fidelity, Hotspot can be instructed to include debug information for non-safepoints as well. Use +`-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints` when running the target process. Note, however, that this will +produce a lot more information with the generated `perf-.map` file potentially growing to MBs of size. + ### Agent Library Unloading Unloading or reloading of a changed agent library is not supported by the JVM (but re-attaching is). Therefore, if you make changes to the diff --git a/src/c/perf-map-agent.c b/src/c/perf-map-agent.c index 9138210..337519f 100644 --- a/src/c/perf-map-agent.c +++ b/src/c/perf-map-agent.c @@ -32,12 +32,15 @@ #include "perf-map-file.h" #define STRING_BUFFER_SIZE 2000 +#define BIG_STRING_BUFFER_SIZE 20000 bool unfold_inlined_methods = false; bool unfold_simple = false; +bool unfold_all = false; bool print_method_signatures = false; bool print_source_loc = false; bool clean_class_names = false; +bool debug_dump_unfold_entries = false; FILE *method_file = NULL; void open_map_file() { @@ -140,7 +143,7 @@ void generate_unfolded_entry(jvmtiEnv *jvmti, jmethodID method, char *buffer, si /* Generates and writes a single entry for a given inlined method. */ void write_unfolded_entry( jvmtiEnv *jvmti, - jmethodID cur_method, + PCStackInfo *info, jmethodID root_method, const char *root_name, const void *start_addr, @@ -149,15 +152,60 @@ void write_unfolded_entry( char inlined_name[STRING_BUFFER_SIZE * 2 + 4]; const char *entry_p; - if (cur_method != root_method) { - generate_unfolded_entry(jvmti, cur_method, inlined_name, sizeof(inlined_name), root_name); - entry_p = inlined_name; - } else - entry_p = root_name; + if (unfold_all) { + char full_name[BIG_STRING_BUFFER_SIZE]; + full_name[0] = '\0'; + int i; + for (i = info->numstackframes - 1; i >= 0; i--) { + //printf("At %d method is %d len %d remaining %d\n", i, info->methods[i], strlen(full_name), sizeof(full_name) - 1 - strlen(full_name)); + sig_string(jvmti, info->methods[i], inlined_name, sizeof(inlined_name)); + strncat(full_name, inlined_name, sizeof(full_name) - 1 - strlen(full_name)); // TODO optimize + if (i != 0) strncat(full_name, "->", sizeof(full_name)); + } + entry_p = full_name; + } else { + jmethodID cur_method = info->methods[0]; // top of stack + if (cur_method != root_method) { + generate_unfolded_entry(jvmti, cur_method, inlined_name, sizeof(inlined_name), root_name); + entry_p = inlined_name; + } else + entry_p = root_name; + } perf_map_write_entry(method_file, start_addr, end_addr - start_addr, entry_p); } +void dump_entries( + jvmtiEnv *jvmti, + jmethodID root_method, + jint code_size, + const void* code_addr, + jint map_length, + const jvmtiAddrLocationMap* map, + const void* compile_info) { + const jvmtiCompiledMethodLoadRecordHeader *header = compile_info; + char root_name[STRING_BUFFER_SIZE]; + sig_string(jvmti, root_method, root_name, sizeof(root_name)); + printf("At %s size %x from %p to %p", root_name, code_size, code_addr, code_addr + code_size); + if (header->kind == JVMTI_CMLR_INLINE_INFO) { + const jvmtiCompiledMethodLoadInlineRecord *record = (jvmtiCompiledMethodLoadInlineRecord *) header; + printf(" with %d entries\n", record->numpcs); + + int i; + for (i = 0; i < record->numpcs; i++) { + PCStackInfo *info = &record->pcinfo[i]; + printf(" %p has %d stack entries\n", info->pc, info->numstackframes); + + int j; + for (j = 0; j < info->numstackframes; j++) { + char buf[2000]; + sig_string(jvmti, info->methods[j], buf, sizeof(buf)); + printf(" %s\n", buf); + } + } + } else printf(" with no inline info\n"); +} + void generate_unfolded_entries( jvmtiEnv *jvmti, jmethodID root_method, @@ -170,6 +218,10 @@ void generate_unfolded_entries( char root_name[STRING_BUFFER_SIZE]; sig_string(jvmti, root_method, root_name, sizeof(root_name)); + + if (debug_dump_unfold_entries) + dump_entries(jvmti, root_method, code_size, code_addr, map_length, map, compile_info); + if (header->kind == JVMTI_CMLR_INLINE_INFO) { const jvmtiCompiledMethodLoadInlineRecord *record = (jvmtiCompiledMethodLoadInlineRecord *) header; @@ -185,10 +237,13 @@ void generate_unfolded_entries( // as long as the top method remains the same we delay recording if (cur_method != top_method) { - // top method has changed, record the range for current method void *end_addr = info->pc; - write_unfolded_entry(jvmti, cur_method, root_method, root_name, start_addr, end_addr); + + if (i > 0) + write_unfolded_entry(jvmti, &record->pcinfo[i - 1], root_method, root_name, start_addr, end_addr); + else + generate_single_entry(jvmti, root_method, start_addr, end_addr - start_addr); start_addr = info->pc; cur_method = top_method; @@ -199,7 +254,11 @@ void generate_unfolded_entries( if (start_addr != code_addr + code_size) { // end_addr is end of this complete code blob const void *end_addr = code_addr + code_size; - write_unfolded_entry(jvmti, cur_method, root_method, root_name, start_addr, end_addr); + + if (i > 0) + write_unfolded_entry(jvmti, &record->pcinfo[i - 1], root_method, root_name, start_addr, end_addr); + else + generate_single_entry(jvmti, root_method, start_addr, end_addr - start_addr); } } else generate_single_entry(jvmti, root_method, code_addr, code_size); @@ -265,10 +324,12 @@ Agent_OnAttach(JavaVM *vm, char *options, void *reserved) { open_map_file(); unfold_simple = strstr(options, "unfoldsimple") != NULL; - unfold_inlined_methods = strstr(options, "unfold") != NULL || unfold_simple; + unfold_all = strstr(options, "unfoldall") != NULL; + unfold_inlined_methods = strstr(options, "unfold") != NULL || unfold_simple || unfold_all; print_method_signatures = strstr(options, "msig") != NULL; print_source_loc = strstr(options, "sourcepos") != NULL; clean_class_names = strstr(options, "dottedclass") != NULL; + debug_dump_unfold_entries = strstr(options, "debug_dump_unfold_entries") != NULL; jvmtiEnv *jvmti; (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1);