import repository from arizona
[raven.git] / apps / gacks / gackswatcher.py
1 import os
2 from optparse import OptionParser
3 import sys
4 import time
5
6 PROC_DIR="/proc"
7
8 CGROUP_NOGROUP="nogroup"
9
10 DEFAULT_DELAY=10
11 DEFAULT_THRESHOLD=1
12
13 def uniq_list(lst):
14    keys = {}
15    for e in lst:\r
16        keys[e] = 1\r
17    return keys.keys()
18
19 def delta_time(x,y):
20     # x is the most recent (newest) timestamp
21     # y is the least recent (oldest) timestamp
22     # TODO: verify wraparounds
23     if (x>=y):
24         return x-y
25     elif y<=0xFFFFFFFF:
26         # 32-bit wraparound
27         return (x+0xFFFFFFFF)-y
28     else:
29         # 64-bit wraparound
30         return (x+0xFFFFFFFFFFFFFFFF)-y
31
32 class Process:
33     def __init__(self, pid=None):
34         self.pid=pid
35         self.cmd=None
36         self.cgroups=[CGROUP_NOGROUP]
37         self.times=[]
38         self.touched=False
39
40     def update_stat(self, line, sampletime):
41         if not (")" in line):
42             return
43
44         (pidcmd, line) = line.split(")",1)
45         if not ("(" in pidcmd):
46             return
47
48         (junk, self.cmd) = pidcmd.split("(",1)
49
50         line=line.strip()
51         parts=line.split(" ")
52
53         utime = int(parts[11])
54         stime = int(parts[12])
55
56         self.update_times(utime, stime, sampletime)
57
58     def update_times(self, utime, stime, sampletime):
59         timerec = (utime, stime, sampletime)
60
61         self.times.insert(0, timerec)
62
63         # two samples is enough
64         if len(self.times)>2:
65             self.times.pop()
66
67     def update_cgroups(self, cgroup_names):
68         if not cgroup_names:
69             self.cgroups = [CGROUP_NOGROUP]
70         else:
71             self.cgroups = uniq_list(cgroup_names)
72
73     def delta_times(self):
74         if (len(self.times)<2):
75             return (0,0,0)
76
77         return (delta_time(self.times[0][0], self.times[1][0]),
78                 delta_time(self.times[0][1], self.times[1][1]),
79                 delta_time(self.times[0][2], self.times[1][2]))
80
81 class Cgroup:
82     def __init__(self, name):
83         self.name = name
84         self.utime = 0
85         self.stime = 0
86         self.etime = 0
87         self.charge = 0
88         self.processes = []
89
90     def add_process(self, process):
91         (utime, stime, etime) = process.delta_times()
92         self.utime += utime
93         self.stime += stime
94         self.etime = etime
95         self.processes.append(process)
96
97     def merge(self, cgroup, threshold=0, chargeUnit=1):
98         if (cgroup.utime + cgroup.etime) >= threshold:
99             self.utime += cgroup.utime
100             self.stime += cgroup.stime
101             self.etime += cgroup.etime
102             self.charge = self.charge + chargeUnit
103
104 class CgroupTotals:
105     def __init__(self):
106         self.cgroups = {}
107
108     def add(self, cgroup, threshold=0, chargeUnit=1):
109         name = cgroup.name
110         if not (name in self.cgroups):
111             self.cgroups[name] = Cgroup(name)
112
113         self.cgroups[name].merge(cgroup, threshold, chargeUnit)
114
115     def dump(self):
116         cglist = []
117
118         for cgroup_name in self.cgroups:
119             cgroup = self.cgroups[cgroup_name]
120             if cgroup.charge > 0:
121                 cglist.append(cgroup)
122
123         cglist = sorted(cglist, key = lambda cg: cg.charge, reverse=True)
124
125         print "%-24s %-8s %-8s %-8s" % ("cgroup", "charge", "utime", "stime")
126         for cgroup in cglist:
127             print "%-24s %8.2f %8d %8d" % (cgroup.name, cgroup.charge, cgroup.utime, cgroup.stime)
128
129 class Processes:
130     def __init__(self):
131         self.processes = {}
132
133     def get_timestamp(self):
134         # wall-time is good enough, multiple it by USER_HZ (100)
135         return int(time.time()*100)
136
137     def update_proc(self, name, sampleTime):
138         try:
139             pid = int(name)
140
141             if not (pid in self.processes):
142                 self.processes[pid] = Process(pid)
143
144             stat = open(os.path.join(PROC_DIR, os.path.join(name, "stat")), "r").readline()
145
146             self.processes[pid].touched=True
147             self.processes[pid].update_stat(stat, sampleTime)
148
149             cgroup_filename = os.path.join(PROC_DIR, os.path.join(name, "cgroup"))
150             if os.path.exists(cgroup_filename):
151                 cgroups = open(cgroup_filename, "r").readlines()
152                 cgroup_names = []
153                 for line in cgroups:
154                     parts = line.strip().split(":")
155                     if len(parts)==3:
156                         cgroup_names.append(parts[2].lstrip("/"))
157                 self.processes[pid].update_cgroups(cgroup_names)
158
159         except IOError:
160             # maybe the process disappeared while we were trying to read its
161             # files.
162             pass
163
164     def update(self):
165         sampleTime = self.get_timestamp()
166
167         # reset the touch bits so we can see what we updated
168         for pid in self.processes.keys():
169             self.processes[pid].touched = False
170
171         names = os.listdir(PROC_DIR)
172         for name in names:
173             if not name.isdigit():
174                 continue
175
176             self.update_proc(name, sampleTime)
177
178         # delete everything we didn't touch
179         for pid in self.processes.keys():
180             if not (self.processes[pid].touched):
181                 del self.processes[pid]
182
183     def get_cgroups(self):
184         cgroups={}
185         for pid in self.processes:
186             process = self.processes[pid]
187             (utime, stime, etime) = process.delta_times()
188             if (utime+stime>0):
189                 for cgroup_name in process.cgroups:
190                     if not cgroup_name in cgroups:
191                         cgroups[cgroup_name] = Cgroup(cgroup_name)
192                     cgroups[cgroup_name].add_process(process)
193         return cgroups
194
195     def dump(self, threshold=DEFAULT_THRESHOLD):
196         plist = []
197         for pid in self.processes:
198             process = self.processes[pid]
199             (utime, stime, etime) = process.delta_times()
200             if (utime+stime>=threshold):
201                 plist.append(process)
202
203         #sort by ctime+utime
204         plist = sorted(plist, key = lambda process: process.delta_times()[0]+process.delta_times()[1], reverse=True)
205
206         print "%-8s %-24s %-24s %-8s %-8s %-8s" % ("pid", "cmd", "cgroups", "utime", "stime", "elp-samp")
207         for process in plist:
208             (utime, stime, etime) = process.delta_times()
209             print "%8d %-24s %-24s %8d %8d %8d" % (process.pid, str(process.cmd), ",".join(process.cgroups), utime, stime, etime)
210
211     def dump_cgroups(self, threshold=DEFAULT_THRESHOLD):
212         cgroups = self.get_cgroups()
213         cglist = []
214
215         for cgroup_name in cgroups:
216             cgroup = cgroups[cgroup_name]
217             if cgroup.utime+cgroup.stime > threshold:
218                 cglist.append(cgroup)
219
220         cglist =sorted(cglist, key = lambda cg: cg.utime+cg.stime, reverse=True)
221
222         print "%-24s %-8s %-8s %-8s %-8s" % ("cgroup", "procs", "utime", "stime", "elp-samp")
223         for cgroup in cglist:
224             print "%-24s %8d %8d %8d %8d" % (cgroup.name, len(cgroup.processes), cgroup.utime, cgroup.stime, cgroup.etime)
225
226 def top(opts):
227     processes = Processes()
228     while (1):
229         processes.update()
230         os.system("clear")
231         processes.dump(opts.threshold)
232
233         time.sleep(opts.delay)
234
235 def topgroups(opts):
236     processes = Processes()
237     while (1):
238         processes.update()
239         os.system("clear")
240         processes.dump_cgroups(opts.threshold)
241
242         time.sleep(opts.delay)
243
244 def chargetest(opts):
245     processes = Processes()
246     cgroups = CgroupTotals()
247     while (1):
248         processes.update()
249         os.system("clear")
250
251         cgroups_dict = processes.get_cgroups()
252         for cgroup_name in cgroups_dict:
253             cgroup = cgroups_dict[cgroup_name]
254             cgroups.add(cgroup, opts.threshold, 1.0/(3600/opts.delay))
255
256         cgroups.dump()
257
258         time.sleep(opts.delay)
259
260 def create_parser():
261    # Generate command line parser
262    parser = OptionParser(usage="gackswatcher [options] command [command_options] [command_args]",
263         description="Commands: top topgroups",
264         version="gackswatcher ??")
265
266    parser.add_option("-d", "--delay", dest="delay", type="int",
267         help="delay between calculations", metavar="seconds", default=DEFAULT_DELAY)
268
269    parser.add_option("-t", "--threshold", dest="threshold", type="int",
270         help="threshold in .1 second units", metavar="tenths-of-seconds", default=DEFAULT_THRESHOLD)
271
272    parser.disable_interspersed_args()
273
274    return parser
275
276 def main():
277     parser = create_parser()
278     (opts, args) = parser.parse_args()
279
280     if len(args)==0:
281         print "specify a command: top, topgroups, ..."
282         sys.exit(-1)
283
284     cmd = args[0]
285
286     if cmd == "top":
287         top(opts)
288     elif cmd == "topgroups":
289         topgroups(opts)
290     elif cmd == "chargetest":
291         chargetest(opts)
292     else:
293         print "unknown command", cmd
294
295 if __name__=="__main__":
296    main()
297
298
299
300
301
302
303
304
305
306
307
308
309