]> git.itanic.dy.fi Git - emerge-timer/blob - emerge-timer.py
Add possibility to plot emerge times into a 2D scatter 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         fig.autofmt_xdate()
216         plt.show()
217
218
219
220 def give_time(time, nocolor=False):
221     """Converts time in seconds to human readable form"""
222     global green_start, color_stop
223
224     if green_start == "":
225         nocolor = False
226
227     if nocolor == True:
228         green_start = ""
229         color_stop = ""
230
231     if type(time) == str:
232         return(GREEN("unknown"))
233
234
235     days = time/(3600.0*24.0)
236     hours = (days - int(days))*24.0
237     minutes = (hours - int(hours))*60.0
238     seconds = (minutes - int(minutes))*60.0
239
240     days = int(days)
241     hours = int(hours)
242     minutes = int(minutes)
243     seconds = int(round(seconds))
244
245     pdays = str()
246     phours = str()
247     pminutes = str()
248     pseconds = str()
249
250     if days > 0:
251         pdays = (GREEN(str(days)) + " day ")
252         if days != 1:
253             pdays = (GREEN(str(days)) + " days ")
254
255     if hours > 0:
256         phours = (GREEN(str(hours)) + " hour ")
257         if hours != 1:
258             phours = (GREEN(str(hours)) + " hours ")
259
260     if minutes > 0:
261         pminutes = (GREEN(str(minutes)) + " minute ")
262         if minutes != 1:
263             pminutes = (GREEN(str(minutes)) + " minutes ")
264
265     if seconds > 0:
266         pseconds = (GREEN(str(seconds)) + " second ")
267         if seconds != 1:
268             pseconds = (GREEN(str(seconds)) + " seconds ")
269
270     if nocolor == True:
271         green_start = "\033[32m"
272         color_stop = "\033[m"
273
274     return (pdays + phours + pminutes + pseconds)
275
276
277
278 def give_date(emerge_date):
279     """Returns a date string from a standard POSIX time"""
280     date = datetime.datetime.fromtimestamp(emerge_date)
281
282     date = "{:%d.%m.%Y %H:%M:%S}".format(date)
283
284     return date
285
286
287
288 def open_log():
289     """Attempt to open the LOGFILE."""
290
291     try:
292         f = open(LOGFILE, 'r')
293     except IOError as detail:
294         print detail
295         sys.exit(1)
296
297     return f
298
299
300
301 def search_log_for_package(package_class):
302     """Searchs emerge log for given package and adds all found
303     versions with their emerge times to the class"""
304
305     log = open_log()
306
307     for line in log:
308         if ((">>>" in line) and ("emerge" in line)):
309             if package_class.name in line:
310
311                 version = line.partition(package_class.name)[2].partition(' ')[0]
312                 digit = version.strip('-')[0].isdigit()
313
314                 if digit:
315                     time_string = line.partition(">>>")
316                     start_time = float(time_string[0].strip().strip(':'))
317
318         elif ((":::" in line) and ("completed emerge" in line)):
319
320             if package_class.name in line:
321                 if digit:
322                     time_string = line.partition(":::")
323                     stop_time = float(time_string[0].strip().strip(':'))
324
325                     emerge_time = stop_time - start_time
326
327                     package_class.add_version(version, emerge_time, start_time)
328
329
330
331 def search_log_for_all_packages():
332     """Goes through the emerge.log and lists all packages in there"""
333
334     log = open_log()
335
336     all_packages = []
337
338     total_emerge_time = 0
339     emerge_number = 0
340
341     for line in log:
342         if ((">>>" in line) and ("emerge" in line)):
343             pack = line.partition(')')[2].strip().partition(' ')[0]
344             start_time = float(line.partition(':')[0])
345
346             all_packages.append((pack, start_time))
347
348         elif ((":::" in line) and ("completed emerge" in line)):
349             for p in all_packages:
350                 if p[0] in line:
351                     stop_time = float(line.partition(':')[0])
352
353                     print("\t" + give_date(p[1]) + " >>> " + GREEN(p[0]))
354
355                     total_emerge_time += stop_time - p[1]
356                     emerge_number += 1
357
358                     all_packages.pop(all_packages.index(p))
359
360     print("\nTotal emerge time of " + GREEN(str(emerge_number)) +
361           " merges: " + give_time(total_emerge_time))
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 \--plot \t Plot emerge times into a 2D scatter plot
585 \t--simulate \t Do a simulation run"""
586
587     print usage
588
589     sys.exit(0)
590
591
592 if __name__ == "__main__":
593
594     # Set the default mode as "package"
595     mode = "package"
596     input_packages = None
597     simulation = False
598     matplotWorking = False
599
600     for arg in sys.argv[1:]:
601
602         if arg == "-p" or arg == "--pretended":
603             mode = "pretended"
604
605         if arg == "-c" or arg == "--current":
606             mode = "current"
607
608         if arg == "-h" or arg == "--help":
609             usage()
610
611         if arg == "-l" or arg == "--list":
612             mode = "list"
613
614         if arg == "-s" or arg == "--sync":
615             mode = "sync"
616
617         if arg == "-q" or arg == "--quiet":
618             QUIET = True
619
620             sys.argv.pop(sys.argv.index(arg))
621
622         if arg == "--plot":
623             try:
624                 import matplotlib.pyplot as plt
625                 matplotWorking = True
626             except Exception:
627                 print "Cannot initialize matplotlib!"
628                 print "Check that you have properly installet matplotlib."
629                 matplotWorking = False
630
631         if arg == "--no-color":
632             green_start = ""
633             color_stop = ""
634
635             sys.argv.pop(sys.argv.index(arg))
636
637         if arg == "--simulate":
638             simulation = True
639
640
641     if len(sys.argv) > 1:
642         input_packages = sys.argv[1:]
643     else:
644         usage()
645
646     if simulation == True:
647
648         print(RED("\n" + '*'*25 + "\n" + "THIS IS A SIMULATION RUN\n"
649               + '*'*25 + "\n"))
650
651         print(RED("Beginning 'package' mode check"))
652
653         print(RED("Checking for one emerge\n"))
654
655         LOGFILE = "simulate/fake_emerge.log"
656         PORTDIR = "simulate/"
657
658         main("package", "first_fake_package")
659
660         print(RED("\nChecking for three emerges\n"))
661
662         main("package", "second_fake_package")
663
664         print(RED("\n'package' mode check complete\n"))
665
666         print(RED(30*'*'))
667
668         print(RED("\nBeginning 'current' mode check"))
669         print(RED("Current emerge with no emerge process\n"))
670
671         main("current", None)
672
673         print(RED("\nCurrent emerge with emerge processes\n"))
674
675         PACKAGES.append(package("test-group/second_fake_package", "2.9-r2"))
676         PACKAGES[0].emerge_time = 60
677         PACKAGES.append(package("test-group/first_fake_package", "1.10.2-r1"))
678         PACKAGES[1].emerge_time = 120
679
680         main("current", None)
681
682         print(RED("\nCurrent emerge with incomplete emerge log " + 
683                   "(causes error in some cases)\n"))
684
685         PACKAGES = []
686         PACKAGES.append(package("test-group/third_fake_package", "2.9-r2"))
687
688         main("current", None)
689
690         print(RED("\n" + '*'*20 + "\n" + "SIMULATION FINISHED\n" +
691               '*'*20))
692
693
694     else:
695         main(mode, input_packages)