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