1# Copyright 2017 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Parser and ranker for dumpsys storaged output. 16 17This module parses output from dumpsys storaged by ranking uids based on 18their io usage measured in 8 different stats. It must be provided the input 19file through command line argument -i/--input. 20 21For more details, see: 22 $ python ranker.py -h 23 24Example: 25 $ python ranker.py -i io.txt -o output.txt -u 20 -cnt 26""" 27 28import argparse 29import sys 30 31IO_NAMES = ["[READ][FOREGROUND][CHARGER_OFF]", 32 "[WRITE][FOREGROUND][CHARGER_OFF]", 33 "[READ][BACKGROUND][CHARGER_OFF]", 34 "[WRITE][BACKGROUND][CHARGER_OFF]", 35 "[READ][FOREGROUND][CHARGER_ON]", 36 "[WRITE][FOREGROUND][CHARGER_ON]", 37 "[READ][BACKGROUND][CHARGER_ON]", 38 "[WRITE][BACKGROUND][CHARGER_ON]"] 39 40 41def get_args(): 42 """Get arguments from command line. 43 44 The only required argument is input file. 45 46 Returns: 47 Args containing cmdline arguments 48 """ 49 50 parser = argparse.ArgumentParser() 51 parser.add_argument("-i", "--input", dest="input", required="true", 52 help="input io FILE, must provide", metavar="FILE") 53 parser.add_argument("-o", "--output", dest="output", default="stdout", 54 help="output FILE, default to stdout", metavar="FILE") 55 parser.add_argument("-u", "--uidcnt", dest="uidcnt", type=int, default=10, 56 help="set number of uids to display for each rank, " 57 "default 10") 58 parser.add_argument("-c", "--combine", dest="combine", default=False, 59 action="store_true", help="add io stats for same uids, " 60 "default to take io stats of last appearing uids") 61 parser.add_argument("-n", "--native", dest="native", default=False, 62 action="store_true", help="only include native apps in " 63 "ranking, default to include all apps") 64 parser.add_argument("-t", "--task", dest="task", default=False, 65 action="store_true", help="display task io under uids, " 66 "default to not display tasks") 67 return parser.parse_args() 68 69 70def is_number(word): 71 try: 72 int(word) 73 return True 74 except ValueError: 75 return False 76 77 78def combine_or_filter(args): 79 """Parser for io input. 80 81 Either args.combine io stats for the same uids 82 or take the io stats for the last uid and ignore 83 the same uids before it. 84 85 If task is required, store task ios along with uid 86 for later display. 87 88 Returns: 89 The structure for the return value uids is as follows: 90 uids: {uid -> [UID_STATS, TASK_STATS(optional)]} 91 UID_STATS: [io1, io2, ..., io8] 92 TASK_STATS: {task_name -> [io1, io2, ..., io8]} 93 """ 94 fin = open(args.input, "r") 95 uids = {} 96 cur_uid = 0 97 task_enabled = args.task 98 for line in fin: 99 words = line.split() 100 if words[0] == "->": 101 # task io 102 if not task_enabled: 103 continue 104 # get task command line 105 i = len(words) - 8 106 task = " ".join(words[1:i]) 107 if task in uids[cur_uid][1]: 108 task_io = uids[cur_uid][1][task] 109 for j in range(8): 110 task_io[j] += long(words[i+j]) 111 else: 112 task_io = [] 113 for j in range(8): 114 task_io.append(long(words[i+j])) 115 uids[cur_uid][1][task] = task_io 116 117 elif len(words) > 8: 118 if not is_number(words[0]) and args.native: 119 # uid not requested, ignore its tasks as well 120 task_enabled = False 121 continue 122 task_enabled = args.task 123 i = len(words) - 8 124 uid = " ".join(words[:i]) 125 if uid in uids and args.combine: 126 uid_io = uids[uid][0] 127 for j in range(8): 128 uid_io[j] += long(words[i+j]) 129 uids[uid][0] = uid_io 130 else: 131 uid_io = [long(words[i+j]) for j in range(8)] 132 uids[uid] = [uid_io] 133 if task_enabled: 134 uids[uid].append({}) 135 cur_uid = uid 136 137 return uids 138 139 140def rank_uids(uids): 141 """Sort uids based on eight different io stats. 142 143 Returns: 144 uid_rank is a 2d list of tuples: 145 The first dimension represent the 8 different io stats. 146 The second dimension is a sorted list of tuples by tup[0], 147 each tuple is a uid's perticular stat at the first dimension and the uid. 148 """ 149 uid_rank = [[(uids[uid][0][i], uid) for uid in uids] for i in range(8)] 150 for i in range(8): 151 uid_rank[i].sort(key=lambda tup: tup[0], reverse=True) 152 return uid_rank 153 154 155def display_uids(uid_rank, uids, args): 156 """Display ranked uid io, along with task io if specified.""" 157 fout = sys.stdout 158 if args.output != "stdout": 159 fout = open(args.output, "w") 160 161 for i in range(8): 162 fout.write("RANKING BY " + IO_NAMES[i] + "\n") 163 for j in range(min(args.uidcnt, len(uid_rank[0]))): 164 uid = uid_rank[i][j][1] 165 uid_stat = " ".join([str(uid_io) for uid_io in uids[uid][0]]) 166 fout.write(uid + " " + uid_stat + "\n") 167 if args.task: 168 for task in uids[uid][1]: 169 task_stat = " ".join([str(task_io) for task_io in uids[uid][1][task]]) 170 fout.write("-> " + task + " " + task_stat + "\n") 171 fout.write("\n") 172 173 174def main(): 175 args = get_args() 176 uids = combine_or_filter(args) 177 uid_rank = rank_uids(uids) 178 display_uids(uid_rank, uids, args) 179 180if __name__ == "__main__": 181 main() 182