]> git.itanic.dy.fi Git - emerge-timer/blob - emerge-timer.py
Add support for showing emerge sync history
[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 search_syncs():
465     f = open_log()
466
467     print "These emerge syncs found"
468     print "\tDate                    Server"
469     print "\t------------------------------"
470
471     for line in f:
472         if "=== Sync completed with" in line:
473             time = float(line.partition(' ')[0].strip(":"))
474             server = line.rpartition(' ')[2]
475
476             print("\t" + GREEN(give_date(time)) +
477                   " === " + server),
478
479
480
481 def main(status, user_package=None):
482     try:
483         _main(status, user_package)
484     except IOError:
485         sys.exit()
486
487
488 def _main(status, user_package=None):
489     """Main function. Hanlde all the different modes of operation."""
490
491     if status == "package":
492         for p in user_package:
493             pack = get_package(p)
494
495             pack = package(pack)
496
497             search_log_for_package(pack)
498
499             if len(pack.versions) != 0:
500                 pack.print_versions()
501                 pack.print_min_max_ave()
502
503             else:
504                 print("Package " + GREEN(pack.name)
505                       + " has never been emerged.")
506
507
508     elif status == "sync":
509         search_syncs()
510         return
511
512
513     elif status == "list":
514         search_log_for_all_packages()
515         return
516
517
518     elif status == "current":
519         if list_emerge_processes():
520             return
521
522         print "Currently emerging:"
523
524         for p in PACKAGES:
525             search_log_for_package(p)
526             p.print_current_emerge()
527
528
529     elif status == "pretended":
530         list_pretended()
531
532         print "This is how long these packages would take to emerge"
533
534         total_pretended_time = 0
535
536         for p in PACKAGES:
537             search_log_for_package(p)
538
539             total_pretended_time += p.print_pretended_times()
540
541             print
542
543         print("Total emerge time of " + GREEN(str(len(PACKAGES)))
544               + " package(s): "+ give_time(total_pretended_time))
545
546
547 def usage():
548     usage = """Usage: emerge-timer.py [package] [options]
549
550 Calculate emerge times from emerge log.
551
552 Options:
553 \t-c, --current \t Show time until currently compiling package finishes
554 \t-p, --pretended  Calculate compile time from piped 'emerge -p' output
555 \t-l, --list \t List all emerged packages
556 \t-s, --sync \t Show emerge sync history
557 \t-h, --help \t Show this helpscreen
558 \t-q, --quiet \t Be less verbose
559 \t--no-color \t Use colorless output
560 \t--simulate \t Do a simulation run"""
561
562     print usage
563
564     sys.exit(0)
565
566
567 if __name__ == "__main__":
568
569     # Set the default mode as "package"
570     mode = "package"
571     input_packages = None
572     simulation = False
573
574     for arg in sys.argv[1:]:
575
576         if arg == "-p" or arg == "--pretended":
577             mode = "pretended"
578
579         if arg == "-c" or arg == "--current":
580             mode = "current"
581
582         if arg == "-h" or arg == "--help":
583             usage()
584
585         if arg == "-l" or arg == "--list":
586             mode = "list"
587
588         if arg == "-s" or arg == "--sync":
589             mode = "sync"
590
591         if arg == "-q" or arg == "--quiet":
592             QUIET = True
593
594             sys.argv.pop(sys.argv.index(arg))
595
596         if arg == "--no-color":
597             green_start = ""
598             color_stop = ""
599
600             sys.argv.pop(sys.argv.index(arg))
601
602         if arg == "--simulate":
603             simulation = True
604
605
606     if len(sys.argv) > 1:
607         input_packages = sys.argv[1:]
608     else:
609         usage()
610
611     if simulation == True:
612
613         print(RED("\n" + '*'*25 + "\n" + "THIS IS A SIMULATION RUN\n"
614               + '*'*25 + "\n"))
615
616         print(RED("Beginning 'package' mode check"))
617
618         print(RED("Checking for one emerge\n"))
619
620         LOGFILE = "simulate/fake_emerge.log"
621         PORTDIR = "simulate/"
622
623         main("package", "first_fake_package")
624
625         print(RED("\nChecking for three emerges\n"))
626
627         main("package", "second_fake_package")
628
629         print(RED("\n'package' mode check complete\n"))
630
631         print(RED(30*'*'))
632
633         print(RED("\nBeginning 'current' mode check"))
634         print(RED("Current emerge with no emerge process\n"))
635
636         main("current", None)
637
638         print(RED("\nCurrent emerge with emerge processes\n"))
639
640         PACKAGES.append(package("test-group/second_fake_package", "2.9-r2"))
641         PACKAGES[0].emerge_time = 60
642         PACKAGES.append(package("test-group/first_fake_package", "1.10.2-r1"))
643         PACKAGES[1].emerge_time = 120
644
645         main("current", None)
646
647         print(RED("\nCurrent emerge with incomplete emerge log " + 
648                   "(causes error in some cases)\n"))
649
650         PACKAGES = []
651         PACKAGES.append(package("test-group/third_fake_package", "2.9-r2"))
652
653         main("current", None)
654
655         print(RED("\n" + '*'*20 + "\n" + "SIMULATION FINISHED\n" +
656               '*'*20))
657
658
659     else:
660         main(mode, input_packages)