本文即简单介绍了如何在Swift中开发命令行工具,以及与Shell命令的交互。水文一篇,不喜勿喷。
主要是使用该工具来解析微信的性能监控组件Matrix的OOM Log。
基本模块
这里,仅简单介绍了常见的基本模块。
Process
Process类可以用来打开另外一个子进程,并监控其运行情况。
Pipe
Pipe这个类就是操作系统的管道,在这里用来接受子进程的输出。这里,可以用于将process的输出传递至管道指定的地方,如一个output变量,或者文件也可以。
CommandLine
用于获取脚本参数而已。
print(CommandLine.argc) // 2 print(CommandLine.arguments) // ["./test.swift", "hello"]
封装Shell命令
仅执行Shell命令
这里提供了两种调用Shell命令的封装函数,个人更倾向于第二种,直接将Shell命令及参数封装成一个字符串传入即可。
@discardableResult func runShell(_ command: String) -> Int32 { let task = Process() task.launchPath = "/bin/bash" task.arguments = ["-c", command] task.launch() task.waitUntilExit() return task.terminationStatus } @discardableResult func runShellWithArgs(_ args: String...) -> Int32 { let task = Process() task.launchPath = "/usr/bin/env" task.arguments = args task.launch() task.waitUntilExit() return task.terminationStatus }
使用如下:
runShell("pwd") runShell("ls -l") runShellWithArgs("pwd") runShellWithArgs("ls", "-l")
需要Shell命令的输出内容
这里就需要使用到Pipe了。
@discardableResult func runShellAndOutput(_ command: String) -> (Int32, String?) { let task = Process() task.launchPath = "/bin/bash" task.arguments = ["-c", command] let pipe = Pipe() task.standardOutput = pipe task.standardError = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8) task.waitUntilExit() return (task.terminationStatus, output) } @discardableResult func runShellWithArgsAndOutput(_ args: String...) -> (Int32, String?) { let task = Process() task.launchPath = "/usr/bin/env" task.arguments = args let pipe = Pipe() task.standardOutput = pipe task.standardError = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8) task.waitUntilExit() return (task.terminationStatus, output) }
使用如下:
let (ret1, output1) = runShellAndOutput("ls -l") if let output11 = output1 { print(output11) } let (ret2, output2) = runShellWithArgsAndOutput("ls", "-l") if let output22 = output2 { print(output2) }
如何解析Matrix的OOM Log
Matrix的OOM Log格式如下,其实就是一个大JSON:
{ "head": { "protocol_ver": 1, "phone": "iPhone10,1", "os_ver": "13.4", "launch_time": 1589361495000, "report_time": 1589362109100, "app_uuid": "" }, "items": [ { "tag": "iOS_MemStat", "info": "", "scene": "", "name": "Malloc 12.54 MiB", "size": 146313216, "count": 1, "stacks": [ { "caller": "f07199ac8a903127b17f0a906ffb0237@84128", "size": 146313216, "count": 1, "frames": [ { "uuid": "a0a7d67af0f3399a8f006f92716d8e6f", "offset": 67308 }, { "uuid": "a0a7d67af0f3399a8f006f92716d8e6f", "offset": 69836 }, { "uuid": "f07199ac8a903127b17f0a906ffb0237", "offset": 84128 }, { "uuid": "b80198f7beb93e79b25c7a27d68bb489", "offset": 14934312 }, { "uuid": "1a46239df2fc34b695bc9f38869f0c85", "offset": 1126304 }, { "uuid": "1a46239df2fc34b695bc9f38869f0c85", "offset": 123584 }, { "uuid": "1a46239df2fc34b695bc9f38869f0c85", "offset": 1135100 }] } ] } ] }
解析的思路其实非常简单,将JSON转为Model,然后根据所需,提取对应的信息即可。
uuid是mach-o的唯一标识,offset则是符号相对于mach-o基地址的偏移量。拿到dSYM文件,使用 atos 命令即可进行符号化。
guard let rawLogModel = MatrixOOMLogParser.parse() else { exit(-1) } print("______ Start to process Matrix OOM Log ...") let group = DispatchGroup() var metaLog = "" for item in bodyInfo.items { guard let stacks = item.stacks else { continue } group.enter() DispatchQueue.global().async { var log = "______ item ______ name: \(item.name), size: \(item.size), count: \(item.count) \n" metaLog += log for stack in stacks { let outputs = stack.frames.map({ (frame: MatrixOOMLogModelFrame) -> String in // let uuid = frame.uuid let offset = frame.offset let instructionAddress = loadAddress + offset let (_, output) = runShellAndOutput("xcrun atos -o \(dwarf) -arch arm64 -l 0x1 \(instructionAddress.hexValue)") return output ?? "" }) log += outputs.joined() print(log) } group.leave() } } group.wait() print("\n\(metaLog)\n") print("______ Finished processing Matrix OOM Log ...")
MatrixOOMLogParser.parse() 就是将JSON转为Model,这里用的就是Swift里边的Codable。
这里有一个需要注意的点,Mac CLI没有Bundle的概念,只有一个bin文件。所以对于原始的JSON文件,只能通过外部bundle的方式来添加。通过 New->Target 单独建立一个bundle。需要在 Xcode -> Build Phases -> Copy Files 中添加该bundle名,然后即可通过 Bundle(url: mockDataBundleURL) 来加载该bundle并获取其中的log文件了。
因为atos的执行时间较长,所以大量的符号化操作会非常耗时。一般来说,这段代码执行六七分钟左右,可以将一个Matrix的OOM Log完全符号化。而符号化之后的记录如何分析,就是另外一个话题了。
参考资料
How do I run an terminal command in a swift script? (e.g. xcodebuild)
到此这篇关于如何使用Swift来实现一个命令行工具的文章就介绍到这了,更多相关Swift 命令行内容请搜索呐喊教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持呐喊教程!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#nhooo.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。