]> git.itanic.dy.fi Git - emerge-timer/blob - emerge-timer.py
Capture IOErrors
[emerge-timer] / emerge-timer.py
1 #!/usr/bin/python
2
3
4 import sys, datetime, os
5
6
7 PORTDIR = "/usr/portage/"
8 LOGFILE = "/var/log/emerge.log"
9
10
11 green_start = "\033[32m"
12 red_start = "\033[1;31m"
13 color_stop = "\033[m"
14
15 QUIET = False
16
17 PACKAGES = []
18
19 def GREEN(string):
20     return green_start + string + color_stop
21
22 def RED(string):
23     return red_start + string + color_stop
24
25
26 class package:
27     def __init__(self, name, version=0):
28         self.name = name
29         self.version = version
30         self.versions = []
31         self.emerge_time = "infinity"
32
33
34     def add_version(self, version, emerge_time, emerge_date):
35         """Add version to the class version list"""
36         self.versions.append((version, emerge_time, emerge_date))
37
38
39     def average_time(self):
40         """Return average time from class version list"""
41         total_time = 0
42         for i in self.versions:
43             total_time += i[1]
44
45         average_time = total_time/len(self.versions)
46
47         return average_time
48
49
50     def total_time(self):
51         """Return total time from class version list"""
52         total_time = 0
53         for i in self.versions:
54             total_time += i[1]
55
56         return total_time
57
58
59     def max_time(self):
60         """Return maximum time from class version list"""
61
62         emerge_times = []
63         for i in self.versions:
64             emerge_times.append(i[1])
65
66         emerge_times.sort()
67
68         return emerge_times[-1]
69
70
71     def min_time(self):
72         """Return minimum time from class version list"""
73
74         emerge_times = []
75         for i in self.versions:
76             emerge_times.append(i[1])
77
78         emerge_times.sort()
79
80         return emerge_times[0]
81
82
83
84     def print_current_emerge(self):
85         """Function used to print all the current emerge stuff"""
86
87         print("\t" + GREEN(self.name + '-' + self.version) +
88               "\n\t current time: " + give_time(self.emerge_time))
89
90
91         if len(self.versions) == 1:
92             print("\t last time:   "),
93             print(give_time(self.average_time())),
94
95         elif len(self.versions) > 1:
96             print("\t average time:"),
97             print(give_time(self.average_time())),
98
99         else:
100             print("\t average time: " + GREEN("unknown\n")),
101             return
102
103         print("\n\t " + '-'*45),
104
105         print("\n\t time to finish:"),
106
107         if type(self.emerge_time) != str:
108
109             finish_time = self.average_time() - self.emerge_time
110
111             if finish_time > 0:
112                 print(give_time(finish_time))
113             else:
114                 print(GREEN("any time now"))
115         else:
116             print(GREEN("unknown"))
117         print
118
119
120     def print_versions(self):
121         """This prints the emerge times for different versions in the
122         'package' operating mode of the script"""
123
124         if QUIET == False:
125
126             version_length = 0
127             time_length = 0
128
129             for p in self.versions:
130                 if len(p[0]) > version_length:
131                     version_length = len(p[0])
132
133                 if len(give_time(p[1], True)) > time_length:
134                     time_length = len(give_time(p[1], True))
135
136             dots =  (version_length + time_length + len(self.name)
137                      + len(give_date(self.versions[0][2])) + 14)
138
139             for p in self.versions:
140
141                 pad = time_length - len(give_time(p[1], True))
142
143                 name = self.name
144                 p_time = give_time(p[1])
145                 p_date = give_date(p[2])
146
147                 print('-' * dots + "\n" +
148                       GREEN(name + (p[0]).ljust(version_length))
149                       + "  >>>  " + p_time + " "*pad + "  >>>  " + p_date)
150
151         print("\n" + "Package " + GREEN(self.name) + " emerged"),
152
153         if len(self.versions) > 1:
154             print(str(len(self.versions)) + " times.\n")
155         elif len(self.versions) == 1:
156             print("once.\n")
157
158
159
160     def print_pretended_times(self):
161         """This is used the print all the pretended times"""
162
163         if QUIET == False:
164             print("\t" + GREEN(self.name + '-' + self.version)),
165
166         if len(self.versions) > 1:
167             aver_time = self.average_time()
168
169             if QUIET == False:
170                 print("\n\taverage time: " + give_time(aver_time))
171
172             return aver_time
173
174         else:
175             if QUIET == False:
176                 print("\n\t no previous emerges")
177
178             return 0
179
180
181     def print_min_max_ave(self):
182
183         if len(self.versions) == 1:
184             return
185
186         maxi = self.max_time()
187         mini = self.min_time()
188         average = self.average_time()
189         total = self.total_time()
190
191         print("Max time:\t" + give_time(maxi) +
192               "\nMin time:\t" + give_time(mini) +
193               "\nAverage time:\t" + give_time(average) +
194               "\nIn total spent:\t" + give_time(total) +
195               "emerging " + GREEN(self.name))
196
197
198
199 def give_time(time, nocolor=False):
200     """Converts time in seconds to human readable form"""
201     global green_start, color_stop
202
203     if green_start == "":
204         nocolor = False
205
206     if nocolor == True:
207         green_start = ""
208         color_stop = ""
209
210     if type(time) == str:
211         return(GREEN("unknown"))
212
213
214     days = time/(3600.0*24.0)
215     hours = (days - int(days))*24.0
216     minutes = (hours - int(hours))*60.0
217     seconds = (minutes - int(minutes))*60.0
218
219     days = int(days)
220     hours = int(hours)
221     minutes = int(minutes)
222     seconds = int(round(seconds))
223
224     pdays = str()
225     phours = str()
226     pminutes = str()
227     pseconds = str()
228
229     if days > 0:
230         pdays = (GREEN(str(days)) + " day ")
231         if days != 1:
232             pdays = (GREEN(str(days)) + " days ")
233
234     if hours > 0:
235         phours = (GREEN(str(hours)) + " hour ")
236         if hours != 1:
237             phours = (GREEN(str(hours)) + " hours ")
238
239     if minutes > 0:
240         pminutes = (GREEN(str(minutes)) + " minute ")
241         if minutes != 1:
242             pminutes = (GREEN(str(minutes)) + " minutes ")
243
244     if seconds > 0:
245         pseconds = (GREEN(str(seconds)) + " second ")
246         if seconds != 1:
247             pseconds = (GREEN(str(seconds)) + " seconds ")
248
249     if nocolor == True:
250         green_start = "\033[32m"
251         color_stop = "\033[m"
252
253     return (pdays + phours + pminutes + pseconds)
254
255
256
257 def give_date(emerge_date):
258     """Returns a date string from a standard POSIX time"""
259     date = datetime.datetime.fromtimestamp(emerge_date)
260
261     date = "{:%d.%m.%Y %H:%M:%S}".format(date)
262
263     return date
264
265
266
267 def open_log():
268     """Attempt to open the LOGFILE."""
269
270     try:
271         f = open(LOGFILE, 'r')
272     except IOError as detail:
273         print detail
274         sys.exit(1)
275
276     return f
277
278
279
280 def search_log_for_package(package_class):
281     """Searchs emerge log for given package and adds all found
282     versions with their emerge times to the class"""
283
284     log = open_log()
285
286     for line in log:
287         if ((">>>" in line) and ("emerge" in line)):
288             if package_class.name in line:
289
290                 version = line.partition(package_class.name)[2].partition(' ')[0]
291                 digit = version.strip('-')[0].isdigit()
292
293                 if digit:
294                     time_string = line.partition(">>>")
295                     start_time = float(time_string[0].strip().strip(':'))
296
297         elif ((":::" in line) and ("completed emerge" in line)):
298
299             if package_class.name in line:
300                 if digit:
301                     time_string = line.partition(":::")
302                     stop_time = float(time_string[0].strip().strip(':'))
303
304                     emerge_time = stop_time - start_time
305
306                     package_class.add_version(version, emerge_time, start_time)
307
308
309
310 def search_log_for_all_packages():
311     """Goes through the emerge.log and lists all packages in there"""
312
313     log = open_log()
314
315     all_packages = []
316
317     total_emerge_time = 0
318     emerge_number = 0
319
320     for line in log:
321         if ((">>>" in line) and ("emerge" in line)):
322             pack = line.partition(')')[2].strip().partition(' ')[0]
323             start_time = float(line.partition(':')[0])
324
325             all_packages.append((pack, start_time))
326
327         elif ((":::" in line) and ("completed emerge" in line)):
328             for p in all_packages:
329                 if p[0] in line:
330                     stop_time = float(line.partition(':')[0])
331
332                     print("\t" + give_date(p[1]) + " >>> " + GREEN(p[0]))
333
334                     total_emerge_time += stop_time - p[1]
335                     emerge_number += 1
336
337                     all_packages.pop(all_packages.index(p))
338
339     print("\nTotal emerge time of " + GREEN(str(emerge_number)) +
340           " merges: " + give_time(total_emerge_time))
341
342
343
344 def get_package(name):
345     """Take the user-input package name and search for it
346     in PORTDIR. """
347
348     dirlist = os.listdir(PORTDIR)
349     possible_package = []
350
351
352     # If the given name is in the format xxx/zzz
353     # assume that xxx is the package group
354     if '/' in name:
355         group = name.partition('/')[0]
356         pkg = name.partition('/')[2]
357         directory = PORTDIR + group
358
359         if group in dirlist:
360             dirs = os.listdir(directory)
361             if pkg in dirs:
362                 possible_package.append(name)
363
364
365     # Go through the directory listing searching for anything
366     # that matches the given name
367     for i in dirlist:
368         directory = PORTDIR + i
369         if os.path.isdir(directory):
370             dirs = os.listdir(directory)
371             if name in dirs:
372                 possible_package.append(i + '/' + name)
373
374
375     if len(possible_package) > 1:
376         print("Multiple packages found for '" + name + "'.")
377         print("Possible packages: ")
378         for value in possible_package:
379             print("\t" + value)
380
381
382     elif len(possible_package) == 1:
383         package = possible_package[0]
384         return package
385
386
387     else:
388         print("No package '" + name + "' found")
389
390
391     sys.exit(1)
392
393
394
395 def list_pretended():
396     """List all the pretended packages given by emerge -p
397     output. Create a class out of each of those packages and add them
398     to the list."""
399
400     log = open_log()
401
402     for line in sys.stdin:
403         if "[ebuild" in line:
404             full_name = line.partition("] ")[2].partition(' ')[0]
405
406             version = full_name.partition('/')[2].partition('-')[2]
407             while not version[0].isdigit():
408                 version = version.partition('-')[2]
409             package_name = full_name[:-len(version)-1]
410
411             PACKAGES.append(package(package_name, version))
412
413
414
415 def list_emerge_processes():
416     """Look for the ebuild process with ps. If the process is found parse
417     the command for the package. With this package search the LOGFILE for
418     the emerge startup time."""
419
420     f = open_log()
421
422     now = datetime.datetime.today()
423
424     for i in os.popen("ps ax"):
425         if (("ebuild.sh" in i) and ("/bin/bash" not in i)):
426             pack = i.partition('[')[2].partition(']')[0]
427
428             version = pack.partition('/')[2].partition('-')[2]
429
430             while not version[0].isdigit():
431                 version = version.partition('-')[2]
432
433             package_name = pack[:-len(version)-1]
434
435             PACKAGES.append(package(package_name, version))
436
437
438     if len(PACKAGES) == 0:
439         print "No current emerge process found."
440
441         return 1
442
443
444     for line in f:
445         if ((">>>" in line) and ("emerge" in line)):
446             for p in PACKAGES:
447                 difference = 0
448
449                 if (p.name + '-' + p.version in line):
450
451                     time = float(line.partition(' ')[0].strip(":"))
452
453                     timestamp = datetime.datetime.fromtimestamp(time)
454                     difference = (now - timestamp).total_seconds()
455
456                     if ((difference < p.emerge_time) or
457                         (p.emerge_time == "infinity")):
458
459                         p.emerge_time = difference
460
461     return 0
462
463
464 def main(status, user_package=None):
465     try:
466         _main(status, user_package)
467     except IOError:
468         sys.exit()
469
470
471 def _main(status, user_package=None):
472     """Main function. Hanlde all the different modes of operation."""
473
474     if status == "package":
475         for p in user_package:
476             pack = get_package(p)
477
478             pack = package(pack)
479
480             search_log_for_package(pack)
481
482             if len(pack.versions) != 0:
483                 pack.print_versions()
484                 pack.print_min_max_ave()
485
486             else:
487                 print("Package " + GREEN(pack.name)
488                       + " has never been emerged.")
489
490
491     elif status == "list":
492         search_log_for_all_packages()
493         return
494
495
496     elif status == "current":
497         if list_emerge_processes():
498             return
499
500         print "Currently emerging:"
501
502         for p in PACKAGES:
503             search_log_for_package(p)
504             p.print_current_emerge()
505
506
507     elif status == "pretended":
508         list_pretended()
509
510         print "This is how long these packages would take to emerge"
511
512         total_pretended_time = 0
513
514         for p in PACKAGES:
515             search_log_for_package(p)
516
517             total_pretended_time += p.print_pretended_times()
518
519             print
520
521         print("Total emerge time of " + GREEN(str(len(PACKAGES)))
522               + " package(s): "+ give_time(total_pretended_time))
523
524
525 def usage():
526     usage = """Usage: emerge-timer.py [package] [options]
527
528 Calculate emerge times from emerge log.
529
530 Options:
531 \t-c, --current \t Show time until currently compiling package finishes
532 \t-p, --pretended  Calculate compile time from piped 'emerge -p' output
533 \t-l, --list \t List all emerged packages
534 \t-h, --help \t Show this helpscreen
535 \t-q, --quiet \t Be less verbose
536 \t--no-color \t Use colorless output
537 \t--simulate \t Do a simulation run"""
538
539     print usage
540
541     sys.exit(0)
542
543
544 if __name__ == "__main__":
545
546     # Set the default mode as "package"
547     mode = "package"
548     input_packages = None
549     simulation = False
550
551     for arg in sys.argv[1:]:
552
553         if arg == "-p" or arg == "--pretended":
554             mode = "pretended"
555
556         if arg == "-c" or arg == "--current":
557             mode = "current"
558
559         if arg == "-h" or arg == "--help":
560             usage()
561
562         if arg == "-l" or arg == "--list":
563             mode = "list"
564
565         if arg == "-q" or arg == "--quiet":
566             QUIET = True
567
568             sys.argv.pop(sys.argv.index(arg))
569
570         if arg == "--no-color":
571             green_start = ""
572             color_stop = ""
573
574             sys.argv.pop(sys.argv.index(arg))
575
576         if arg == "--simulate":
577             simulation = True
578
579
580     if len(sys.argv) > 1:
581         input_packages = sys.argv[1:]
582     else:
583         usage()
584
585     if simulation == True:
586
587         print(RED("\n" + '*'*25 + "\n" + "THIS IS A SIMULATION RUN\n"
588               + '*'*25 + "\n"))
589
590         print(RED("Beginning 'package' mode check"))
591
592         print(RED("Checking for one emerge\n"))
593
594         LOGFILE = "simulate/fake_emerge.log"
595         PORTDIR = "simulate/"
596
597         main("package", "first_fake_package")
598
599         print(RED("\nChecking for three emerges\n"))
600
601         main("package", "second_fake_package")
602
603         print(RED("\n'package' mode check complete\n"))
604
605         print(RED(30*'*'))
606
607         print(RED("\nBeginning 'current' mode check"))
608         print(RED("Current emerge with no emerge process\n"))
609
610         main("current", None)
611
612         print(RED("\nCurrent emerge with emerge processes\n"))
613
614         PACKAGES.append(package("test-group/second_fake_package", "2.9-r2"))
615         PACKAGES[0].emerge_time = 60
616         PACKAGES.append(package("test-group/first_fake_package", "1.10.2-r1"))
617         PACKAGES[1].emerge_time = 120
618
619         main("current", None)
620
621         print(RED("\nCurrent emerge with incomplete emerge log " + 
622                   "(causes error in some cases)\n"))
623
624         PACKAGES = []
625         PACKAGES.append(package("test-group/third_fake_package", "2.9-r2"))
626
627         main("current", None)
628
629         print(RED("\n" + '*'*20 + "\n" + "SIMULATION FINISHED\n" +
630               '*'*20))
631
632
633     else:
634         main(mode, input_packages)