]> git.itanic.dy.fi Git - emerge-timer/blob - emerge-timer.py
Don't print the total emerge time
[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("\n" + GREEN(str(emerge_number)) + " emerges in total found.")
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)