blob: c47b6f1da44ef49b9d6a9bb31515a48b1b841fff [file] [log] [blame]
Lakshman Annadorai3c8f88e2022-02-16 16:24:51 -08001#!/usr/bin/env python
2#
3# Copyright (C) 2022 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
Jaegeuk Kimd4ac37c2022-08-19 17:22:04 -070017"""Trace parser for f2fs traces."""
Lakshman Annadorai3c8f88e2022-02-16 16:24:51 -080018
19import collections
20import re
21
Jaegeuk Kimd4ac37c2022-08-19 17:22:04 -070022# ex) bt_stack_manage-21277 [000] .... 5879.043608: f2fs_datawrite_start: entry_name /misc/bluedroid/bt_config.bak.new, offset 0, bytes 408, cmdline bt_stack_manage, pid 21277, i_size 0, ino 9103
23RE_WRITE_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+f2fs_datawrite_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
Lakshman Annadorai3c8f88e2022-02-16 16:24:51 -080024
Jaegeuk Kimd4ac37c2022-08-19 17:22:04 -070025# ex) dumpsys-21321 [001] .... 5877.599324: f2fs_dataread_start: entry_name /system/lib64/libbinder.so, offset 311296, bytes 4096, cmdline dumpsys, pid 21321, i_size 848848, ino 2397
26RE_READ_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+f2fs_dataread_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
Lakshman Annadorai3c8f88e2022-02-16 16:24:51 -080027
28MIN_PID_BYTES = 1024 * 1024 # 1 MiB
29SMALL_FILE_BYTES = 1024 # 1 KiB
30
31
32class ProcessTrace:
33
34 def __init__(self, cmdLine, filename, numBytes):
35 self.cmdLine = cmdLine
36 self.totalBytes = numBytes
37 self.bytesByFiles = {filename: numBytes}
38
39 def add_file_trace(self, filename, numBytes):
40 self.totalBytes += numBytes
41 if filename in self.bytesByFiles:
42 self.bytesByFiles[filename] += numBytes
43 else:
44 self.bytesByFiles[filename] = numBytes
45
46 def dump(self, mode, outputFile):
47 smallFileCnt = 0
48 smallFileBytes = 0
49 for _, numBytes in self.bytesByFiles.items():
50 if numBytes < SMALL_FILE_BYTES:
51 smallFileCnt += 1
52 smallFileBytes += numBytes
53
54 if (smallFileCnt != 0):
55 outputFile.write(
56 "Process: {}, Traced {} KB: {}, Small file count: {}, Small file KB: {}\n"
57 .format(self.cmdLine, mode, to_kib(self.totalBytes), smallFileCnt,
58 to_kib(smallFileBytes)))
59
60 else:
61 outputFile.write("Process: {}, Traced {} KB: {}\n".format(
62 self.cmdLine, mode, to_kib(self.totalBytes)))
63
64 if (smallFileCnt == len(self.bytesByFiles)):
65 return
66
67 sortedEntries = collections.OrderedDict(
68 sorted(
69 self.bytesByFiles.items(), key=lambda item: item[1], reverse=True))
70
71 for i in range(len(sortedEntries)):
72 filename, numBytes = sortedEntries.popitem(last=False)
73 if numBytes < SMALL_FILE_BYTES:
74 # Entries are sorted by bytes. So, break on the first small file entry.
75 break
76
77 outputFile.write("File: {}, {} KB: {}\n".format(filename, mode,
78 to_kib(numBytes)))
79
80
81class UidTrace:
82
83 def __init__(self, uid, cmdLine, filename, numBytes):
84 self.uid = uid
85 self.packageName = ""
86 self.totalBytes = numBytes
87 self.traceByProcess = {cmdLine: ProcessTrace(cmdLine, filename, numBytes)}
88
89 def add_process_trace(self, cmdLine, filename, numBytes):
90 self.totalBytes += numBytes
91 if cmdLine in self.traceByProcess:
92 self.traceByProcess[cmdLine].add_file_trace(filename, numBytes)
93 else:
94 self.traceByProcess[cmdLine] = ProcessTrace(cmdLine, filename, numBytes)
95
96 def dump(self, mode, outputFile):
97 outputFile.write("Traced {} KB: {}\n\n".format(mode,
98 to_kib(self.totalBytes)))
99
100 if self.totalBytes < MIN_PID_BYTES:
101 return
102
103 sortedEntries = collections.OrderedDict(
104 sorted(
105 self.traceByProcess.items(),
106 key=lambda item: item[1].totalBytes,
107 reverse=True))
108 totalEntries = len(sortedEntries)
109 for i in range(totalEntries):
110 _, processTrace = sortedEntries.popitem(last=False)
111 if processTrace.totalBytes < MIN_PID_BYTES:
112 # Entries are sorted by bytes. So, break on the first small PID entry.
113 break
114
115 processTrace.dump(mode, outputFile)
116 if i < totalEntries - 1:
117 outputFile.write("\n")
118
119
120class AndroidFsParser:
121
122 def __init__(self, re_string, uidProcessMapper):
123 self.traceByUid = {} # Key: uid, Value: UidTrace
124 if (re_string == RE_WRITE_START):
125 self.mode = "write"
126 else:
127 self.mode = "read"
128 self.re_matcher = re.compile(re_string)
129 self.uidProcessMapper = uidProcessMapper
130 self.totalBytes = 0
131
132 def parse(self, line):
133 match = self.re_matcher.match(line)
134 if not match:
135 return False
136 try:
137 self.do_parse_start(line, match)
138 except Exception:
139 print("cannot parse: {}".format(line))
140 raise
141 return True
142
143 def do_parse_start(self, line, match):
144 pid = int(match.group(1))
145 # start_time = float(match.group(2)) * 1000 #ms
146 filename = match.group(3)
147 # offset = int(match.group(4))
148 numBytes = int(match.group(5))
149 cmdLine = match.group(6)
150 pid = int(match.group(7))
151 # isize = int(match.group(8))
152 # ino = int(match.group(9))
153 self.totalBytes += numBytes
154 uid = self.uidProcessMapper.get_uid(cmdLine, pid)
155
156 if uid in self.traceByUid:
157 self.traceByUid[uid].add_process_trace(cmdLine, filename, numBytes)
158 else:
159 self.traceByUid[uid] = UidTrace(uid, cmdLine, filename, numBytes)
160
161 def dumpTotal(self, outputFile):
162 if self.totalBytes > 0:
163 outputFile.write("Traced system-wide {} KB: {}\n\n".format(
164 self.mode, to_kib(self.totalBytes)))
165
166 def dump(self, uid, outputFile):
167 if uid not in self.traceByUid:
168 return
169
170 uidTrace = self.traceByUid[uid]
171 uidTrace.dump(self.mode, outputFile)
172
173
174def to_kib(bytes):
175 return bytes / 1024