matrix icon indicating copy to clipboard operation
matrix copied to clipboard

iOS的out-of-memory日志如何翻译成堆栈信息

Open bart1989 opened this issue 6 years ago • 18 comments

iOS的out-of-memory日志中的堆栈信息如何翻译成堆栈信息? 是否提供了如OOMDetector中的translate_oom.py工具。

bart1989 avatar Apr 24 '19 08:04 bart1989

可以提供,脚本还在整理,现在还有很多不通用的地方。

chzhij5 avatar Apr 29 '19 03:04 chzhij5

您好,麻烦问下,现在可以提供翻译的脚本了么?

qinhaibo avatar Jul 23 '19 02:07 qinhaibo

嗯.. 我“失忆”了,小本本已经记下,这周我找相关同学要到脚本加上去,

chzhij5 avatar Jul 29 '19 03:07 chzhij5

同问下翻译脚本,或者告知怎么翻译?谢谢

gelinxiao avatar Aug 06 '19 02:08 gelinxiao

@bart1989 @qinhaibo @gelinxiao 看了我们后台同学的翻译脚本,极其不举普适性。正在催他们搞一个通用的。时间不掌握在我手上,T_T.

参考这里内存监控的格式:

https://github.com/Tencent/matrix/wiki/Matrix-for-iOS-macOS-%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E

matrix/matrix-iOS/script 里也有卡顿和耗电堆栈解析脚本,结合看看。看看能不能先自己解析一下,或者提个PR,帮我们完善一下解析脚本!!希望谅解。

chzhij5 avatar Aug 08 '19 14:08 chzhij5

@chzhij5 请教个问题,内存监控只有在超出内存崩溃的情况下,才会记录并上报吗?我这边集成到项目中只有这种情况下才上报,内存监控配置我采用的是defaultConfig,是我配置的有问题吗?

gelinxiao avatar Aug 15 '19 08:08 gelinxiao

@chzhij5 请教个问题,内存监控只有在超出内存崩溃的情况下,才会记录并上报吗?我这边集成到项目中只有这种情况下才上报,内存监控配置我采用的是defaultConfig,是我配置的有问题吗?

内存监控开启后会一直记录。

https://github.com/Tencent/matrix/blob/8e80e452d7100be9e67d4f6d9678aa4bcf88b73c/matrix/matrix-iOS/Matrix/WCMemoryStat/MemoryStatPlugin/WCMemoryStatPlugin.mm#L89 参考这里。现在是设定只有检测到爆内存才会上报。

chzhij5 avatar Aug 20 '19 10:08 chzhij5

@bart1989 @qinhaibo @gelinxiao 看了我们后台同学的翻译脚本,极其不举普适性。正在催他们搞一个通用的。时间不掌握在我手上,T_T.

参考这里内存监控的格式:

https://github.com/Tencent/matrix/wiki/Matrix-for-iOS-macOS-%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E

matrix/matrix-iOS/script 里也有卡顿和耗电堆栈解析脚本,结合看看。看看能不能先自己解析一下,或者提个PR,帮我们完善一下解析脚本!!希望谅解。

请问下当前是没有弄这个的计划吗

SnailLife avatar Sep 04 '20 07:09 SnailLife

@chzhij5 请教个问题,内存监控只有在超出内存崩溃的情况下,才会记录并上报吗?我这边集成到项目中只有这种情况下才上报,内存监控配置我采用的是defaultConfig,是我配置的有问题吗?

请问下这块你们是怎么处理的

SnailLife avatar Sep 17 '20 02:09 SnailLife

@bart1989 @qinhaibo @gelinxiao 看了我们后台同学的翻译脚本,极其不举普适性。正在催他们搞一个通用的。时间不掌握在我手上,T_T. 参考这里内存监控的格式: https://github.com/Tencent/matrix/wiki/Matrix-for-iOS-macOS-%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E matrix/matrix-iOS/script 里也有卡顿和耗电堆栈解析脚本,结合看看。看看能不能先自己解析一下,或者提个PR,帮我们完善一下解析脚本!!希望谅解。

请问下当前是没有弄这个的计划吗

同问

liu3399shuai avatar Dec 03 '20 12:12 liu3399shuai

@chzhij5 请教个问题,内存监控只有在超出内存崩溃的情况下,才会记录并上报吗?我这边集成到项目中只有这种情况下才上报,内存监控配置我采用的是defaultConfig,是我配置的有问题吗?

请问 内存这块json数据 你解析出来了

SnailLife avatar Jun 23 '21 09:06 SnailLife

您好,可以分享一下你们解析out-of-memory的日志的脚本吗,不通用也没事,给大家提供一下思路

riceForChina avatar Nov 05 '21 06:11 riceForChina

您好,可以分享一下你们解析out-of-memory的日志的脚本吗

ywda avatar May 24 '22 01:05 ywda

写了一个简单的解析脚本,结果如下

image

脚本如下

#coding=utf-8

import os;
import re;
import json;
from optparse import OptionParser
from util import *

class OOMAnalyzer(object):

    def __init__(self, inputDir, logFileUrl, appDsymPath=None):
        self.inputDir = inputDir
        self.logFileUrl = logFileUrl
        self.appDsymPath = appDsymPath

    def run(self):
        if self.logFileUrl.find("http:") >= 0 or self.logFileUrl.find("https:") >= 0:
            # 下载日志文件
            downloadFilePath = "%s/%s" % (self.inputDir, "OOM.json")
            downloadCMD = "wget %s -O %s" % (self.logFileUrl, downloadFilePath)
            os.system(downloadCMD)
            logFilePath = "%s/%s" % (self.inputDir, "OOM.json")
        else:
            logFilePath = self.logFileUrl

        # 转换trace文件为json文件
        print "========转换log文件为json对象"
        f = open(logFilePath, mode="r")
        contentStr = f.read()
        logJson = json.loads(contentStr)
        f.close()

        # 解析数据
        self.parseLog(logJson)

        # 保存数据
        # outputPath = "%s/%s" % (self.inputDir, "Loads.xlsx")
        # if os.path.exists(outputPath):
        #     os.remove(outputPath)
        # self.savePreviewInfoToExcel(logJson, outputPath)
        # self.saveLogToExcelWithGroup(logJson, groupedData, outputPath)
        # self.saveLogToExcel(logJson, outputPath)

        print "Done"

    def parseLog(self, logJson):
        # 解析获取所有的Offsets
        uuid2Offsets = self.parseOffsets(logJson)

        # Head
        head = logJson["head"]
        app_uuid = head["app_uuid"]

        # 解析当前APP的符号
        curAppOffsets = uuid2Offsets.get(app_uuid)
        if curAppOffsets is None:
            return
        address2SymMap = self.symbolictedAddress(self.appDsymPath, curAppOffsets)
        print "address2SymMap=%s" % (address2SymMap)

        # 重新构造结果
        self.recreateLog(logJson, app_uuid, address2SymMap)

        # 保存结果
        outputPath = "%s/%s" % (self.inputDir, "OOM-Symbolicated.json")
        fo = open(outputPath, "w")
        fo.write(json.dumps(logJson))
        fo.close()

        print "Done"

    def parseOffsets(self, logJson):
        # Items 处理
        uuid2Offsets = {}
        datas = logJson["items"]
        for dataItemIdx in range(len(datas)):
            dataItem = datas[dataItemIdx]

            stacks = dataItem.get("stacks")
            if stacks is None:
                continue

            for stackIdx in range(len(stacks)):
                stack = stacks[stackIdx]
                frames = stack["frames"]
                for frameIdx in range(len(frames)):
                    frame = frames[frameIdx]
                    uuid = frame["uuid"]
                    offset = frame["offset"]

                    uuids = uuid2Offsets.get(uuid)
                    if uuids is None:
                        uuids = []
                        uuid2Offsets[uuid] = uuids

                    try:
                        uuids.index(offset)
                    except ValueError:
                        uuids.append(offset)
        print "处理结果 uuid 数量 %s" % (len(uuid2Offsets.keys()))
        return uuid2Offsets

    def symbolictedAddress(self, dsymPath, addresses, loadAddress = "", isSlide = True):
        allAddressStr = ""
        for address in addresses:
            if isSlide:
                addressInt = int(address) + 0x100000000
                allAddressStr += ("%s " % hex(addressInt))
            else:
                allAddressStr += ("%s " % address)

        if isSlide:
            cmd = "atos -o %s -l %s %s " % (dsymPath, "0x100000000", allAddressStr)
        else:
            cmd = "atos -o %s -l %s %s " % (dsymPath, loadAddress, allAddressStr)

        # res = os.system()
        a = os.popen(cmd)
        res = a.read()

        symbols = res.split("\n")

        address2SymMap = {}
        for i in range(len(addresses)):
            address2SymMap[addresses[i]] = symbols[i]

        return address2SymMap


    def recreateLog(self, logJson, destUuid, address2SymMap):
        # Items 处理
        uuid2Offsets = {}
        datas = logJson["items"]
        for dataItemIdx in range(len(datas)):
            dataItem = datas[dataItemIdx]

            # Size
            size = dataItem.get("size")
            sizeStr = readableStrFromSize(size)
            if sizeStr is not None:
                dataItem["size"] = sizeStr

            # Stacks
            stacks = dataItem.get("stacks")
            if stacks is None:
                continue

            for stackIdx in range(len(stacks)):
                stack = stacks[stackIdx]
                frames = stack["frames"]
                symbolictedFrames = []
                for frameIdx in range(len(frames)):
                    frame = frames[frameIdx]
                    uuid = frame["uuid"]
                    offset = frame["offset"]
                    if destUuid == uuid:
                        symbolictedFrame = address2SymMap.get(offset)
                        if symbolictedFrame is not None:
                            symbolictedFrames.append(symbolictedFrame)
                stack["frames"] = symbolictedFrames

def main(argv):
    parser = OptionParser('usage: %prog -d <directory_path> -r <远程服务器Host>')
    parser.add_option("-d", "--dir", dest="dir", help="包含.appletrace的文件夹")
    parser.add_option("-u", "--file-url", dest="fileUrl", help="日志文件路径")

    (options, args) = parser.parse_args()
    if options.dir is None:
        parser.print_help();
        return

    if options.remoteHost is None:
        parser.print_help();
        return

    processor = OOMAnalyzer(options.dir, options.fileUrl)
    processor.run()


if __name__ == "__main__":
    main()

测试代码:

    def test_OOMAnalyzer(self):
        inputDir = "/Users/aron/Downloads/trace/global_943_2"
        logFileUrl = "%s/download?path=%%2FLibrary%%2FBBPerfData%%2FOOM.json" % ("http://10.1.17.250")
        dsymPath = "/Users/aron/Library/Developer/Xcode/DerivedData/MatrixDemo-hddrwbozwcpgmucghwqbgeurgvbg/Build/Products/Debug-iphoneos/MatrixDemo.app/MatrixDemo"
        processor = OOMAnalyzer(inputDir, logFileUrl, appDsymPath=dsymPath)
        processor.run()

flypigrmvb avatar Jun 28 '22 07:06 flypigrmvb

写了一个简单的解析脚本,结果如下

image

脚本如下

#coding=utf-8

import os;
import re;
import json;
from optparse import OptionParser
from util import *

class OOMAnalyzer(object):

    def __init__(self, inputDir, logFileUrl, appDsymPath=None):
        self.inputDir = inputDir
        self.logFileUrl = logFileUrl
        self.appDsymPath = appDsymPath

    def run(self):
        if self.logFileUrl.find("http:") >= 0 or self.logFileUrl.find("https:") >= 0:
            # 下载日志文件
            downloadFilePath = "%s/%s" % (self.inputDir, "OOM.json")
            downloadCMD = "wget %s -O %s" % (self.logFileUrl, downloadFilePath)
            os.system(downloadCMD)
            logFilePath = "%s/%s" % (self.inputDir, "OOM.json")
        else:
            logFilePath = self.logFileUrl

        # 转换trace文件为json文件
        print "========转换log文件为json对象"
        f = open(logFilePath, mode="r")
        contentStr = f.read()
        logJson = json.loads(contentStr)
        f.close()

        # 解析数据
        self.parseLog(logJson)

        # 保存数据
        # outputPath = "%s/%s" % (self.inputDir, "Loads.xlsx")
        # if os.path.exists(outputPath):
        #     os.remove(outputPath)
        # self.savePreviewInfoToExcel(logJson, outputPath)
        # self.saveLogToExcelWithGroup(logJson, groupedData, outputPath)
        # self.saveLogToExcel(logJson, outputPath)

        print "Done"

    def parseLog(self, logJson):
        # 解析获取所有的Offsets
        uuid2Offsets = self.parseOffsets(logJson)

        # Head
        head = logJson["head"]
        app_uuid = head["app_uuid"]

        # 解析当前APP的符号
        curAppOffsets = uuid2Offsets.get(app_uuid)
        if curAppOffsets is None:
            return
        address2SymMap = self.symbolictedAddress(self.appDsymPath, curAppOffsets)
        print "address2SymMap=%s" % (address2SymMap)

        # 重新构造结果
        self.recreateLog(logJson, app_uuid, address2SymMap)

        # 保存结果
        outputPath = "%s/%s" % (self.inputDir, "OOM-Symbolicated.json")
        fo = open(outputPath, "w")
        fo.write(json.dumps(logJson))
        fo.close()

        print "Done"

    def parseOffsets(self, logJson):
        # Items 处理
        uuid2Offsets = {}
        datas = logJson["items"]
        for dataItemIdx in range(len(datas)):
            dataItem = datas[dataItemIdx]

            stacks = dataItem.get("stacks")
            if stacks is None:
                continue

            for stackIdx in range(len(stacks)):
                stack = stacks[stackIdx]
                frames = stack["frames"]
                for frameIdx in range(len(frames)):
                    frame = frames[frameIdx]
                    uuid = frame["uuid"]
                    offset = frame["offset"]

                    uuids = uuid2Offsets.get(uuid)
                    if uuids is None:
                        uuids = []
                        uuid2Offsets[uuid] = uuids

                    try:
                        uuids.index(offset)
                    except ValueError:
                        uuids.append(offset)
        print "处理结果 uuid 数量 %s" % (len(uuid2Offsets.keys()))
        return uuid2Offsets

    def symbolictedAddress(self, dsymPath, addresses, loadAddress = "", isSlide = True):
        allAddressStr = ""
        for address in addresses:
            if isSlide:
                addressInt = int(address) + 0x100000000
                allAddressStr += ("%s " % hex(addressInt))
            else:
                allAddressStr += ("%s " % address)

        if isSlide:
            cmd = "atos -o %s -l %s %s " % (dsymPath, "0x100000000", allAddressStr)
        else:
            cmd = "atos -o %s -l %s %s " % (dsymPath, loadAddress, allAddressStr)

        # res = os.system()
        a = os.popen(cmd)
        res = a.read()

        symbols = res.split("\n")

        address2SymMap = {}
        for i in range(len(addresses)):
            address2SymMap[addresses[i]] = symbols[i]

        return address2SymMap


    def recreateLog(self, logJson, destUuid, address2SymMap):
        # Items 处理
        uuid2Offsets = {}
        datas = logJson["items"]
        for dataItemIdx in range(len(datas)):
            dataItem = datas[dataItemIdx]

            # Size
            size = dataItem.get("size")
            sizeStr = readableStrFromSize(size)
            if sizeStr is not None:
                dataItem["size"] = sizeStr

            # Stacks
            stacks = dataItem.get("stacks")
            if stacks is None:
                continue

            for stackIdx in range(len(stacks)):
                stack = stacks[stackIdx]
                frames = stack["frames"]
                symbolictedFrames = []
                for frameIdx in range(len(frames)):
                    frame = frames[frameIdx]
                    uuid = frame["uuid"]
                    offset = frame["offset"]
                    if destUuid == uuid:
                        symbolictedFrame = address2SymMap.get(offset)
                        if symbolictedFrame is not None:
                            symbolictedFrames.append(symbolictedFrame)
                stack["frames"] = symbolictedFrames

def main(argv):
    parser = OptionParser('usage: %prog -d <directory_path> -r <远程服务器Host>')
    parser.add_option("-d", "--dir", dest="dir", help="包含.appletrace的文件夹")
    parser.add_option("-u", "--file-url", dest="fileUrl", help="日志文件路径")

    (options, args) = parser.parse_args()
    if options.dir is None:
        parser.print_help();
        return

    if options.remoteHost is None:
        parser.print_help();
        return

    processor = OOMAnalyzer(options.dir, options.fileUrl)
    processor.run()


if __name__ == "__main__":
    main()

测试代码:

def test_OOMAnalyzer(self):
    inputDir = "/Users/aron/Downloads/trace/global_943_2"
    logFileUrl = "%s/download?path=%%2FLibrary%%2FBBPerfData%%2FOOM.json" % ("http://10.1.17.250")
    dsymPath = "/Users/aron/Library/Developer/Xcode/DerivedData/MatrixDemo-hddrwbozwcpgmucghwqbgeurgvbg/Build/Products/Debug-iphoneos/MatrixDemo.app/MatrixDemo"
    processor = OOMAnalyzer(inputDir, logFileUrl, appDsymPath=dsymPath)
    processor.run()

readableStrFromSize 应该是你的工具里面的吧,可以分享出来吗?

lqwang521 avatar Apr 13 '23 10:04 lqwang521

@lqwang521 方法如下

def readableStrFromSize(size):
    kb = size * 1.0 / 1024
    mb = kb / 1024
    gb = mb / 1024
    if gb > 1:
        return "%.2f GB" % (gb)
    elif mb > 1:
        return "%.2f MB" % (mb)
    elif kb > 1:
        return "%.2f kB" % (kb)
    else:
        return "%.2f B" % (size)

flypigrmvb avatar Apr 17 '23 01:04 flypigrmvb

写了一个简单的解析脚本,结果如下

image

脚本如下

#coding=utf-8

import os;
import re;
import json;
from optparse import OptionParser
from util import *

class OOMAnalyzer(object):

    def __init__(self, inputDir, logFileUrl, appDsymPath=None):
        self.inputDir = inputDir
        self.logFileUrl = logFileUrl
        self.appDsymPath = appDsymPath

    def run(self):
        if self.logFileUrl.find("http:") >= 0 or self.logFileUrl.find("https:") >= 0:
            # 下载日志文件
            downloadFilePath = "%s/%s" % (self.inputDir, "OOM.json")
            downloadCMD = "wget %s -O %s" % (self.logFileUrl, downloadFilePath)
            os.system(downloadCMD)
            logFilePath = "%s/%s" % (self.inputDir, "OOM.json")
        else:
            logFilePath = self.logFileUrl

        # 转换trace文件为json文件
        print "========转换log文件为json对象"
        f = open(logFilePath, mode="r")
        contentStr = f.read()
        logJson = json.loads(contentStr)
        f.close()

        # 解析数据
        self.parseLog(logJson)

        # 保存数据
        # outputPath = "%s/%s" % (self.inputDir, "Loads.xlsx")
        # if os.path.exists(outputPath):
        #     os.remove(outputPath)
        # self.savePreviewInfoToExcel(logJson, outputPath)
        # self.saveLogToExcelWithGroup(logJson, groupedData, outputPath)
        # self.saveLogToExcel(logJson, outputPath)

        print "Done"

    def parseLog(self, logJson):
        # 解析获取所有的Offsets
        uuid2Offsets = self.parseOffsets(logJson)

        # Head
        head = logJson["head"]
        app_uuid = head["app_uuid"]

        # 解析当前APP的符号
        curAppOffsets = uuid2Offsets.get(app_uuid)
        if curAppOffsets is None:
            return
        address2SymMap = self.symbolictedAddress(self.appDsymPath, curAppOffsets)
        print "address2SymMap=%s" % (address2SymMap)

        # 重新构造结果
        self.recreateLog(logJson, app_uuid, address2SymMap)

        # 保存结果
        outputPath = "%s/%s" % (self.inputDir, "OOM-Symbolicated.json")
        fo = open(outputPath, "w")
        fo.write(json.dumps(logJson))
        fo.close()

        print "Done"

    def parseOffsets(self, logJson):
        # Items 处理
        uuid2Offsets = {}
        datas = logJson["items"]
        for dataItemIdx in range(len(datas)):
            dataItem = datas[dataItemIdx]

            stacks = dataItem.get("stacks")
            if stacks is None:
                continue

            for stackIdx in range(len(stacks)):
                stack = stacks[stackIdx]
                frames = stack["frames"]
                for frameIdx in range(len(frames)):
                    frame = frames[frameIdx]
                    uuid = frame["uuid"]
                    offset = frame["offset"]

                    uuids = uuid2Offsets.get(uuid)
                    if uuids is None:
                        uuids = []
                        uuid2Offsets[uuid] = uuids

                    try:
                        uuids.index(offset)
                    except ValueError:
                        uuids.append(offset)
        print "处理结果 uuid 数量 %s" % (len(uuid2Offsets.keys()))
        return uuid2Offsets

    def symbolictedAddress(self, dsymPath, addresses, loadAddress = "", isSlide = True):
        allAddressStr = ""
        for address in addresses:
            if isSlide:
                addressInt = int(address) + 0x100000000
                allAddressStr += ("%s " % hex(addressInt))
            else:
                allAddressStr += ("%s " % address)

        if isSlide:
            cmd = "atos -o %s -l %s %s " % (dsymPath, "0x100000000", allAddressStr)
        else:
            cmd = "atos -o %s -l %s %s " % (dsymPath, loadAddress, allAddressStr)

        # res = os.system()
        a = os.popen(cmd)
        res = a.read()

        symbols = res.split("\n")

        address2SymMap = {}
        for i in range(len(addresses)):
            address2SymMap[addresses[i]] = symbols[i]

        return address2SymMap


    def recreateLog(self, logJson, destUuid, address2SymMap):
        # Items 处理
        uuid2Offsets = {}
        datas = logJson["items"]
        for dataItemIdx in range(len(datas)):
            dataItem = datas[dataItemIdx]

            # Size
            size = dataItem.get("size")
            sizeStr = readableStrFromSize(size)
            if sizeStr is not None:
                dataItem["size"] = sizeStr

            # Stacks
            stacks = dataItem.get("stacks")
            if stacks is None:
                continue

            for stackIdx in range(len(stacks)):
                stack = stacks[stackIdx]
                frames = stack["frames"]
                symbolictedFrames = []
                for frameIdx in range(len(frames)):
                    frame = frames[frameIdx]
                    uuid = frame["uuid"]
                    offset = frame["offset"]
                    if destUuid == uuid:
                        symbolictedFrame = address2SymMap.get(offset)
                        if symbolictedFrame is not None:
                            symbolictedFrames.append(symbolictedFrame)
                stack["frames"] = symbolictedFrames

def main(argv):
    parser = OptionParser('usage: %prog -d <directory_path> -r <远程服务器Host>')
    parser.add_option("-d", "--dir", dest="dir", help="包含.appletrace的文件夹")
    parser.add_option("-u", "--file-url", dest="fileUrl", help="日志文件路径")

    (options, args) = parser.parse_args()
    if options.dir is None:
        parser.print_help();
        return

    if options.remoteHost is None:
        parser.print_help();
        return

    processor = OOMAnalyzer(options.dir, options.fileUrl)
    processor.run()


if __name__ == "__main__":
    main()

测试代码:

    def test_OOMAnalyzer(self):
        inputDir = "/Users/aron/Downloads/trace/global_943_2"
        logFileUrl = "%s/download?path=%%2FLibrary%%2FBBPerfData%%2FOOM.json" % ("http://10.1.17.250")
        dsymPath = "/Users/aron/Library/Developer/Xcode/DerivedData/MatrixDemo-hddrwbozwcpgmucghwqbgeurgvbg/Build/Products/Debug-iphoneos/MatrixDemo.app/MatrixDemo"
        processor = OOMAnalyzer(inputDir, logFileUrl, appDsymPath=dsymPath)
        processor.run()

我想请问下这个json文件是哪里产生的?

suddenly1990 avatar Feb 20 '24 07:02 suddenly1990