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