Skip to content

Commit ed8fbcd

Browse files
committed
kram-profile - log count, duration about each track.
Can eventually store with the File object and display in a hud. But for now, just useful to see the count/duration for mem traces. Less useful for build or perf traces.
1 parent 7653d3b commit ed8fbcd

File tree

2 files changed

+147
-88
lines changed

2 files changed

+147
-88
lines changed

Diff for: kram-profile/kram-profile/Log.swift

+14-16
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import Darwin
5050

5151
class Log {
5252
// verbose: Whether os_log or print is used to report logs.
53-
static var prints = true
53+
static var prints = false
5454
// stacktrace: Whether stack trace is logged on errors.
5555
static var stacktraces = false
5656
// timestamp: Show timestamps on all entries when printing statements.
@@ -103,15 +103,6 @@ class Log {
103103
#endif
104104
}
105105

106-
func fault(_ message: String) {
107-
let text = formatMessage(message, .fault)
108-
if Log.prints {
109-
print(text)
110-
} else {
111-
os_log("%@", log: log, type: .fault, text)
112-
}
113-
}
114-
115106
func error(_ message: String, _ function: String = #function, _ line: Int = #line) {
116107
let text = formatMessage(message, .error, function, line)
117108
if Log.prints {
@@ -122,8 +113,8 @@ class Log {
122113
}
123114

124115
// os_log left out warnings, so reuse default type for that
125-
func warn(_ message: String) {
126-
let text = formatMessage(message, .default)
116+
func warn(_ message: String, _ function: String = #function, _ line: Int = #line) {
117+
let text = formatMessage(message, .default, function, line)
127118
if Log.prints {
128119
print(text)
129120
} else {
@@ -167,22 +158,29 @@ class Log {
167158
text += "\(timestamp)I[\(category)] \(message)"
168159
case .default: // not a keyword
169160
text += "\(timestamp)W[\(category)] \(message)"
161+
text += Log.formatLocation(file, line, function)
170162
case .error:
171163
text += "\(timestamp)E[\(category)] \(message)\n"
172164
text += Log.formatLocation(file, line, function)
173-
case .fault:
174-
text += "\(timestamp)F[\(category)] \(message)\n"
175-
text += Log.formatLocation(file, line, function)
176165
default:
177166
text += message
178167
}
179168
} else {
180169
// Consider reporting the data above to os_log.
181170
// os_log reports data, time, app, threadId and message to stderr.
182171
text += message
172+
173+
// os_log can't show correct file/line, since it uses addrReturnAddress - ugh
174+
switch type {
175+
case .default: fallthrough
176+
case .error:
177+
text += Log.formatLocation(file, line, function)
178+
default:
179+
break
180+
}
183181
}
184182

185-
if Log.stacktraces && (type == .error || type == .fault) {
183+
if Log.stacktraces && (type == .error) {
186184
text += "\n"
187185

188186
// Improve this - these are mangled symbols without file/line of where

Diff for: kram-profile/kram-profile/kram_profileApp.swift

+133-72
Original file line numberDiff line numberDiff line change
@@ -15,43 +15,47 @@ import UniformTypeIdentifiers
1515
// can interop with. SwiftUI has not browser widget.
1616

1717
// DONE: add bg list color depending on sort
18+
// DONE: fn+F doesn't honor fullscreen
19+
// DONE: Perfetto can only read .gz files, and not .zip files.
20+
// But could decode zip files here, and send over gz compressed.
21+
// Would need to idenfity zip archive > 1 file vs. zip single file.
22+
// DONE: add gz compression to all file data. Use libCompression
23+
// but it only has zlib compression. Use DataCompression which
24+
// messages zlib deflate to gzip.
25+
// DONE: if list hidden, then can't advance
26+
// DONE: be nice to focus the search input on cmd+F just to make me happy. (using cmd+S)
27+
// Browser goes to its own search which doesn’t help.
28+
29+
1830
// TODO: add sort mode for name, time and incorporating dir or not
1931
// TODO: fix the js wait, even with listener, there's still a race
2032
// maybe there's some ServiceWorker still loading the previous json?
2133
// Perfetto is using a ServiceWorker, Safari uses those now, and ping/pong unware.
22-
// TODO: still getting race condition. Perfetto is trying to
34+
// TODO: still getting web race condition. Perfetto is trying to
2335
// load the previous file, and we’re sending a new one.
24-
// TODO: update recent document list
36+
// TODO: add/update recent document list (need to hold onto dropped/opened folder)
2537
// TODO: have a way to reload dropped folder (not just subfiles)
2638
// TODO: nav title and list item text is set before duration is computed
2739
// need some way to update that.
2840
// TODO: support WindowGroup and multiwindow, each needs own webView, problem
2941
// is that onOpenURL opens a new window always.
30-
// DONE: fn+F doesn't honor fullscreen
31-
// TODO: be nice to focus the search input on cmd+F just to make me happy.
32-
// Browser goes to its own search which doesn’t help.
3342
// TODO: work on sending a more efficient form. Could use Perfetto SDK to write to prototbuf. The Catapult json format is overly verbose. Need some thread and scope strings, some open/close timings that reference a scope string and thread.
34-
// DONE: Perfetto can only read .gz files, and not .zip files.
35-
// But could decode zip files here, and send over gz compressed.
36-
// Would need to idenfity zip archive > 1 file vs. zip single file.
37-
// DONE: add gz compression to all file data. Use libCompression
38-
// but it only has zlib compression. Use DataCompression which
39-
// messages zlib deflate to gzip.
4043
// TODO: switch font to Inter, bundle that with the app?
4144
// .environment(\.font, Font.custom("CustomFont", size: 14))
4245
// TODO: for perf traces, compute duration between frame
4346
// markers. Multiple frames in a file, then show max frame duration
4447
// instead of the entire file.
4548
// TODO: can't type ahead search in the list while the webview is loading (f.e. e will advance)
4649
// but arrow keys work to move to next
47-
// DONE: if list hidden, then can't advance
4850
// TODO: can't overide "delete" key doing a back in the WKWebView history
4951
// Perfetto warns that content will be lost
5052
// TODO: track duration would be useful (esp. for memory traces)
5153
// Would have to modify the thread_name, and process the tid and timings
5254
// Better if Perfetto could display this
5355
// TODO: list view type ahead search doesn't work unless name is the first Text entry
5456
// TODO: switch to dark mode
57+
// TODO: no simple scrollTo, since this is all React style
58+
// There is a ScrollViewReader, but value only usable within. UITableView has this.
5559
// TODO track when files change or get deleted, update the list item then
5660
// can disable list items that are deleted in case they return (can still pick if current)
5761
// https://developer.apple.com/documentation/coreservices/file_system_events?language=objc
@@ -201,9 +205,10 @@ func updateFileCache(file: File) {
201205

202206
class MyWebView : WKWebView {
203207

204-
// This is ugly.
208+
// So that keyboard events are routed
205209
override var acceptsFirstResponder: Bool { true }
206210

211+
// This is to prevent bonk
207212
override func performKeyEquivalent(with event: NSEvent) -> Bool {
208213
// unclear why all events are going to WebView
209214
// so have to return false to not have them filtered
@@ -224,6 +229,10 @@ class MyWebView : WKWebView {
224229
// true means it doesn't bonk, but WKWebView still gets to
225230
// respond to the keys. Ugh. Stupid system.
226231
return true
232+
233+
// or this ?
234+
// return super.performKeyEquivalent(with: event)
235+
227236
}
228237
}
229238

@@ -297,7 +306,33 @@ struct WebView : NSViewRepresentable {
297306
// MyWKWebView()
298307
//}
299308

300-
// has to be https to work for some reason, but all data is previewed locally
309+
/*
310+
class MyMTKView: MTKView {
311+
312+
313+
314+
}
315+
316+
// wraps MTKView (NSView) into SwiftUI, so it can be a part of the hierarcy,
317+
// updateNSView called when app foreground/backgrounded, or the size is changed
318+
struct MTKViewWrapper: NSViewRepresentable {
319+
var mtkView: MyMTKView
320+
321+
// TODO: could hand this down without rebuilding wrapper, could be @Published from UserData
322+
//var currentPath: String
323+
324+
func makeNSView(context: NSViewRepresentableContext<MTKViewWrapper>) -> MyMTKView {
325+
return mtkView
326+
}
327+
328+
func updateNSView(_ view: MyMTKView, context: NSViewRepresentableContext<MTKViewWrapper>) {
329+
//view.updateUserData(currentPath: currentPath)
330+
331+
}
332+
}
333+
*/
334+
335+
// https to work for some reason, but all data is previewed locally
301336
var ORIGIN = "https://ui.perfetto.dev"
302337

303338
// https://gist.github.com/pwightman/64c57076b89c5d7f8e8c
@@ -523,6 +558,77 @@ func loadFileJS(_ path: String) -> String? {
523558
var perfetto: PerfettoFile
524559
}
525560

561+
class ThreadInfo : Hashable, Equatable, Comparable {
562+
563+
var id: Int = 0
564+
var threadName: String = ""
565+
var startTime: Int = Int.max
566+
var endTime: Int = Int.min
567+
var count: Int = 0
568+
569+
// id doesn't implement Hashable
570+
func hash(into hasher: inout Hasher) {
571+
hasher.combine(id)
572+
}
573+
574+
public static func == (lhs: ThreadInfo, rhs: ThreadInfo) -> Bool {
575+
return lhs.id == rhs.id
576+
}
577+
public static func < (lhs: ThreadInfo, rhs: ThreadInfo) -> Bool {
578+
return lhs.id < rhs.id
579+
}
580+
581+
func combine(_ s: Int, _ d: Int) {
582+
startTime = min(startTime, s)
583+
endTime = max(endTime, s+d)
584+
count += 1
585+
}
586+
587+
var description: String {
588+
let duration = Double(endTime - startTime) * 1e-6
589+
return "\(id) \(threadName) \(duration) \(count)x"
590+
}
591+
592+
}
593+
594+
// parse json trace
595+
func updateThreadInfo(_ catapultProfile: CatapultProfile, _ file: inout File) {
596+
597+
598+
// was using Set<>, but having trouble with lookup
599+
var threadInfos: [Int: ThreadInfo] = [:]
600+
601+
for i in 0..<catapultProfile.traceEvents!.count {
602+
let event = catapultProfile.traceEvents![i]
603+
604+
// have to have tid to associate with ThreadInfo
605+
guard let tid = event.tid else { continue }
606+
607+
if threadInfos[tid] == nil {
608+
var info = ThreadInfo()
609+
info.id = tid
610+
611+
threadInfos[tid] = info
612+
}
613+
614+
if event.name != nil && event.name! == "thread_name" {
615+
let threadName = event.args!["name"]!.value as! String
616+
threadInfos[tid]!.threadName = threadName
617+
}
618+
else if event.ts != nil && event.dur != nil {
619+
let s = event.ts!
620+
let d = event.dur!
621+
622+
threadInfos[tid]!.combine(s, d)
623+
}
624+
}
625+
626+
// TODO: could store this in the File object, just append with \n
627+
for threadInfo in threadInfos.values.sorted() {
628+
log.info(threadInfo.description)
629+
}
630+
}
631+
526632
func updateDuration(_ catapultProfile: CatapultProfile, _ file: inout File) {
527633
var startTime = Int.max
528634
var endTime = Int.min
@@ -612,6 +718,11 @@ func loadFileJS(_ path: String) -> String? {
612718
}
613719

614720
updateDuration(catapultProfile, &file)
721+
722+
// For now, just log the per-thread info
723+
if type == FileType.Memory {
724+
updateThreadInfo(catapultProfile, &file)
725+
}
615726
}
616727
}
617728
}
@@ -673,6 +784,11 @@ func loadFileJS(_ path: String) -> String? {
673784
// walk the file and compute the duration if we don't already have ti
674785
if file.duration == nil {
675786
updateDuration(catapultProfile, &file)
787+
788+
// For now, just log the per-thread info
789+
if type == FileType.Memory {
790+
updateThreadInfo(catapultProfile, &file)
791+
}
676792
}
677793
}
678794

@@ -1149,64 +1265,6 @@ A tool to help profile mem, perf, and builds.
11491265
}
11501266
.searchableOptional(text: $searchText, isPresented: $searchIsActive, placement: .sidebar, prompt: "Filter")
11511267

1152-
/*
1153-
#if false
1154-
.onAppear {
1155-
// https://stackoverflow.com/questions/73252399/swiftui-keyboard-navigation-in-lists-on-macos
1156-
if keyMonitor != nil {
1157-
return
1158-
}
1159-
1160-
keyMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { event in
1161-
1162-
// This is advancing 2 entries, since List also responds
1163-
// This gets called 2x after error dialog post, but unclear why.
1164-
// Should just be down/up.
1165-
1166-
if event.isARepeat {
1167-
return event
1168-
}
1169-
1170-
if focusedField != .listView {
1171-
if selection != nil {
1172-
if event.keyCode == Keycode.downArrow {
1173-
selection = selectFile(selection, searchResults, true)
1174-
}
1175-
else if event.keyCode == Keycode.upArrow {
1176-
selection = selectFile(selection, searchResults, false)
1177-
}
1178-
}
1179-
}
1180-
return event
1181-
}
1182-
}
1183-
.onDisappear {
1184-
if keyMonitor != nil {
1185-
NSEvent.removeMonitor(keyMonitor!)
1186-
keyMonitor = nil
1187-
}
1188-
}
1189-
#else
1190-
1191-
// need these macOS 14 calls to advance the list when list is closed
1192-
.onKeyPress(.downArrow, action: {
1193-
if focusedField != .listView {
1194-
selection = selectFile(selection, searchResults, true)
1195-
return .handled
1196-
}
1197-
return .ignored
1198-
})
1199-
.onKeyPress(.upArrow, action: {
1200-
if focusedField != .listView {
1201-
selection = selectFile(selection, searchResults, false)
1202-
return .handled
1203-
}
1204-
return .ignored
1205-
})
1206-
1207-
#endif
1208-
*/
1209-
12101268
.onChange(of: selection /*, initial: true*/) { newState in
12111269
openFileSelection(myWebView)
12121270
//focusedField = .webView
@@ -1253,6 +1311,9 @@ A tool to help profile mem, perf, and builds.
12531311
Button("Prev File") {
12541312
if selection != nil {
12551313
selection = selectFile(selection, searchResults, false)
1314+
1315+
// TODO: no simple scrollTo, since this is all React style
1316+
// There is a ScrollViewReader, but valud only usable within
12561317
}
12571318
}
12581319
.keyboardShortcut("N", modifiers:[.shift, .command])

0 commit comments

Comments
 (0)