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