如何使用Swift來實現一個命令行工具

本文即簡單介紹瞭如何在Swift中開發命令行工具,以及與Shell命令的交互。水文一篇,不喜勿噴。shell

主要是使用該工具來解析微信的性能監控組件Matrix的OOM Log。swift

基本模塊

這裏,僅簡單介紹了常見的基本模塊。數組

Process

Process類能夠用來打開另一個子進程,並監控其運行狀況。xcode

  1. launchPath:指定了執行路徑。如能夠設置爲 /usr/bin/env ,這個命令能夠用於打印本機上全部的環境變量;也能夠用於執行shell命令,若是你接了參數的話。本文的Demo就用它來執行輸入的命令。
  2. arguments:參數,以數組形式傳遞便可。
  3. launch:調用launch函數便可啓動process,用於執行命令。
  4. waitUntilExit:通常執行Shell命令,須要等待命令返回。
  5. terminationStatus:當前process的結束狀態,正常爲0.
  6. standardOutput:standardOutput對應於終端的標準輸出。standardError則是錯誤輸出。

Pipe

Pipe這個類就是操做系統的管道,在這裏用來接受子進程的輸出。這裏,能夠用於將process的輸出傳遞至管道指定的地方,如一個output變量,或者文件也能夠。bash

  1. fileHandleForReading:pipe從哪裏讀取內容?
  2. fileHandleForWriting:pipe將內容寫到哪裏?

CommandLine

用於獲取腳本參數而已。微信

print(CommandLine.argc) // 2
print(CommandLine.arguments) // ["./test.swift", "hello"]
複製代碼

封裝Shell命令

僅執行Shell命令

這裏提供了兩種調用Shell命令的封裝函數,我的更傾向於第二種,直接將Shell命令及參數封裝成一個字符串傳入便可。app

@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
}
複製代碼

使用以下:async

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徹底符號化。而符號化以後的記錄如何分析,就是另一個話題了。

參考資料

相關文章
相關標籤/搜索