diff --git a/jmh-fork/jmh-core/native-perf-map-agent/src/main/c/perf_map_agent.c b/jmh-fork/jmh-core/native-perf-map-agent/src/main/c/perf_map_agent.c index 38b6290..67bba3b 100644 --- a/jmh-fork/jmh-core/native-perf-map-agent/src/main/c/perf_map_agent.c +++ b/jmh-fork/jmh-core/native-perf-map-agent/src/main/c/perf_map_agent.c @@ -2,7 +2,13 @@ * perf_map_agent.c — JVMTI agent that writes /tmp/perf-.map * * Hooks CompiledMethodLoad events and writes perf map entries with - * absolute source file paths resolved via git root discovery. + * absolute source file paths resolved against the enclosing git repository. + * + * The git root is walked exactly once, on the first lookup, into a + * basename-keyed index; every later resolution is an in-memory bucket scan. + * CompiledMethodLoad is delivered on the JVM service thread while user code + * runs, so a filesystem walk per lookup would compete with the workload being + * measured. * * Usage: * java -agentpath:libperf_map_agent.so[=file=] ... @@ -74,7 +80,6 @@ static const char *cache_insert(const char *key, const char *value) { /* -------------------------------------------------------------------------- */ static char git_root[PATH_MAX]; -static pthread_once_t git_root_once = PTHREAD_ONCE_INIT; static void find_git_root(void) { char cwd[PATH_MAX]; @@ -113,9 +118,22 @@ static void find_git_root(void) { } /* -------------------------------------------------------------------------- */ -/* Recursive file search */ +/* Source file index */ /* -------------------------------------------------------------------------- */ +#define SOURCE_INDEX_BUCKETS \ + 4096 /* power of two, so mask instead of modulo \ + */ + +typedef struct index_entry { + char *path; /* absolute path of an indexed regular file */ + struct index_entry *next; +} index_entry_t; + +/* Chained buckets, keyed by the hash of the file's basename. */ +static index_entry_t *source_index[SOURCE_INDEX_BUCKETS]; +static pthread_once_t source_index_once = PTHREAD_ONCE_INIT; + static int should_skip_dir(const char *name) { if (name[0] == '.') { return 1; @@ -127,15 +145,48 @@ static int should_skip_dir(const char *name) { return 0; } +/* FNV-1a over the basename, folded into a bucket number. */ +static uint32_t hash_basename(const char *name) { + uint32_t h = 2166136261u; + for (; *name != '\0'; name++) { + h ^= (unsigned char)*name; + h *= 16777619u; + } + return h & (SOURCE_INDEX_BUCKETS - 1); +} + +static const char *path_basename(const char *path) { + const char *slash = strrchr(path, '/'); + return slash ? slash + 1 : path; +} + +static void index_insert(const char *abs_path) { + index_entry_t *e = malloc(sizeof(*e)); + if (!e) { + return; + } + e->path = strdup(abs_path); + if (!e->path) { + free(e); + return; + } + + uint32_t bucket = hash_basename(path_basename(abs_path)); + e->next = source_index[bucket]; + source_index[bucket] = e; +} + /* - * Search for a file whose path ends with `suffix` under `dir`. - * Returns 1 if found (result written to `out`), 0 otherwise. + * Index every regular file under `dir`, recursing into subdirectories that + * `should_skip_dir` accepts. + * + * `d_type` spares a stat() per entry; entry types the filesystem does not + * report, and symlinks (which are followed), fall back to stat(). */ -static int find_file_recursive(const char *dir, const char *suffix, char *out, - size_t out_size) { +static void index_dir(const char *dir) { DIR *d = opendir(dir); if (!d) { - return 0; + return; } struct dirent *ent; @@ -147,34 +198,62 @@ static int find_file_recursive(const char *dir, const char *suffix, char *out, char full_path[PATH_MAX]; snprintf(full_path, sizeof(full_path), "%s/%s", dir, ent->d_name); - struct stat st; - if (stat(full_path, &st) != 0) { - continue; - } - - if (S_ISDIR(st.st_mode)) { - if (should_skip_dir(ent->d_name)) { + int is_dir = (ent->d_type == DT_DIR); + int is_reg = (ent->d_type == DT_REG); + if (!is_dir && !is_reg) { + struct stat st; + if (stat(full_path, &st) != 0) { continue; } - if (find_file_recursive(full_path, suffix, out, out_size)) { - closedir(d); - return 1; - } - } else if (S_ISREG(st.st_mode)) { - size_t full_len = strlen(full_path); - size_t suffix_len = strlen(suffix); - if (full_len >= suffix_len && - strcmp(full_path + full_len - suffix_len, suffix) == 0) { - strncpy(out, full_path, out_size - 1); - out[out_size - 1] = '\0'; - closedir(d); - return 1; - } + is_dir = S_ISDIR(st.st_mode); + is_reg = S_ISREG(st.st_mode); + } + + if (is_dir && !should_skip_dir(ent->d_name)) { + index_dir(full_path); + } else if (is_reg) { + index_insert(full_path); } } closedir(d); - return 0; +} + +static void source_index_init(void) { + find_git_root(); + index_dir(git_root); +} + +static void index_free(void) { + for (size_t i = 0; i < SOURCE_INDEX_BUCKETS; i++) { + index_entry_t *e = source_index[i]; + while (e) { + index_entry_t *next = e->next; + free(e->path); + free(e); + e = next; + } + source_index[i] = NULL; + } +} + +/* + * Find an indexed file whose absolute path ends with "/". + * Returns NULL when no indexed file matches. + */ +static const char *index_find(const char *relative_path) { + char suffix[PATH_MAX]; + snprintf(suffix, sizeof(suffix), "/%s", relative_path); + size_t suffix_len = strlen(suffix); + + uint32_t bucket = hash_basename(path_basename(relative_path)); + for (index_entry_t *e = source_index[bucket]; e; e = e->next) { + size_t len = strlen(e->path); + if (len >= suffix_len && strcmp(e->path + len - suffix_len, suffix) == 0) { + return e->path; + } + } + return NULL; } /* -------------------------------------------------------------------------- */ @@ -183,7 +262,11 @@ static int find_file_recursive(const char *dir, const char *suffix, char *out, /* * Resolve a class-relative path (e.g. "com/example/Foo.java") to an absolute - * path on disk by searching from the git root. + * path on disk. + * + * The first call walks the git root once and builds the basename index; later + * calls only scan one bucket, and the per-relative-path cache below keeps even + * that off the repeated-lookup path while handing out stable pointers. * * Returns a pointer that remains valid for the process lifetime. * Empty string means "source file not found on disk". @@ -197,20 +280,11 @@ static const char *resolve_source_file(const char *relative_path) { return cached; } - pthread_once(&git_root_once, find_git_root); - - /* Build the suffix to search for: "/com/example/Foo.java" */ - char suffix[PATH_MAX]; - snprintf(suffix, sizeof(suffix), "/%s", relative_path); + pthread_once(&source_index_once, source_index_init); - char found[PATH_MAX]; - const char *result = NULL; - if (find_file_recursive(git_root, suffix, found, sizeof(found))) { - result = cache_insert(relative_path, found); - } else { - /* Source not on disk (JDK class, dependency, etc.) */ - result = cache_insert(relative_path, ""); - } + /* Empty value: source not on disk (JDK class, dependency, etc.) */ + const char *found = index_find(relative_path); + const char *result = cache_insert(relative_path, found ? found : ""); pthread_mutex_unlock(&cache_lock); return result ? result : ""; @@ -578,5 +652,6 @@ JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *jvm) { e = next; } source_cache = NULL; + index_free(); pthread_mutex_unlock(&cache_lock); }