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