]> git.itanic.dy.fi Git - emerge-timer/blob - emerge-timer.py
7173abe705c41f95bad8afd34b802fbbdb0b42a6
[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 seek_versions(self, whatToSeek, Input):
42
43         if whatToSeek == "version":
44             for i in self.versions:
45                 if i[1] == Input:
46                     return i[2]
47
48
49     def average_time(self):
50         """Return average time from class version list"""
51         total_time = 0
52         for i in self.versions:
53             total_time += i[1]
54
55         average_time = total_time/len(self.versions)
56
57         return average_time
58
59
60     def total_time(self):
61         """Return total time from class version list"""
62         total_time = 0
63         for i in self.versions:
64             total_time += i[1]
65
66         return total_time
67
68
69     def min_max_time(self, setting):
70         """Return maximum or minimum time from class version list"""
71
72         emerge_times = []
73         for i in self.versions:
74             emerge_times.append(i[1])
75
76         emerge_times.sort()
77
78         if setting == "max":
79             return emerge_times[-1]
80         elif setting == "min":
81             return emerge_times[0]
82
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.min_max_time("max")
192         mini = self.min_max_time("min")
193         average = self.average_time()
194         total = self.total_time()
195
196         print("Max time:\t" + give_time(maxi) + "on " +
197               give_date(self.seek_versions("version", maxi)) +
198               "\nMin time:\t" + give_time(mini) + "on " +
199               give_date(self.seek_versions("version", mini)) +
200               "\nAverage time:\t" + give_time(average) +
201               "\n\nIn total spent:\t" + give_time(total) +
202               "emerging " + GREEN(self.name))
203
204     def plotToScreen(self):
205         dates = []
206         times = []
207
208         for i in self.plotData:
209             dates.append(datetime.date.fromtimestamp(i[1]))
210             times.append(i[0])
211
212         fig = plt.figure()
213
214         plt.plot_date(dates, times, xdate=True, ydate=False)
215
216         plt.ylabel("Emerge time [s]")
217         plt.suptitle(self.name)
218         plt.grid()
219         fig.autofmt_xdate()
220         plt.show()
221
222
223
224 def give_time(time, nocolor=False):
225     """Converts time in seconds to human readable form"""
226     global green_start, color_stop
227
228     if green_start == "":
229         nocolor = False
230
231     if nocolor == True:
232         green_start = ""
233         color_stop = ""
234
235     if type(time) == str:
236         return(GREEN("unknown"))
237
238
239     days = time/(3600.0*24.0)
240     hours = (days - int(days))*24.0
241     minutes = (hours - int(hours))*60.0
242     seconds = (minutes - int(minutes))*60.0
243
244     days = int(days)
245     hours = int(hours)
246     minutes = int(minutes)
247     seconds = int(round(seconds))
248
249     pdays = str()
250     phours = str()
251     pminutes = str()
252     pseconds = str()
253
254     if days > 0:
255         pdays = (GREEN(str(days)) + " day ")
256         if days != 1:
257             pdays = (GREEN(str(days)) + " days ")
258
259     if hours > 0:
260         phours = (GREEN(str(hours)) + " hour ")
261         if hours != 1:
262             phours = (GREEN(str(hours)) + " hours ")
263
264     if minutes > 0:
265         pminutes = (GREEN(str(minutes)) + " minute ")
266         if minutes != 1:
267             pminutes = (GREEN(str(minutes)) + " minutes ")
268
269     if seconds > 0:
270         pseconds = (GREEN(str(seconds)) + " second ")
271         if seconds != 1:
272             pseconds = (GREEN(str(seconds)) + " seconds ")
273
274     if nocolor == True:
275         green_start = "\033[32m"
276         color_stop = "\033[m"
277
278     return (pdays + phours + pminutes + pseconds)
279
280
281
282 def give_date(emerge_date):
283     """Returns a date string from a standard POSIX time"""
284     date = datetime.datetime.fromtimestamp(emerge_date)
285
286     date = "{:%d.%m.%Y %H:%M:%S}".format(date)
287
288     return date
289
290
291
292 def open_log():
293     """Attempt to open the LOGFILE."""
294
295     try:
296         f = open(LOGFILE, 'r')
297     except IOError as detail:
298         print detail
299         sys.exit(1)
300
301     return f
302
303
304
305 def search_log_for_package(package_class):
306     """Searchs emerge log for given package and adds all found
307     versions with their emerge times to the class"""
308
309     log = open_log()
310
311     for line in log:
312         if ((">>>" in line) and ("emerge" in line)):
313             if package_class.name in line:
314
315                 version = line.partition(package_class.name)[2].partition(' ')[0]
316                 digit = version.strip('-')[0].isdigit()
317
318                 if digit:
319                     time_string = line.partition(">>>")
320                     start_time = float(time_string[0].strip().strip(':'))
321
322         elif ((":::" in line) and ("completed emerge" in line)):
323
324             if package_class.name in line:
325                 if digit:
326                     time_string = line.partition(":::")
327                     stop_time = float(time_string[0].strip().strip(':'))
328
329                     emerge_time = stop_time - start_time
330
331                     package_class.add_version(version, emerge_time, start_time)
332
333
334
335 def search_log_for_all_packages():
336     """Goes through the emerge.log and lists all packages in there"""
337
338     log = open_log()
339
340     all_packages = []
341
342     total_emerge_time = 0
343     emerge_number = 0
344
345     for line in log:
346         if ((">>>" in line) and ("emerge" in line)):
347             pack = line.partition(')')[2].strip().partition(' ')[0]
348             start_time = float(line.partition(':')[0])
349
350             all_packages.append((pack, start_time))
351
352         elif ((":::" in line) and ("completed emerge" in line)):
353             for p in all_packages:
354                 if p[0] in line:
355                     print("\t" + give_date(p[1]) + " >>> " + GREEN(p[0]))
356
357                     emerge_number += 1
358
359                     all_packages.pop(all_packages.index(p))
360
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. Handle 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
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     main(mode, input_packages)