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