轻量级APP启动信息构建方案
好文推荐:
作者:字节大力智能
转载地址:https://juejin.cn/post/6992744674796503077
背景
在头条的启动框架下,启动任务已经划分的较为明确,而启动时序是启动任务中的关键信息。目前我们获取这些信息的主要手段是看systrace,但直接读systrace存在少量问题:
- systrace在release下少量信息不全,例如IO线程信息,而启动优化的主要评估场景是release
- systrace信息相对较重,可阅读性差,同时对启动任务的阅读的干扰性大
在上述问题的影响下,会添加开发人员排查、验证启动任务问题,以及优化启动任务的难度。
因而本文考虑设计一个轻量级的信息形容、收集与信息重建方案,灵活适应release模式与debug模式,同时添加可阅读性,降低开发人员排查问题的成本。
1 方案设计
轻量级启动信息构建方案主要由三部分组成:
- 启动信息构建:负责提炼关键信息做成新数据结构
- 启动信息收集:负责收集、输出各个任务的信息到重建模块
- 启动信息重建:负责信息构建、输出可视化图形
2 具体模块实现
2.1 启动信息构建
data class InitDataStruct( var startTime: Long = 0, var duration: Long = 0, var currentProcess: String = "", var taskName: String = "")
关键的启动信息主要有这么几个维度:
- 启动时间(归一化)
- 启动耗时
- 启动线程
- 启动名称
而并不关心,即需要剔除掉的任务:
- 非启动任务信息(这并不是说它不重要,只是在启动框架这一环它并不是高优)
- 启动任务stack
Format形如
{"task_name":"class com.xxx.xxxTask","start_time":5,"duration":9,"current_process":"AA xxxThread#4"}
2.2 启动信息收集
因为没接入公司平台(太小),因而考虑就以log的方式输出结果。
大概是希望实现下面的功能,但一个一个加就有点复制粘贴有点太low了
调研了一下有一种AspectJ的做法,可以利用
@PointCut("execution(* com.xxx.xxx.xxxTask.run(*))")
在task附近埋下切入点
利用@Before
、@After
注入切入代码就可。
2.3 启动信息收集与绘制
因为目前是依赖人工进行启动分析,因而我们收集启动信息的手段依赖于Console打印的日志,形如
{"task_name":"class com.xxx.Task","start_time":0,"duration":2,"current_process":"main"}
这里我们直接写个读取工具给他转义一下,让他变成具备可读性的数据结构
# 在Client中以json保存下来的def toInitInfo(json): return InitInfo(json["start_time"], json["duration"], json["current_process"], str(json["task_name"]).split('.')[-1])class InitInfo: #startTime和duration均做了归一化 def __init__(self, startTime, duration, currentProcessName, taskName): self.startTime = startTime self.taskName = taskName self.duration = duration self.currentProcessName = currentProcessName def printitself(self): print("task_name : " + self.taskName) print("\tstartTime : " + str(self.startTime)) print("\tduration : " + str(self.duration)) print("\tcurrentProcessName : " + self.currentProcessName) # 获取task时长 def getNameCombineDuration(self): return self.taskName + " " + str(self.duration) # 获取当前打印的最大长度 def getConstructLen(self): return len(self.getNameCombineDuration()) + 2 def generateFormatStr(self, perTime, perBlank): totalLen = max(3, int(1.0 * perBlank * max(1, self.duration) / perTime)) cntLen = max(0, totalLen - self.getConstructLen()) strr = "|" + (cntLen / 2 + cntLen % 2) * "-" + self.getNameCombineDuration()[0:min(totalLen - 2, len(self.getNameCombineDuration()))]+ cntLen / 2 * "-" + "|" return strr def generateBlank(self, timeNow, perTime, perBlank): strr = max(0, int((self.startTime - timeNow) / perTime) * perBlank) * " " return strr
并将所有task插入到list中,以完成时间作为sort Function
def sortByEnd(initInfo1, initInfo2): return (initInfo1.startTime + initInfo1.duration) <= (initInfo2.startTime + initInfo2.duration)def dealWithList(): for item in line_jsons: if(taskMap.has_key(item.currentProcessName)): taskMap[item.currentProcessName].append(item) else: taskMap[item.currentProcessName] = [] taskMap[item.currentProcessName].append(item)
现在到了问题的核心,我们该采用什么规则把绘图绘制出来,这取决于我们需要得到的信息有哪些:
- 第一种:分析启动任务耗时,可采用相似systrace,横轴为固定的单位时间长度,纵轴是currentProcess
def drawMp(): duraLen = 0 maxLen = 0 # 10ms间隔 currentPerTime = 10 endFile = open("timeline.txt","w") # 先保证起始坐标轴一致 for key in taskMap.keys(): maxLen = max(maxLen, len(key)) # 计算最长字符串 for item in line_jsons: duraLen = max(duraLen, item.getConstructLen()) # 画个坐标轴 xplot = maxLen * " " + " :" for index in range(0, (line_jsons[-1].startTime + line_jsons[-1].duration) / currentPerTime): cntLen = duraLen - 2 - len(str(index * currentPerTime)) xplot += "|" + (cntLen / 2 + cntLen % 2) * "-" + str(index * currentPerTime) + cntLen / 2 * "-" + "|" endFile.write(xplot + "\n") # 画图 for key in taskMap.keys(): strr = key + (maxLen - len(key)) * " " + " :" timeNow = 0 for item in taskMap[key]: item.printitself() strr += item.generateBlank(timeNow, perTime = currentPerTime, perBlank = duraLen) strr += item.generateFormatStr(10, duraLen) timeNow = item.startTime + item.duration strr += "\n" endFile.write(strr) endFile.close()
- 第二种:分析启动任务排布的正当性,即能否存在长尾型的启动路径,这里考虑横轴为离散化后的启动任务时间,纵轴为currentProcess
## 第二种画图法:离散# 离散点阵图duraCordi = []def drawMp2(): # 离散单位区间长度 duraLen = 0 def addBlank(st, ed): return (ed - st) * duraLen * " " def formatString(st, ed, taskName, duraLen): strr = "|" leftBlank = (ed - st) * duraLen - 2 - len(taskName) strr += (leftBlank / 2 + leftBlank % 2) * "-" strr += taskName strr += leftBlank / 2 * "-" + "|" return strr # 先离散 # 最短是 -> |maxLen(xxxTask)| dura = [] filee = open("timeline2.txt","w") for item in line_jsons: duraLen = max(duraLen, len(item.getNameCombineDuration()) + 2) dura.append(item.startTime) dura.append(item.startTime + item.duration) duraCordi = list(set(dura)) duraCordi.sort() print(duraCordi) #再遍历塞值进去 maxLen = 0 for key in taskMap.keys(): maxLen = max(maxLen, len(key)) for key in taskMap.keys(): currentIndex = 0 strr = key + (maxLen - len(key)) * " " + " :" for item in taskMap[key]: stIndex = bisect.bisect_left(duraCordi, item.startTime) edIndex = bisect.bisect_left(duraCordi, item.startTime + max(item.duration, 1)) strr += addBlank(currentIndex, stIndex) strr += formatString(stIndex, edIndex, item.getNameCombineDuration(), duraLen = duraLen) currentIndex = edIndex strr += "\n" filee.write(strr) filee.close()
3 效果比照
- 第一种启动耗时为单位的
- 第二种启动时间离散化后的
比方我们需要分析启动任务的排布能否正当,即可以看第二种图像,可以看到主线程启动任务较多,可能存在肯定的长尾效应。
相比systrace,更为轻量
说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 轻量级APP启动信息构建方案
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 轻量级APP启动信息构建方案