]> git.itanic.dy.fi Git - emerge-timer/blob - emerge-timer.py
Add background grid to the plot
[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                     stop_time = float(line.partition(':')[0])
354
355                     print("\t" + give_date(p[1]) + " >>> " + GREEN(p[0]))
356
357                     total_emerge_time += stop_time - p[1]
358                     emerge_number += 1
359
360                     all_packages.pop(all_packages.index(p))
361
362     print("\n" + GREEN(str(emerge_number)) + " emerges in total found.")
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)