From 50dfba7e8ec1d7bb30a288f7042bed96ad06fb16 Mon Sep 17 00:00:00 2001 From: Alec Miller Date: Sat, 16 Mar 2024 13:47:03 -0700 Subject: [PATCH] kram-profile - demangle backend names, remove updateFileCache, store totalFronted/Backend times --- kram-profile/Source/KramZipHelper.cpp | 41 ++++++ kram-profile/Source/KramZipHelperW.h | 6 + kram-profile/kram-profile/File.swift | 13 +- .../kram-profile/kram_profileApp.swift | 134 ++++++++++++++---- 4 files changed, 163 insertions(+), 31 deletions(-) diff --git a/kram-profile/Source/KramZipHelper.cpp b/kram-profile/Source/KramZipHelper.cpp index 8a54e09..42c175e 100644 --- a/kram-profile/Source/KramZipHelper.cpp +++ b/kram-profile/Source/KramZipHelper.cpp @@ -18,6 +18,47 @@ #include #endif +// Throwing this in for now, since it's the only .cpp file +#if KRAM_MAC || KRAM_IOS +#include // demangle +#include +#include +#endif + +extern "C" const char* _Nonnull demangleSymbolName(const char* _Nonnull symbolName_) { + using namespace NAMESPACE_STL; + + // serialize to multiple threads + static mutex sMutex; + static unordered_map sSymbolToDemangleName; + lock_guard lock(sMutex); + + string symbolName(symbolName_); + auto it = sSymbolToDemangleName.find(symbolName); + if (it != sSymbolToDemangleName.end()) { + return it->second; + } + + // see CBA if want a generalized demangle for Win/Linux + size_t size = 0; + int status = 0; + char* symbol = abi::__cxa_demangle(symbolName.c_str(), nullptr, &size, &status); + const char* result = nullptr; + if (status == 0) { + + sSymbolToDemangleName[symbolName] = symbol; + result = symbol; + // not freeing the symbols here + //free(symbol); + } + else { + // This will do repeated demangle though. Maybe should add to table? + result = symbolName_; + } + + return result; +} + namespace kram { using namespace NAMESPACE_STL; diff --git a/kram-profile/Source/KramZipHelperW.h b/kram-profile/Source/KramZipHelperW.h index 2fca76f..84baad8 100644 --- a/kram-profile/Source/KramZipHelperW.h +++ b/kram-profile/Source/KramZipHelperW.h @@ -35,3 +35,9 @@ typedef struct ZipEntryW { @end +// This is only needed for OptFunction and backend names +// Can't prepend extern "C" onto the call. +extern "C" { +const char* _Nonnull demangleSymbolName(const char* _Nonnull symbolName_); +} + diff --git a/kram-profile/kram-profile/File.swift b/kram-profile/kram-profile/File.swift index bf8393c..ba24ea2 100644 --- a/kram-profile/kram-profile/File.swift +++ b/kram-profile/kram-profile/File.swift @@ -35,7 +35,7 @@ class File: Identifiable, /*Hashable, */ Equatable, Comparable let containerType: ContainerType var archive: Archive? - var duration = 0.0 + var duration = 0.0 // in seconds var fileContent: Data? var modStamp: Date? @@ -43,6 +43,8 @@ class File: Identifiable, /*Hashable, */ Equatable, Comparable // This is only updated for Build fileType var buildTimings: [String:BuildTiming] = [:] + var totalFrontend = 0 // in micros + var totalBackend = 0 // only available for memory file type right now var threadInfo = "" @@ -155,7 +157,7 @@ func generateNavigationTitle(_ sel: String?) -> String { var text = generateDuration(file: f) + " " + f.name if let fileArchive = f.archive { - text += "in (" + fileArchive.name + ")" + text += " in (" + fileArchive.name + ")" } return text @@ -195,11 +197,6 @@ func lookupFile(selection: String) -> File { return lookupFile(url:URL(string:selection)!) } -// This one won't be one in the list, though -func updateFileCache(file: File) { - fileCache[file.url] = file -} - //------------- class Archive: Identifiable, /*Hashable, */ Equatable, Comparable { @@ -303,6 +300,8 @@ func lookupArchive(_ url: URL) -> Archive { // release other calcs (f.e. duration, histogram, etc) // can point to new archive content here file.buildTimings.removeAll() + file.totalFrontend = 0 + file.totalBackend = 0 } } } diff --git a/kram-profile/kram-profile/kram_profileApp.swift b/kram-profile/kram-profile/kram_profileApp.swift index fd1f91f..b4d85db 100644 --- a/kram-profile/kram-profile/kram_profileApp.swift +++ b/kram-profile/kram-profile/kram_profileApp.swift @@ -31,18 +31,33 @@ import UniformTypeIdentifiers // then can focus on the bigger values. // TODO: Sort by name and convert to count - can then see common counts // so keep the json loaded in Swift. Can json be cloned and modded? -// TODO: option to colesce to count and name with sort +// TODO: option to coalesce to count and name with sort // Build traces // TODO: parse totals from build traces, what CBA is doing // TODO: present total time, and % of total in the nav panel -// TODO: import zip, and run cba on contents, mmap and decompress each -// can use incremental mode? -// TODO: can't mmap web link, but can load zip off server with timings +// Perf traces +// TODO: ... + +// TODO: track kram-profile memory use, jettison Data that isn't needed after have built up timings. +// can re-decompress from zip mmap. + +// TODO: background process to compute duration and buildTimings across all files +// how to refresh the list as these are updated. Use Swift Task? Or could do on C++ side with TaskSystem. + +// DONE: import zip // DONE: add/update recent document list (need to hold onto dropped/opened folder) -// TODO: save/load the duration and modstamps for File, and any other metadata (totals per section) -// TODO: add jump to source, but path would need to be correct (sandbox block?) +// DONE: can't mmap web link, but can load zip off server with timings + +// TODO: run cba on files, mmap and decompress each can use incremental mode? +// TODO: save/load the duration and modstamps for File at quit, and any other metadata (totals per section) +// TODO: add jump to source/header, but path would need to be correct (sandbox block?) + +// Build traces +// TODO: OptFunction needs demangled. All backend strings are still mangled. +// Don’t need the library CBA uses just use api::__cxa_demangle() on macOS. +// https://github.com/llvm/llvm-project/issues/459 // TODO: across all files, many of the strings are the same. Could replace all strings // with an index, compress, and zip archive with the index table. buid, perf, mem strings @@ -78,14 +93,14 @@ import UniformTypeIdentifiers // 4-bit, 12-bit, 16-bit, variable, pad to 4B -// TODO: recent documents list doesn't survive relaunch, but only when app is rebuilt +// DONE: recent documents list doesn't survive relaunch, but only when app is rebuilt // but still kind of annoying for development // DONE: have a way to reload dropped folder // DONE: track parent archives, folder, and loose drop files // and when reload is hit, then reload all of that rebuild the list // and then reload the selected file -// TODO: zipHelper to deal with archives, can use Swift Data to mmap content if needed +// DONE: zipHelper to deal with archives, can use Swift Data to mmap content if needed // mmap the zip, list out the files and locations, and then defalte the content somewhere // only then can data be handed off toe Pefertto or CBA. And CBA needs all files. // Maybe extend CBA to read a zip file. Can just use ZipHelper. @@ -706,6 +721,9 @@ func updateFileBuildTimings(_ catapultProfile: CatapultProfile) -> [String:Build // and then subtracting the immediate children. // See what CBA and Perfetto do to establish this. + // Would be good to establish this nesting once and store the level + // with each event.d + // run through each file, and build a local map of name to size count for i in 0.. [String:BuildTiming] { func buildPerfettoJsonFromBuildTimings(buildTimings: [String:BuildTiming]) -> String { // now convert those timings back into a perfetto displayable report - // So just need to buid up the json above into events on tracks + // So just need to build up the json above into events on tracks var events: [CatapultEvent] = [] // Also sort or assign a sort_index to the tracks. Sort biggest to smallest. @@ -808,23 +826,27 @@ func buildPerfettoJsonFromBuildTimings(buildTimings: [String:BuildTiming]) -> St } } - // TODO: sort this by the duration events.sort { + // want threadnames first, could just prepend these to array? + if $0.ph! != $1.ph! { + return $0.ph! < $1.ph! + } + + // then thread id if $0.tid! != $1.tid! { return $0.tid! < $1.tid! } + // then duration // has to be > to work as a single tid if $0.dur != $1.dur! { return $0.dur! > $1.dur! } + // then name return $0.name! < $1.name! } - // assign thread id, may not need names or tid - // since Perfetto will just treat the events as subevents - let catapultProfile = CatapultProfile(traceEvents: events) do { @@ -945,7 +967,6 @@ func updateThreadInfo(_ catapultProfile: CatapultProfile, _ file: inout File) { } file.threadInfo = text - updateFileCache(file: file) } func updateDuration(_ catapultProfile: CatapultProfile, _ file: inout File) { @@ -967,8 +988,6 @@ func updateDuration(_ catapultProfile: CatapultProfile, _ file: inout File) { if startTime <= endTime { // for now assume micros file.duration = Double(endTime - startTime) * 1e-6 - - updateFileCache(file: file) } } @@ -1076,6 +1095,56 @@ func loadFileJS(_ path: String) -> String? { file.buildTimings = updateFileBuildTimings(catapultProfile) } + /* These are types CBA is looking at. It's not looking at any totals + DebugType isn't in this. + + if (StrEqual(name, "ExecuteCompiler")) + event.type = BuildEventType::kCompiler; + else if (StrEqual(name, "Frontend")) + event.type = BuildEventType::kFrontend; + else if (StrEqual(name, "Backend")) + event.type = BuildEventType::kBackend; + else if (StrEqual(name, "Source")) + event.type = BuildEventType::kParseFile; + else if (StrEqual(name, "ParseTemplate")) + event.type = BuildEventType::kParseTemplate; + else if (StrEqual(name, "ParseClass")) + event.type = BuildEventType::kParseClass; + else if (StrEqual(name, "InstantiateClass")) + event.type = BuildEventType::kInstantiateClass; + else if (StrEqual(name, "InstantiateFunction")) + event.type = BuildEventType::kInstantiateFunction; + else if (StrEqual(name, "OptModule")) + event.type = BuildEventType::kOptModule; + else if (StrEqual(name, "OptFunction")) + event.type = BuildEventType::kOptFunction; + + // here are totals that are in the file + // Total ExecuteCompiler = Total Frontend + Total Backend + // 2 frontend blocks though, + // 1. Source, InstantiateFunction, CodeGenFunction, ... + // 2. CodeGenFunction, DebugType, and big gaps + // + // 1 backend block + // OptModule + + "Total ExecuteCompiler" <- important + "Total Frontend" <- important <- important + "Total InstantiateFunction" + "Total CodeGen Function" + "Total Backend" + "Total CodeGenPasses" + "Total OptModule" <- important + "Total OptFunction" + "Total RunPass" + "Total InstantiatePass" + "Total Source" + "Total ParseClass" + "Total DebugType" + "Total PerformPendingInstantiations" + "Total Optimizer" + */ + for i in 0.. String? { event.name == "CodeGen Function" || event.name == "RunPass" { - // This is a name - let detail = event.args!["detail"]!.value as! String - catapultProfile.traceEvents![i].name = detail + // backend symbols need demangle + let isDemangleNeeded = event.name == "OptFunction" + + if isDemangleNeeded { + let detail = event.args!["detail"]!.value as! String + let symbolName = String(cString: demangleSymbolName(detail)) + + catapultProfile.traceEvents![i].name = symbolName + } + else { + // This is a name + let detail = event.args!["detail"]!.value as! String + catapultProfile.traceEvents![i].name = detail + } + } + + // These aren't renamed but are useful data for report + // and are already calculated. + else if event.name == "Total Backend" { + file.totalBackend = event.dur! + } + else if event.name == "Total Ffrontend" { + file.totalFrontend = event.dur! } } - // walk the file and compute the duration if we don't already have ti + // walk the file and compute the duration if we don't already have it if file.duration == 0.0 { - - updateDuration(catapultProfile, &file) // For now, just log the per-thread info @@ -1341,7 +1428,7 @@ struct kram_profileApp: App { } // This isn't so valuable to open a file, but opening a referenced header from build - // would be. + // would be. But would to respond to/retrieve selection in JS side. func openContainingFolder(_ str: String) { let url = URL(string: str)! NSWorkspace.shared.activateFileViewerSelecting([url]); @@ -1382,7 +1469,6 @@ struct kram_profileApp: App { let file = lookupFile(selection: sel) file.setLoadStamp() - updateFileCache(file: file) } // Want to be able to lock the scale of the