• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/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#
17"""Trace parser for f2fs traces."""
18
19import collections
20import re
21
22# 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]+)"
24
25# 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]+)"
27
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
176