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