From 82bf11ef908f7b1db478cd1d4bce307f358d4709 Mon Sep 17 00:00:00 2001 From: coleramos425 Date: Mon, 30 Jun 2025 15:50:17 +0000 Subject: [PATCH 1/4] Support for JSON logging in MemoryAnalysis handler Signed-off-by: coleramos425 --- inc/memory_analysis_handler.h | 1 + src/memory_analysis_handler.cc | 284 ++++++++++++++++++++++++++++++++- 2 files changed, 283 insertions(+), 2 deletions(-) diff --git a/inc/memory_analysis_handler.h b/inc/memory_analysis_handler.h index f4f9e8e..6000bc9 100644 --- a/inc/memory_analysis_handler.h +++ b/inc/memory_analysis_handler.h @@ -95,6 +95,7 @@ class __attribute__((visibility("default"))) memory_analysis_handler_t : public bool handle_cache_line_count_analysis(const message_t &message); void report_cache_line_use(); void report_bank_conflicts(); + void report_json(); private: //! Maps each of the supported memory access sizes to the conflict sets for that size diff --git a/src/memory_analysis_handler.cc b/src/memory_analysis_handler.cc index 315d5c7..da46ebc 100644 --- a/src/memory_analysis_handler.cc +++ b/src/memory_analysis_handler.cc @@ -29,6 +29,10 @@ #include #include #include +#include +#include +#include +#include namespace { constexpr size_t no_banks = 32; @@ -605,14 +609,290 @@ void memory_analysis_handler_t::report(const std::string &kernel_name, kernelDB: log_file_ = nullptr; } } + void memory_analysis_handler_t::report() { setupLogger(); - report_cache_line_use(); - report_bank_conflicts(); + + // Check log format + bool bFormatJson = false; + const char* logDurLogFormat = std::getenv("LOGDUR_LOG_FORMAT"); + if (logDurLogFormat) { + std::string strFormat = logDurLogFormat; + if (strFormat == "json") { + bFormatJson = true; + } + } + + if (bFormatJson) { + report_json(); + } else { + report_cache_line_use(); + report_bank_conflicts(); + } } void memory_analysis_handler_t::clear() { global_accesses.clear(); lds_accesses.clear(); } + +template +void renderJSON(std::map& fields, std::iostream& out, bool omitFinalComma) +{ + if constexpr (std::is_same_v) { + auto it = fields.begin(); + while (it != fields.end()) + { + out << "\"" << it->first << "\": \"" << it->second << "\""; + it++; + if (it != fields.end() || !omitFinalComma) + out << ","; + } + } + else + { + auto it = fields.begin(); + while (it != fields.end()) + { + out << "\"" << it->first << "\": " << it->second; + it++; + if (it != fields.end() || !omitFinalComma) + out << ","; + } + } +} + +template +void renderJSON(std::vector>& fields, std::iostream& out, bool omitFinalComma, bool valueAsString) +{ + if (valueAsString) { + auto it = fields.begin(); + while (it != fields.end()) + { + out << "\"" << it->first << "\": \"" << it->second << "\""; + it++; + if (it != fields.end() || !omitFinalComma) + out << ","; + } + } + else + { + auto it = fields.begin(); + while (it != fields.end()) + { + out << "\"" << it->first << "\": " << it->second; + it++; + if (it != fields.end() || !omitFinalComma) + out << ","; + } + } +} + +// Function to get code context line for JSON output +std::string getCodeContext(const std::string &fname, uint16_t line) { + static std::string cached_fname; + static std::vector cached_lines; + + // If accessing a new file, clear the old cache and read new file + if (fname != cached_fname) { + cached_fname = fname; + cached_lines.clear(); + + std::ifstream file(fname); + if (!file) { + return ""; // Return empty if file cannot be opened + } + + std::string line_content; + while (std::getline(file, line_content)) { + cached_lines.push_back(line_content); + } + } + + // Check if the requested line is out of bounds + if (line == 0 || line > cached_lines.size()) { + return ""; + } + + // Retrieve and process the requested line: replace each tab by 8 spaces + std::string processed_line = cached_lines[line - 1]; + + // Replace tabs with spaces + size_t pos = 0; + while ((pos = processed_line.find('\t', pos)) != std::string::npos) { + processed_line.replace(pos, 1, " "); // Replace '\t' with 8 spaces + pos += 8; + } + + // Trim leading and trailing whitespace + size_t start = processed_line.find_first_not_of(" "); + if (start == std::string::npos) { + return ""; + } + size_t end = processed_line.find_last_not_of(" "); + return processed_line.substr(start, end - start + 1); +} + +void memory_analysis_handler_t::report_json() { + std::stringstream json_output; + + // Check if the file already exists and has content + bool file_exists = false; + bool file_has_content = false; + std::string existing_content; + + if (location_ != "console") { + std::ifstream check_file(location_); + if (check_file.good()) { + file_exists = true; + check_file.seekg(0, std::ios::end); + file_has_content = check_file.tellg() > 0; + if (file_has_content) { + check_file.seekg(0, std::ios::beg); + existing_content.assign((std::istreambuf_iterator(check_file)), + std::istreambuf_iterator()); + } + check_file.close(); + } + } + + // For the first write to a file, create the initial structure + if (location_ == "console" || !file_has_content) { + json_output << "{\n"; + json_output << " \"kernel_analyses\": [\n"; + + // Write the kernel analysis object + json_output << " {\n"; + } else { + // For subsequent writes, we need to insert into the existing array + // Find the position where we need to insert (before the closing "]) + size_t kernel_analyses_close = existing_content.rfind(" ]"); + if (kernel_analyses_close != std::string::npos) { + // Insert before the closing bracket with proper comma + json_output << existing_content.substr(0, kernel_analyses_close); + json_output << ",\n {\n"; + } else { + // Fallback: start fresh structure + json_output << "{\n"; + json_output << " \"kernel_analyses\": [\n"; + json_output << " {\n"; + } + } + + // Kernel info section + json_output << " \"kernel_info\": {\n"; + json_output << " \"name\": \"" << kernel_ << "\",\n"; + json_output << " \"dispatch_id\": " << dispatch_id_ << "\n"; + json_output << " },\n"; + + // Cache analysis section + json_output << " \"cache_analysis\": {\n"; + json_output << " \"accesses\": [\n"; + + bool first_cache_access = true; + for (const auto &[fname, line_col] : global_accesses) { + for (const auto &[line, col_accesses] : line_col) { + for (const auto &[col, accesses] : col_accesses) { + for (const auto &access : accesses) { + if (!first_cache_access) { + json_output << ",\n"; + } + first_cache_access = false; + + json_output << " {\n"; + json_output << " \"source_location\": {\n"; + json_output << " \"file\": \"" << fname << "\",\n"; + json_output << " \"line\": " << line << ",\n"; + json_output << " \"column\": " << col << "\n"; + json_output << " },\n"; + json_output << " \"code_context\": \"" << getCodeContext(fname, line) << "\",\n"; + json_output << " \"access_info\": {\n"; + json_output << " \"type\": \"" << rw2str(access.rw_kind, rw2str_map) << "\",\n"; + json_output << " \"execution_count\": " << access.no_accesses << ",\n"; + json_output << " \"ir_bytes\": " << access.ir_access_size << ",\n"; + json_output << " \"isa_bytes\": " << access.isa_access_size << ",\n"; + json_output << " \"isa_instruction\": \"" << access.isa_instruction << "\",\n"; + json_output << " \"cache_lines\": {\n"; + json_output << " \"needed\": " << access.min_cache_lines_needed << ",\n"; + json_output << " \"used\": " << access.no_cache_lines_used << "\n"; + json_output << " }\n"; + json_output << " }\n"; + json_output << " }"; + } + } + } + } + + json_output << "\n ]\n"; + json_output << " },\n"; + + // Bank conflicts section + json_output << " \"bank_conflicts\": {\n"; + json_output << " \"accesses\": [\n"; + + bool first_bank_access = true; + for (const auto &[fname, line_col] : lds_accesses) { + for (const auto &[line, col_accesses] : line_col) { + for (const auto &[col, accesses] : col_accesses) { + for (const auto &access : accesses) { + if (!first_bank_access) { + json_output << ",\n"; + } + first_bank_access = false; + + json_output << " {\n"; + json_output << " \"source_location\": {\n"; + json_output << " \"file\": \"" << fname << "\",\n"; + json_output << " \"line\": " << line << ",\n"; + json_output << " \"column\": " << col << "\n"; + json_output << " },\n"; + json_output << " \"code_context\": \"" << getCodeContext(fname, line) << "\",\n"; + json_output << " \"access_info\": {\n"; + json_output << " \"type\": \"" << rw2str(access.rw_kind, rw2str_map) << "\",\n"; + json_output << " \"execution_count\": " << access.no_accesses << ",\n"; + json_output << " \"ir_bytes\": " << access.ir_access_size << ",\n"; + json_output << " \"total_conflicts\": " << access.no_bank_conflicts << "\n"; + json_output << " }\n"; + json_output << " }"; + } + } + } + } + + json_output << "\n ]\n"; + json_output << " }\n"; + json_output << " }\n"; // Close kernel analysis object + + // Always close the array and add metadata for now + // This creates valid JSON for each dispatch, and subsequent dispatches will be handled above + json_output << " ],\n"; + + // Metadata section + json_output << " \"metadata\": {\n"; + json_output << " \"version\": \"1.0\",\n"; + json_output << " \"kernels_found\": 1,\n"; + + // Add timestamp + auto now = std::time(nullptr); + auto tm = *std::localtime(&now); + json_output << " \"timestamp\": \"" << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") << "\",\n"; + + json_output << " \"gpu_info\": {\n"; + json_output << " \"architecture\": \"unknown\",\n"; + json_output << " \"cache_line_size\": 128\n"; + json_output << " }\n"; + json_output << " }\n"; + json_output << "}\n"; + + // Write to the log file + if (location_ == "console") { + *log_file_ << json_output.str(); + } else { + // For file output, rewrite the entire file with the new content + std::ofstream outfile(location_); + outfile << json_output.str(); + outfile.close(); + } +} + } // namespace dh_comms From a9bb05be1a8e5cf9862faf1e407cf3ce07273a9c Mon Sep 17 00:00:00 2001 From: coleramos425 Date: Mon, 30 Jun 2025 16:56:09 +0000 Subject: [PATCH 2/4] Support arch and cache line size fields in metadata output Signed-off-by: coleramos425 --- src/memory_analysis_handler.cc | 51 +++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/memory_analysis_handler.cc b/src/memory_analysis_handler.cc index da46ebc..d465c89 100644 --- a/src/memory_analysis_handler.cc +++ b/src/memory_analysis_handler.cc @@ -869,17 +869,60 @@ void memory_analysis_handler_t::report_json() { // Metadata section json_output << " \"metadata\": {\n"; - json_output << " \"version\": \"1.0\",\n"; - json_output << " \"kernels_found\": 1,\n"; + + std::string version = "null"; // Default + std::ifstream version_file("VERSION"); + if (version_file.good()) { + std::string version_from_file; + std::getline(version_file, version_from_file); + if (!version_from_file.empty()) { + // Trim whitespace and newline + size_t first = version_from_file.find_first_not_of(" \t\n\r"); + if (std::string::npos != first) { + size_t last = version_from_file.find_last_not_of(" \t\n\r"); + version = version_from_file.substr(first, (last - first + 1)); + } + } + } + + json_output << " \"version\": \"" << version << "\",\n"; // Add timestamp auto now = std::time(nullptr); auto tm = *std::localtime(&now); json_output << " \"timestamp\": \"" << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") << "\",\n"; + std::string arch = "unknown"; + int cache_line_size = 128; // default + + hipDeviceProp_t props; + hipError_t err = hipGetDeviceProperties(&props, 0); + if (err == hipSuccess) { + std::string gcnArchName_str(props.gcnArchName); + size_t colon_pos = gcnArchName_str.find(':'); + if (colon_pos != std::string::npos) { + arch = gcnArchName_str.substr(0, colon_pos); + } else { + arch = gcnArchName_str; + } + + std::map arch_to_cache_size = { + {"gfx906", 64}, + {"gfx908", 64}, + {"gfx90a", 128}, + {"gfx940", 128}, + {"gfx941", 128}, + {"gfx942", 128} + }; + + if (arch_to_cache_size.count(arch)) { + cache_line_size = arch_to_cache_size[arch]; + } + } + json_output << " \"gpu_info\": {\n"; - json_output << " \"architecture\": \"unknown\",\n"; - json_output << " \"cache_line_size\": 128\n"; + json_output << " \"architecture\": \"" << arch << "\",\n"; + json_output << " \"cache_line_size\": " << cache_line_size << "\n"; json_output << " }\n"; json_output << " }\n"; json_output << "}\n"; From 4ecbda5f78413fa08e7f6361b87b288649464c44 Mon Sep 17 00:00:00 2001 From: coleramos425 Date: Mon, 30 Jun 2025 17:25:44 +0000 Subject: [PATCH 3/4] Simplify JSON logging approach. Not sure how to indicated end of process for proper JSON formatting Signed-off-by: coleramos425 --- src/memory_analysis_handler.cc | 147 +++++++++++---------------------- 1 file changed, 48 insertions(+), 99 deletions(-) diff --git a/src/memory_analysis_handler.cc b/src/memory_analysis_handler.cc index d465c89..ef5ea17 100644 --- a/src/memory_analysis_handler.cc +++ b/src/memory_analysis_handler.cc @@ -736,58 +736,18 @@ std::string getCodeContext(const std::string &fname, uint16_t line) { void memory_analysis_handler_t::report_json() { std::stringstream json_output; - // Check if the file already exists and has content - bool file_exists = false; - bool file_has_content = false; - std::string existing_content; - - if (location_ != "console") { - std::ifstream check_file(location_); - if (check_file.good()) { - file_exists = true; - check_file.seekg(0, std::ios::end); - file_has_content = check_file.tellg() > 0; - if (file_has_content) { - check_file.seekg(0, std::ios::beg); - existing_content.assign((std::istreambuf_iterator(check_file)), - std::istreambuf_iterator()); - } - check_file.close(); - } - } - - // For the first write to a file, create the initial structure - if (location_ == "console" || !file_has_content) { - json_output << "{\n"; - json_output << " \"kernel_analyses\": [\n"; - - // Write the kernel analysis object - json_output << " {\n"; - } else { - // For subsequent writes, we need to insert into the existing array - // Find the position where we need to insert (before the closing "]) - size_t kernel_analyses_close = existing_content.rfind(" ]"); - if (kernel_analyses_close != std::string::npos) { - // Insert before the closing bracket with proper comma - json_output << existing_content.substr(0, kernel_analyses_close); - json_output << ",\n {\n"; - } else { - // Fallback: start fresh structure - json_output << "{\n"; - json_output << " \"kernel_analyses\": [\n"; - json_output << " {\n"; - } - } + json_output << "{\n"; + json_output << " \"kernel_analysis\": {\n"; // Kernel info section - json_output << " \"kernel_info\": {\n"; - json_output << " \"name\": \"" << kernel_ << "\",\n"; - json_output << " \"dispatch_id\": " << dispatch_id_ << "\n"; - json_output << " },\n"; + json_output << " \"kernel_info\": {\n"; + json_output << " \"name\": \"" << kernel_ << "\",\n"; + json_output << " \"dispatch_id\": " << dispatch_id_ << "\n"; + json_output << " },\n"; // Cache analysis section - json_output << " \"cache_analysis\": {\n"; - json_output << " \"accesses\": [\n"; + json_output << " \"cache_analysis\": {\n"; + json_output << " \"accesses\": [\n"; bool first_cache_access = true; for (const auto &[fname, line_col] : global_accesses) { @@ -799,36 +759,36 @@ void memory_analysis_handler_t::report_json() { } first_cache_access = false; - json_output << " {\n"; - json_output << " \"source_location\": {\n"; - json_output << " \"file\": \"" << fname << "\",\n"; - json_output << " \"line\": " << line << ",\n"; - json_output << " \"column\": " << col << "\n"; - json_output << " },\n"; - json_output << " \"code_context\": \"" << getCodeContext(fname, line) << "\",\n"; - json_output << " \"access_info\": {\n"; - json_output << " \"type\": \"" << rw2str(access.rw_kind, rw2str_map) << "\",\n"; - json_output << " \"execution_count\": " << access.no_accesses << ",\n"; - json_output << " \"ir_bytes\": " << access.ir_access_size << ",\n"; - json_output << " \"isa_bytes\": " << access.isa_access_size << ",\n"; - json_output << " \"isa_instruction\": \"" << access.isa_instruction << "\",\n"; - json_output << " \"cache_lines\": {\n"; - json_output << " \"needed\": " << access.min_cache_lines_needed << ",\n"; - json_output << " \"used\": " << access.no_cache_lines_used << "\n"; - json_output << " }\n"; + json_output << " {\n"; + json_output << " \"source_location\": {\n"; + json_output << " \"file\": \"" << fname << "\",\n"; + json_output << " \"line\": " << line << ",\n"; + json_output << " \"column\": " << col << "\n"; + json_output << " },\n"; + json_output << " \"code_context\": \"" << getCodeContext(fname, line) << "\",\n"; + json_output << " \"access_info\": {\n"; + json_output << " \"type\": \"" << rw2str(access.rw_kind, rw2str_map) << "\",\n"; + json_output << " \"execution_count\": " << access.no_accesses << ",\n"; + json_output << " \"ir_bytes\": " << access.ir_access_size << ",\n"; + json_output << " \"isa_bytes\": " << access.isa_access_size << ",\n"; + json_output << " \"isa_instruction\": \"" << access.isa_instruction << "\",\n"; + json_output << " \"cache_lines\": {\n"; + json_output << " \"needed\": " << access.min_cache_lines_needed << ",\n"; + json_output << " \"used\": " << access.no_cache_lines_used << "\n"; json_output << " }\n"; - json_output << " }"; + json_output << " }\n"; + json_output << " }"; } } } } - json_output << "\n ]\n"; - json_output << " },\n"; + json_output << "\n ]\n"; + json_output << " },\n"; // Bank conflicts section - json_output << " \"bank_conflicts\": {\n"; - json_output << " \"accesses\": [\n"; + json_output << " \"bank_conflicts\": {\n"; + json_output << " \"accesses\": [\n"; bool first_bank_access = true; for (const auto &[fname, line_col] : lds_accesses) { @@ -840,32 +800,28 @@ void memory_analysis_handler_t::report_json() { } first_bank_access = false; - json_output << " {\n"; - json_output << " \"source_location\": {\n"; - json_output << " \"file\": \"" << fname << "\",\n"; - json_output << " \"line\": " << line << ",\n"; - json_output << " \"column\": " << col << "\n"; - json_output << " },\n"; - json_output << " \"code_context\": \"" << getCodeContext(fname, line) << "\",\n"; - json_output << " \"access_info\": {\n"; - json_output << " \"type\": \"" << rw2str(access.rw_kind, rw2str_map) << "\",\n"; - json_output << " \"execution_count\": " << access.no_accesses << ",\n"; - json_output << " \"ir_bytes\": " << access.ir_access_size << ",\n"; - json_output << " \"total_conflicts\": " << access.no_bank_conflicts << "\n"; - json_output << " }\n"; - json_output << " }"; + json_output << " {\n"; + json_output << " \"source_location\": {\n"; + json_output << " \"file\": \"" << fname << "\",\n"; + json_output << " \"line\": " << line << ",\n"; + json_output << " \"column\": " << col << "\n"; + json_output << " },\n"; + json_output << " \"code_context\": \"" << getCodeContext(fname, line) << "\",\n"; + json_output << " \"access_info\": {\n"; + json_output << " \"type\": \"" << rw2str(access.rw_kind, rw2str_map) << "\",\n"; + json_output << " \"execution_count\": " << access.no_accesses << ",\n"; + json_output << " \"ir_bytes\": " << access.ir_access_size << ",\n"; + json_output << " \"total_conflicts\": " << access.no_bank_conflicts << "\n"; + json_output << " }\n"; + json_output << " }"; } } } } - json_output << "\n ]\n"; - json_output << " }\n"; - json_output << " }\n"; // Close kernel analysis object - - // Always close the array and add metadata for now - // This creates valid JSON for each dispatch, and subsequent dispatches will be handled above - json_output << " ],\n"; + json_output << "\n ]\n"; + json_output << " }\n"; + json_output << " },\n"; // Metadata section json_output << " \"metadata\": {\n"; @@ -928,14 +884,7 @@ void memory_analysis_handler_t::report_json() { json_output << "}\n"; // Write to the log file - if (location_ == "console") { - *log_file_ << json_output.str(); - } else { - // For file output, rewrite the entire file with the new content - std::ofstream outfile(location_); - outfile << json_output.str(); - outfile.close(); - } + *log_file_ << json_output.str() << "\n"; } } // namespace dh_comms From bf408bbc7618ebd8a8020f5b287a8b6cbfa88f30 Mon Sep 17 00:00:00 2001 From: coleramos425 Date: Thu, 10 Jul 2025 16:30:21 +0000 Subject: [PATCH 4/4] In order to produce valid JSON output, and close array after last dispatch report, implemented a mechanizm in omniprobe to *close report*. This way we're certain we're done writing dispatches Signed-off-by: coleramos425 --- omniprobe/omniprobe | 29 +++++++++++++++++++++++++++++ src/memory_analysis_handler.cc | 20 ++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/omniprobe/omniprobe b/omniprobe/omniprobe index aec461c..605e59e 100755 --- a/omniprobe/omniprobe +++ b/omniprobe/omniprobe @@ -36,6 +36,32 @@ from pyfiglet import figlet_format sys.stdout.reconfigure(line_buffering=True) +def finalize_json_output(location, handlers): + """Finalize JSON output by adding closing bracket if needed (MemoryAnalysis plugin only)""" + if location == "console": + return + + # Check if MemoryAnalysis plugin is being used + using_memory_analysis = any("libMemAnalysis64.so" in handler for handler in handlers) + if not using_memory_analysis: + return + + # Check if JSON format is being used + if os.environ.get("LOGDUR_LOG_FORMAT") == "json": + try: + # Check if the file exists and has content + if os.path.exists(location) and os.path.getsize(location) > 0: + # Read the file to check if it starts with '[' (indicating it has JSON array content) + with open(location, "r") as f: + content = f.read().strip() + if content.startswith("["): + # Append closing bracket to complete the JSON array + with open(location, "a") as f: + f.write("\n]") + except Exception as e: + print(f"Warning: Could not finalize JSON output: {e}") + + # Generate ASCII art for the word "omniprobe" name = "Omniprobe" ascii_art = figlet_format(name, font="standard") # Default font @@ -635,6 +661,9 @@ def main(): parms.handlers = handlers if len(parms.remaining) != 0 and not parms.dump_env == True: capture_subprocess_output(parms.remaining[1:], new_env=setup_env(parms)) + # Finalize JSON output if needed + log_location = parms.log_location if parms.log_location else "console" + finalize_json_output(log_location, parms.handlers) elif parms.dump_env == True: setup_env(parms) diff --git a/src/memory_analysis_handler.cc b/src/memory_analysis_handler.cc index ef5ea17..b6cb8ab 100644 --- a/src/memory_analysis_handler.cc +++ b/src/memory_analysis_handler.cc @@ -736,6 +736,17 @@ std::string getCodeContext(const std::string &fname, uint16_t line) { void memory_analysis_handler_t::report_json() { std::stringstream json_output; + // Check if this is the first dispatch to write the opening bracket + bool is_first_dispatch = (dispatch_id_ == 1); + bool is_console_output = (location_ == "console"); + + // Write opening bracket for first dispatch (but not for console output) + if (is_first_dispatch && !is_console_output) { + json_output << "[\n"; + } else if (!is_first_dispatch) { + json_output << ",\n"; + } + json_output << "{\n"; json_output << " \"kernel_analysis\": {\n"; @@ -881,10 +892,15 @@ void memory_analysis_handler_t::report_json() { json_output << " \"cache_line_size\": " << cache_line_size << "\n"; json_output << " }\n"; json_output << " }\n"; - json_output << "}\n"; + json_output << "}"; + + // Add newline for console output (for readability) + if (is_console_output) { + json_output << "\n"; + } // Write to the log file - *log_file_ << json_output.str() << "\n"; + *log_file_ << json_output.str(); } } // namespace dh_comms