]> git.itanic.dy.fi Git - emerge-timer/blob - emerge-timer.py
search_syncs(): print how many syncs have been performed
[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     a = 0
494     for line in f:
495         if "=== Sync completed with" in line:
496             time = float(line.partition(' ')[0].strip(":"))
497             server = line.rpartition(' ')[2]
498
499             print("\t" + GREEN(give_date(time)) +
500                   " === " + server),
501             a += 1
502
503     print("\n" + GREEN(str(a)) + " emerge syncs found. ")
504
505
506 def main(status, user_package=None):
507     try:
508         _main(status, user_package)
509     except IOError:
510         sys.exit()
511
512
513 def _main(status, user_package=None):
514     """Main function. Handle all the different modes of operation."""
515
516     if status == "package":
517         for p in user_package:
518             pack = get_package(p)
519
520             pack = package(pack)
521
522             search_log_for_package(pack)
523
524             if len(pack.versions) != 0:
525                 pack.print_versions()
526                 pack.print_min_max_ave()
527
528                 if matplotWorking:
529                     pack.plotToScreen()
530
531             else:
532                 print("Package " + GREEN(pack.name)
533                       + " has never been emerged.")
534
535
536     elif status == "sync":
537         search_syncs()
538         return
539
540
541     elif status == "list":
542         search_log_for_all_packages()
543         return
544
545
546     elif status == "current":
547         if list_emerge_processes():
548             return
549
550         print "Currently emerging:"
551
552         for p in PACKAGES:
553             search_log_for_package(p)
554             p.print_current_emerge()
555
556
557     elif status == "pretended":
558         list_pretended()
559
560         print "This is how long these packages would take to emerge"
561
562         total_pretended_time = 0
563
564         for p in PACKAGES:
565             search_log_for_package(p)
566
567             total_pretended_time += p.print_pretended_times()
568
569             print
570
571         print("Total emerge time of " + GREEN(str(len(PACKAGES)))
572               + " package(s): "+ give_time(total_pretended_time))
573
574
575 def usage():
576     usage = """Usage: emerge-timer.py [package] [options]
577
578 Calculate emerge times from emerge log.
579
580 Options:
581 \t-c, --current \t Show time until currently compiling package finishes
582 \t-p, --pretended  Calculate compile time from piped 'emerge -p' output
583 \t-l, --list \t List all emerged packages
584 \t-s, --sync \t Show emerge sync history
585 \t-h, --help \t Show this helpscreen
586 \t-q, --quiet \t Be less verbose
587 \t--no-color \t Use colorless output
588 \t--plot \t\t Plot emerge times into a 2D scatter plot
589 \t\t\t (needs matlibplot installed for this to work)"""
590
591
592     print usage
593
594     sys.exit(0)
595
596
597 if __name__ == "__main__":
598
599     # Set the default mode as "package"
600     mode = "package"
601     input_packages = None
602     simulation = False
603     matplotWorking = False
604
605     for arg in sys.argv[1:]:
606
607         if arg == "-p" or arg == "--pretended":
608             mode = "pretended"
609
610         if arg == "-c" or arg == "--current":
611             mode = "current"
612
613         if arg == "-h" or arg == "--help":
614             usage()
615
616         if arg == "-l" or arg == "--list":
617             mode = "list"
618
619         if arg == "-s" or arg == "--sync":
620             mode = "sync"
621
622         if arg == "-q" or arg == "--quiet":
623             QUIET = True
624
625             sys.argv.pop(sys.argv.index(arg))
626
627         if arg == "--plot":
628             try:
629                 import matplotlib.pyplot as plt
630                 matplotWorking = True
631             except ImportError:
632                 print(RED("Cannot initialize matplotlib!"))
633                 print(RED("Check that you have properly installed matplotlib.\n"))
634                 matplotWorking = False
635
636             sys.argv.pop(sys.argv.index(arg))
637
638         if arg == "--no-color":
639             green_start = ""
640             color_stop = ""
641
642             sys.argv.pop(sys.argv.index(arg))
643
644         if arg == "--simulate":
645             simulation = True
646
647
648     if len(sys.argv) > 1:
649         input_packages = sys.argv[1:]
650     else:
651         usage()
652
653     main(mode, input_packages)