From 841736b9a4e0945a086f23172b21eb0590809e46 Mon Sep 17 00:00:00 2001 From: Alec Miller Date: Sun, 17 Mar 2024 11:17:46 -0700 Subject: [PATCH] kram-profile - more archive support, ignore unsupported files Notes on archives which are tricky due to storage of relative paths. These can confuse the List id mechanism which is based on URL. Just make sure to store unique identifying folder when building archives. Can now build a report based on selected file - this will look at archive or same parent folder. This allows multiple archives to be loaded. --- kram-profile/README.md | 6 +- kram-profile/kram-profile/File.swift | 27 +++++---- .../kram-profile/kram_profileApp.swift | 55 +++++++++++++------ 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/kram-profile/README.md b/kram-profile/README.md index 53bcae3..dbd1895 100644 --- a/kram-profile/README.md +++ b/kram-profile/README.md @@ -24,9 +24,9 @@ TODO: (x are done) * x Find start/end time of each json files. * x Support gzip trace files * x Add sort by range (useful for mem/build traces) +* x Add zip archive support, can drop archive of 1+ traces * Add frame type for perf traces for vsync ticker (binary format prob has it) -* Add zip archive support, can drop archive of 1+ traces * Tie in with the excellent ClangBuildAnalyzer tool * Scale specific traces to a single duration. That way the next file comes in at that scale. * Move away from Catapult json to own binary format. Can then translate to json or use the Perfetto SDK to convert to protobufs. @@ -61,8 +61,8 @@ Cpu Profilers. See for more details * spall * Commercial -* Telemetry - httpd://www.radgametools.com/telemeetry.htm -* Superluminal - +* Telemetry - httpd://www.radgametools.com/telemetry.htm +* Superluminal - higher-rate sampling profiler * Xcode Instruments - see Xcode * AMD Code Analyst - see Xcode * Intel Vtune - diff --git a/kram-profile/kram-profile/File.swift b/kram-profile/kram-profile/File.swift index 90defaf..89f9cf5 100644 --- a/kram-profile/kram-profile/File.swift +++ b/kram-profile/kram-profile/File.swift @@ -24,6 +24,10 @@ enum FileType { class File: Identifiable, /*Hashable, */ Equatable, Comparable { + // TODO: archive url relative to archive so not unqique if multiple archives dropped + // but currently all lookup is by url, and not url + archive. Just make sure to + // include unique dir when building archives. zip has max 512 char path. + var id: String { url.absoluteString } var name: String { url.lastPathComponent } let url: URL @@ -82,7 +86,7 @@ class File: Identifiable, /*Hashable, */ Equatable, Comparable } } - // show some of dir file is in, TODO: 2 levels not enough + // show some of dir file is in, TODO: 2 levels not enough? public static func buildShortDirectory(url: URL) -> String { let count = url.pathComponents.count @@ -135,27 +139,20 @@ class File: Identifiable, /*Hashable, */ Equatable, Comparable } } -// TODO: now that it's a class, can probably elimiante that lookuFile calls func generateDuration(file: File) -> String { - // need for duration - let f = lookupFile(url: file.url) - if f.duration != 0.0 { - // TODO: may want to add s/mb based on file type - return String(format:"%0.3f", f.duration) // sec vis to ms for now - } - else { - return "" - } + if file.duration == 0.0 { return "" } + + let unitText = file.fileType == .Memory ? "m" : "s" + return "\(double:file.duration, decimals:3)\(unitText)" } func generateNavigationTitle(_ sel: String?) -> String { - if sel == nil { - return "" - } + if sel == nil { return "" } let f = lookupFile(selection: sel!) var text = generateDuration(file: f) + " " + f.name + // add the archive name if let fileArchive = f.archive { text += " in (" + fileArchive.name + ")" } @@ -407,6 +404,8 @@ func listFilesFromArchive(_ urlArchive: URL) -> [File] { continue } + // TODO: archives don't have full paths, so lookup can get confused + // if there are multiple archives with same paths. let file = lookupFile(url:url) if file.archive != archive { file.archive = archive diff --git a/kram-profile/kram-profile/kram_profileApp.swift b/kram-profile/kram-profile/kram_profileApp.swift index 1e01796..9d5a87b 100644 --- a/kram-profile/kram-profile/kram_profileApp.swift +++ b/kram-profile/kram-profile/kram_profileApp.swift @@ -788,8 +788,25 @@ func updateFileBuildTimings(_ events: [CatapultEvent]) -> [String:BuildTiming] { return buildTimings } +func findFilesForBuildTimings(files: [File], selection: String) -> [File] { + let selectedFile = lookupFile(url:URL(string:selection)!) + let isArchive = selectedFile.archive != nil + + let filteredFiles = files.filter { file in + if isArchive { + return file.archive != nil && file.archive! == selectedFile.archive! + } + else { + return file.parentFolders == selectedFile.parentFolders + } + } + + return filteredFiles; +} + func postBuildTimingsReport(files: [File]) -> String? { let buildTimings = mergeFileBuildTimings(files: files) + if buildTimings.isEmpty { return nil } let buildJsonBase64 = buildPerfettoJsonFromBuildTimings(buildTimings: buildTimings) let buildJS = postLoadFileJS(fileContentBase64: buildJsonBase64, title: "BuildTimings") return buildJS @@ -1413,8 +1430,6 @@ func loadFileJS(_ path: String) -> String? { // Clang has some build totals as durations on fake threads // but those are smaller than the full duration. - let doCompress = true - var json : Data if file.containerType == .Compressed { @@ -1436,6 +1451,10 @@ func loadFileJS(_ path: String) -> String? { let decoder = JSONDecoder() var catapultProfile = try decoder.decode(CatapultProfile.self, from: json) + if catapultProfile.traceEvents == nil { + return nil + } + // demangle the OptFunction name for i in 0.. String? { let fileContentFixed = try encoder.encode(catapultProfile) // gzip compress the data before sending it over - if doCompress { - guard let compressedData = fileContentFixed.gzip() else { return nil } - fileContentBase64 = compressedData.base64EncodedString() - } - else { - fileContentBase64 = fileContentFixed.base64EncodedString() - } + guard let compressedData = fileContentFixed.gzip() else { return nil } + fileContentBase64 = compressedData.base64EncodedString() } return postLoadFileJS(fileContentBase64: fileContentBase64, title:fileURL.lastPathComponent) @@ -1685,7 +1699,7 @@ struct kram_profileApp: App { // preserve the original selection if still present if selection != nil { var found = false - for file in fileSearcher.files { + for file in fileSearcher.filesSorted { if file.id == selection { found = true break; @@ -1694,12 +1708,12 @@ struct kram_profileApp: App { // load first file in the list if !found { - selection = fileSearcher.files[0].id + selection = fileSearcher.filesSorted[0].id } } else { // load first file in the list - selection = fileSearcher.files[0].id + selection = fileSearcher.filesSorted[0].id } } } @@ -2097,16 +2111,25 @@ A tool to help profile mem, perf, and builds. CommandGroup(after: .toolbar) { // TODO: only enable if build files are present - // eventually don't run this on all, maybe find those related to selection - Button("Build Report") { - // should this be on all or just those seached? - let buildJS = postBuildTimingsReport(files: fileSearcher.filesSearched) + Button("Build Report All") { + let buildFiles = fileSearcher.files + let buildJS = postBuildTimingsReport(files: buildFiles) + if buildJS != nil { + runJavascript(myWebView, buildJS!) + } + } + .disabled(selection == nil) + + Button("Build Report Selected") { + let buildFiles = findFilesForBuildTimings(files: fileSearcher.files, selection: selection!) + let buildJS = postBuildTimingsReport(files: buildFiles) if buildJS != nil { runJavascript(myWebView, buildJS!) } } .disabled(selection == nil) + // must call through NSWindow Button("See Below") { // Window isn't set in AppDelegate, so menu item is skipped.