]> git.itanic.dy.fi Git - emerge-timer/blob - emerge-timer.py
27a5d9df3659876002149843945d5e40b878b402
[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         fig.autofmt_xdate()
216         plt.show()
217
218
219
220 def give_time(time, nocolor=False):
221     """Converts time in seconds to human readable form"""
222     global green_start, color_stop
223
224     if green_start == "":
225         nocolor = False
226
227     if nocolor == True:
228         green_start = ""
229         color_stop = ""
230
231     if type(time) == str:
232         return(GREEN("unknown"))
233
234
235     days = time/(3600.0*24.0)
236     hours = (days - int(days))*24.0
237     minutes = (hours - int(hours))*60.0
238     seconds = (minutes - int(minutes))*60.0
239
240     days = int(days)
241     hours = int(hours)
242     minutes = int(minutes)
243     seconds = int(round(seconds))
244
245     pdays = str()
246     phours = str()
247     pminutes = str()
248     pseconds = str()
249
250     if days > 0:
251         pdays = (GREEN(str(days)) + " day ")
252         if days != 1:
253             pdays = (GREEN(str(days)) + " days ")
254
255     if hours > 0:
256         phours = (GREEN(str(hours)) + " hour ")
257         if hours != 1:
258             phours = (GREEN(str(hours)) + " hours ")
259
260     if minutes > 0:
261         pminutes = (GREEN(str(minutes)) + " minute ")
262         if minutes != 1:
263             pminutes = (GREEN(str(minutes)) + " minutes ")
264
265     if seconds > 0:
266         pseconds = (GREEN(str(seconds)) + " second ")
267         if seconds != 1:
268             pseconds = (GREEN(str(seconds)) + " seconds ")
269
270     if nocolor == True:
271         green_start = "\033[32m"
272         color_stop = "\033[m"
273
274     return (pdays + phours + pminutes + pseconds)
275
276
277
278 def give_date(emerge_date):
279     """Returns a date string from a standard POSIX time"""
280     date = datetime.datetime.fromtimestamp(emerge_date)
281
282     date = "{:%d.%m.%Y %H:%M:%S}".format(date)
283
284     return date
285
286
287
288 def open_log():
289     """Attempt to open the LOGFILE."""
290
291     try:
292         f = open(LOGFILE, 'r')
293     except IOError as detail:
294         print detail
295         sys.exit(1)
296
297     return f
298
299
300
301 def search_log_for_package(package_class):
302     """Searchs emerge log for given package and adds all found
303     versions with their emerge times to the class"""
304
305     log = open_log()
306
307     for line in log:
308         if ((">>>" in line) and ("emerge" in line)):
309             if package_class.name in line:
310
311                 version = line.partition(package_class.name)[2].partition(' ')[0]
312                 digit = version.strip('-')[0].isdigit()
313
314                 if digit:
315                     time_string = line.partition(">>>")
316                     start_time = float(time_string[0].strip().strip(':'))
317
318         elif ((":::" in line) and ("completed emerge" in line)):
319
320             if package_class.name in line:
321                 if digit:
322                     time_string = line.partition(":::")
323                     stop_time = float(time_string[0].strip().strip(':'))
324
325                     emerge_time = stop_time - start_time
326
327                     package_class.add_version(version, emerge_time, start_time)
328
329
330
331 def search_log_for_all_packages():
332     """Goes through the emerge.log and lists all packages in there"""
333
334     log = open_log()
335
336     all_packages = []
337
338     total_emerge_time = 0
339     emerge_number = 0
340
341     for line in log:
342         if ((">>>" in line) and ("emerge" in line)):
343             pack = line.partition(')')[2].strip().partition(' ')[0]
344             start_time = float(line.partition(':')[0])
345
346             all_packages.append((pack, start_time))
347
348         elif ((":::" in line) and ("completed emerge" in line)):
349             for p in all_packages:
350                 if p[0] in line:
351                     stop_time = float(line.partition(':')[0])
352
353                     print("\t" + give_date(p[1]) + " >>> " + GREEN(p[0]))
354
355                     total_emerge_time += stop_time - p[1]
356                     emerge_number += 1
357
358                     all_packages.pop(all_packages.index(p))
359
360     print("\nTotal emerge time of " + GREEN(str(emerge_number)) +
361           " merges: " + give_time(total_emerge_time))
362
363
364
365 def get_package(name):
366     """Take the user-input package name and search for it
367     in PORTDIR. """
368
369     dirlist = os.listdir(PORTDIR)
370     possible_package = []
371
372
373     # If the given name is in the format xxx/zzz
374     # assume that xxx is the package group
375     if '/' in name:
376         group = name.partition('/')[0]
377         pkg = name.partition('/')[2]
378         directory = PORTDIR + group
379
380         if group in dirlist:
381             dirs = os.listdir(directory)
382             if pkg in dirs:
383                 possible_package.append(name)
384
385
386     # Go through the directory listing searching for anything
387     # that matches the given name
388     for i in dirlist:
389         directory = PORTDIR + i
390         if os.path.isdir(directory):
391             dirs = os.listdir(directory)
392             if name in dirs:
393                 possible_package.append(i + '/' + name)
394
395
396     if len(possible_package) > 1:
397         print("Multiple packages found for '" + name + "'.")
398         print("Possible packages: ")
399         for value in possible_package:
400             print("\t" + value)
401
402
403     elif len(possible_package) == 1:
404         package = possible_package[0]
405         return package
406
407
408     else:
409         print("No package '" + name + "' found")
410
411
412     sys.exit(1)
413
414
415
416 def list_pretended():
417     """List all the pretended packages given by emerge -p
418     output. Create a class out of each of those packages and add them
419     to the list."""
420
421     log = open_log()
422
423     for line in sys.stdin:
424         if "[ebuild" in line:
425             full_name = line.partition("] ")[2].partition(' ')[0]
426
427             version = full_name.partition('/')[2].partition('-')[2]
428             while not version[0].isdigit():
429                 version = version.partition('-')[2]
430             package_name = full_name[:-len(version)-1]
431
432             PACKAGES.append(package(package_name, version))
433
434
435
436 def list_emerge_processes():
437     """Look for the ebuild process with ps. If the process is found parse
438     the command for the package. With this package search the LOGFILE for
439     the emerge startup time."""
440
441     f = open_log()
442
443     now = datetime.datetime.today()
444
445     for i in os.popen("ps ax"):
446         if (("ebuild.sh" in i) and ("/bin/bash" not in i)):
447             pack = i.partition('[')[2].partition(']')[0]
448
449             version = pack.partition('/')[2].partition('-')[2]
450
451             while not version[0].isdigit():
452                 version = version.partition('-')[2]
453
454             package_name = pack[:-len(version)-1]
455
456             PACKAGES.append(package(package_name, version))
457
458
459     if len(PACKAGES) == 0:
460         print "No current emerge process found."
461
462         return 1
463
464
465     for line in f:
466         if ((">>>" in line) and ("emerge" in line)):
467             for p in PACKAGES:
468                 difference = 0
469
470                 if (p.name + '-' + p.version in line):
471
472                     time = float(line.partition(' ')[0].strip(":"))
473
474                     timestamp = datetime.datetime.fromtimestamp(time)
475                     difference = (now - timestamp).total_seconds()
476
477                     if ((difference < p.emerge_time) or
478                         (p.emerge_time == "infinity")):
479
480                         p.emerge_time = difference
481
482     return 0
483
484
485 def search_syncs():
486     f = open_log()
487
488     print "These emerge syncs found"
489     print "\tDate                    Server"
490     print "\t------------------------------"
491
492     for line in f:
493         if "=== Sync completed with" in line:
494             time = float(line.partition(' ')[0].strip(":"))
495             server = line.rpartition(' ')[2]
496
497             print("\t" + GREEN(give_date(time)) +
498                   " === " + server),
499
500
501
502 def main(status, user_package=None):
503     try:
504         _main(status, user_package)
505     except IOError:
506         sys.exit()
507
508
509 def _main(status, user_package=None):
510     """Main function. Hanlde all the different modes of operation."""
511
512     if status == "package":
513         for p in user_package:
514             pack = get_package(p)
515
516             pack = package(pack)
517
518             search_log_for_package(pack)
519
520             if len(pack.versions) != 0:
521                 pack.print_versions()
522                 pack.print_min_max_ave()
523
524                 if matplotWorking:
525                     pack.plotToScreen()
526
527             else:
528                 print("Package " + GREEN(pack.name)
529                       + " has never been emerged.")
530
531
532     elif status == "sync":
533         search_syncs()
534         return
535
536
537     elif status == "list":
538         search_log_for_all_packages()
539         return
540
541
542     elif status == "current":
543         if list_emerge_processes():
544             return
545
546         print "Currently emerging:"
547
548         for p in PACKAGES:
549             search_log_for_package(p)
550             p.print_current_emerge()
551
552
553     elif status == "pretended":
554         list_pretended()
555
556         print "This is how long these packages would take to emerge"
557
558         total_pretended_time = 0
559
560         for p in PACKAGES:
561             search_log_for_package(p)
562
563             total_pretended_time += p.print_pretended_times()
564
565             print
566
567         print("Total emerge time of " + GREEN(str(len(PACKAGES)))
568               + " package(s): "+ give_time(total_pretended_time))
569
570
571 def usage():
572     usage = """Usage: emerge-timer.py [package] [options]
573
574 Calculate emerge times from emerge log.
575
576 Options:
577 \t-c, --current \t Show time until currently compiling package finishes
578 \t-p, --pretended  Calculate compile time from piped 'emerge -p' output
579 \t-l, --list \t List all emerged packages
580 \t-s, --sync \t Show emerge sync history
581 \t-h, --help \t Show this helpscreen
582 \t-q, --quiet \t Be less verbose
583 \t--no-color \t Use colorless output
584 \t--plot \t\t Plot emerge times into a 2D scatter plot
585 \t\t\t (needs matlibplot installed for this to work)
586 \t--simulate \t Do a simulation run"""
587
588     print usage
589
590     sys.exit(0)
591
592
593 if __name__ == "__main__":
594
595     # Set the default mode as "package"
596     mode = "package"
597     input_packages = None
598     simulation = False
599     matplotWorking = False
600
601     for arg in sys.argv[1:]:
602
603         if arg == "-p" or arg == "--pretended":
604             mode = "pretended"
605
606         if arg == "-c" or arg == "--current":
607             mode = "current"
608
609         if arg == "-h" or arg == "--help":
610             usage()
611
612         if arg == "-l" or arg == "--list":
613             mode = "list"
614
615         if arg == "-s" or arg == "--sync":
616             mode = "sync"
617
618         if arg == "-q" or arg == "--quiet":
619             QUIET = True
620
621             sys.argv.pop(sys.argv.index(arg))
622
623         if arg == "--plot":
624             try:
625                 import matplotlib.pyplot as plt
626                 matplotWorking = True
627             except ImportError:
628                 print(RED("Cannot initialize matplotlib!"))
629                 print(RED("Check that you have properly installed matplotlib.\n"))
630                 matplotWorking = False
631
632             sys.argv.pop(sys.argv.index(arg))
633
634         if arg == "--no-color":
635             green_start = ""
636             color_stop = ""
637
638             sys.argv.pop(sys.argv.index(arg))
639
640         if arg == "--simulate":
641             simulation = True
642
643
644     if len(sys.argv) > 1:
645         input_packages = sys.argv[1:]
646     else:
647         usage()
648
649     if simulation == True:
650
651         print(RED("\n" + '*'*25 + "\n" + "THIS IS A SIMULATION RUN\n"
652               + '*'*25 + "\n"))
653
654         print(RED("Beginning 'package' mode check"))
655
656         print(RED("Checking for one emerge\n"))
657
658         LOGFILE = "simulate/fake_emerge.log"
659         PORTDIR = "simulate/"
660
661         main("package", "first_fake_package")
662
663         print(RED("\nChecking for three emerges\n"))
664
665         main("package", "second_fake_package")
666
667         print(RED("\n'package' mode check complete\n"))
668
669         print(RED(30*'*'))
670
671         print(RED("\nBeginning 'current' mode check"))
672         print(RED("Current emerge with no emerge process\n"))
673
674         main("current", None)
675
676         print(RED("\nCurrent emerge with emerge processes\n"))
677
678         PACKAGES.append(package("test-group/second_fake_package", "2.9-r2"))
679         PACKAGES[0].emerge_time = 60
680         PACKAGES.append(package("test-group/first_fake_package", "1.10.2-r1"))
681         PACKAGES[1].emerge_time = 120
682
683         main("current", None)
684
685         print(RED("\nCurrent emerge with incomplete emerge log " + 
686                   "(causes error in some cases)\n"))
687
688         PACKAGES = []
689         PACKAGES.append(package("test-group/third_fake_package", "2.9-r2"))
690
691         main("current", None)
692
693         print(RED("\n" + '*'*20 + "\n" + "SIMULATION FINISHED\n" +
694               '*'*20))
695
696
697     else:
698         main(mode, input_packages)