]> git.itanic.dy.fi Git - emerge-timer/blob - emerge-timer.py
Changes to --list option
[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                     print("\t" + give_date(p[1]) + " >>> " + GREEN(p[0]))
331
332                     emerge_number += 1
333
334                     all_packages.pop(all_packages.index(p))
335
336     print("\n" + GREEN(str(emerge_number)) + " packages in total.")
337
338
339
340 def get_package(name):
341     """Take the user-input package name and search for it
342     in PORTDIR. """
343
344     dirlist = os.listdir(PORTDIR)
345     possible_package = []
346
347
348     # If the given name is in the format xxx/zzz
349     # assume that xxx is the package group
350     if '/' in name:
351         group = name.partition('/')[0]
352         pkg = name.partition('/')[2]
353         directory = PORTDIR + group
354
355         if group in dirlist:
356             dirs = os.listdir(directory)
357             if pkg in dirs:
358                 possible_package.append(name)
359
360
361     # Go through the directory listing searching for anything
362     # that matches the given name
363     for i in dirlist:
364         directory = PORTDIR + i
365         if os.path.isdir(directory):
366             dirs = os.listdir(directory)
367             if name in dirs:
368                 possible_package.append(i + '/' + name)
369
370
371     if len(possible_package) > 1:
372         print("Multiple packages found for '" + name + "'.")
373         print("Possible packages: ")
374         for value in possible_package:
375             print("\t" + value)
376
377
378     elif len(possible_package) == 1:
379         package = possible_package[0]
380         return package
381
382
383     else:
384         print("No package '" + name + "' found")
385
386
387     sys.exit(1)
388
389
390
391 def list_pretended():
392     """List all the pretended packages given by emerge -p
393     output. Create a class out of each of those packages and add them
394     to the list."""
395
396     log = open_log()
397
398     for line in sys.stdin:
399         if "[ebuild" in line:
400             full_name = line.partition("] ")[2].partition(' ')[0]
401
402             version = full_name.partition('/')[2].partition('-')[2]
403             while not version[0].isdigit():
404                 version = version.partition('-')[2]
405             package_name = full_name[:-len(version)-1]
406
407             PACKAGES.append(package(package_name, version))
408
409
410
411 def list_emerge_processes():
412     """Look for the ebuild process with ps. If the process is found parse
413     the command for the package. With this package search the LOGFILE for
414     the emerge startup time."""
415
416     f = open_log()
417
418     now = datetime.datetime.today()
419
420     for i in os.popen("ps ax"):
421         if (("ebuild.sh" in i) and ("/bin/bash" not in i)):
422             pack = i.partition('[')[2].partition(']')[0]
423
424             version = pack.partition('/')[2].partition('-')[2]
425
426             while not version[0].isdigit():
427                 version = version.partition('-')[2]
428
429             package_name = pack[:-len(version)-1]
430
431             PACKAGES.append(package(package_name, version))
432
433
434     if len(PACKAGES) == 0:
435         print "No current emerge process found."
436
437         return 1
438
439
440     for line in f:
441         if ((">>>" in line) and ("emerge" in line)):
442             for p in PACKAGES:
443                 difference = 0
444
445                 if (p.name + '-' + p.version in line):
446
447                     time = float(line.partition(' ')[0].strip(":"))
448
449                     timestamp = datetime.datetime.fromtimestamp(time)
450                     difference = (now - timestamp).total_seconds()
451
452                     if ((difference < p.emerge_time) or
453                         (p.emerge_time == "infinity")):
454
455                         p.emerge_time = difference
456
457     return 0
458
459
460 def search_syncs():
461     f = open_log()
462
463     print "These emerge syncs found"
464     print "\tDate                    Server"
465     print "\t------------------------------"
466
467     for line in f:
468         if "=== Sync completed with" in line:
469             time = float(line.partition(' ')[0].strip(":"))
470             server = line.rpartition(' ')[2]
471
472             print("\t" + GREEN(give_date(time)) +
473                   " === " + server),
474
475
476
477 def main(status, user_package=None):
478     try:
479         _main(status, user_package)
480     except IOError:
481         sys.exit()
482
483
484 def _main(status, user_package=None):
485     """Main function. Hanlde all the different modes of operation."""
486
487     if status == "package":
488         for p in user_package:
489             pack = get_package(p)
490
491             pack = package(pack)
492
493             search_log_for_package(pack)
494
495             if len(pack.versions) != 0:
496                 pack.print_versions()
497                 pack.print_min_max_ave()
498
499             else:
500                 print("Package " + GREEN(pack.name)
501                       + " has never been emerged.")
502
503
504     elif status == "sync":
505         search_syncs()
506         return
507
508
509     elif status == "list":
510         search_log_for_all_packages()
511         return
512
513
514     elif status == "current":
515         if list_emerge_processes():
516             return
517
518         print "Currently emerging:"
519
520         for p in PACKAGES:
521             search_log_for_package(p)
522             p.print_current_emerge()
523
524
525     elif status == "pretended":
526         list_pretended()
527
528         print "This is how long these packages would take to emerge"
529
530         total_pretended_time = 0
531
532         for p in PACKAGES:
533             search_log_for_package(p)
534
535             total_pretended_time += p.print_pretended_times()
536
537             print
538
539         print("Total emerge time of " + GREEN(str(len(PACKAGES)))
540               + " package(s): "+ give_time(total_pretended_time))
541
542
543 def usage():
544     usage = """Usage: emerge-timer.py [package] [options]
545
546 Calculate emerge times from emerge log.
547
548 Options:
549 \t-c, --current \t Show time until currently compiling package finishes
550 \t-p, --pretended  Calculate compile time from piped 'emerge -p' output
551 \t-l, --list \t List all emerged packages
552 \t-s, --sync \t Show emerge sync history
553 \t-h, --help \t Show this helpscreen
554 \t-q, --quiet \t Be less verbose
555 \t--no-color \t Use colorless output
556 \t--simulate \t Do a simulation run"""
557
558     print usage
559
560     sys.exit(0)
561
562
563 if __name__ == "__main__":
564
565     # Set the default mode as "package"
566     mode = "package"
567     input_packages = None
568     simulation = False
569
570     for arg in sys.argv[1:]:
571
572         if arg == "-p" or arg == "--pretended":
573             mode = "pretended"
574
575         if arg == "-c" or arg == "--current":
576             mode = "current"
577
578         if arg == "-h" or arg == "--help":
579             usage()
580
581         if arg == "-l" or arg == "--list":
582             mode = "list"
583
584         if arg == "-s" or arg == "--sync":
585             mode = "sync"
586
587         if arg == "-q" or arg == "--quiet":
588             QUIET = True
589
590             sys.argv.pop(sys.argv.index(arg))
591
592         if arg == "--no-color":
593             green_start = ""
594             color_stop = ""
595
596             sys.argv.pop(sys.argv.index(arg))
597
598         if arg == "--simulate":
599             simulation = True
600
601
602     if len(sys.argv) > 1:
603         input_packages = sys.argv[1:]
604     else:
605         usage()
606
607     if simulation == True:
608
609         print(RED("\n" + '*'*25 + "\n" + "THIS IS A SIMULATION RUN\n"
610               + '*'*25 + "\n"))
611
612         print(RED("Beginning 'package' mode check"))
613
614         print(RED("Checking for one emerge\n"))
615
616         LOGFILE = "simulate/fake_emerge.log"
617         PORTDIR = "simulate/"
618
619         main("package", "first_fake_package")
620
621         print(RED("\nChecking for three emerges\n"))
622
623         main("package", "second_fake_package")
624
625         print(RED("\n'package' mode check complete\n"))
626
627         print(RED(30*'*'))
628
629         print(RED("\nBeginning 'current' mode check"))
630         print(RED("Current emerge with no emerge process\n"))
631
632         main("current", None)
633
634         print(RED("\nCurrent emerge with emerge processes\n"))
635
636         PACKAGES.append(package("test-group/second_fake_package", "2.9-r2"))
637         PACKAGES[0].emerge_time = 60
638         PACKAGES.append(package("test-group/first_fake_package", "1.10.2-r1"))
639         PACKAGES[1].emerge_time = 120
640
641         main("current", None)
642
643         print(RED("\nCurrent emerge with incomplete emerge log " + 
644                   "(causes error in some cases)\n"))
645
646         PACKAGES = []
647         PACKAGES.append(package("test-group/third_fake_package", "2.9-r2"))
648
649         main("current", None)
650
651         print(RED("\n" + '*'*20 + "\n" + "SIMULATION FINISHED\n" +
652               '*'*20))
653
654
655     else:
656         main(mode, input_packages)