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