#!/usr/bin/python # -*- coding: utf-8 -*- # Distributed under the terms of the GNU General Public License v2 # Author: LK4D4 """ Pqlop - python qlop. ~~~~~ Python analog of qlop from portage-utils. """ from __future__ import print_function import argparse import os import sys #import logging from time import ctime, time #logger = logging.getLogger('pqlop logs') #logger.setLevel(logging.DEBUG) #fh = logging.FileHandler('pqlop.log') #fh.setLevel(logging.DEBUG) #logger.addHandler(fh) # things for color output ESC_SEQ = "\x1b[" RED = ESC_SEQ+"31;01m" GREEN = ESC_SEQ+"32;01m" BLUE = ESC_SEQ+"34;01m" YELLOW = ESC_SEQ+"33;01m" RESET = ESC_SEQ+"39;49;00m" def colorize(color, text): """Colorize text, first argument is escape sequence for color""" return color+str(text)+RESET class PqlopUsageFormatter(argparse.HelpFormatter): """Change prefix for usage message.""" def add_usage(self, usage, actions, groups, prefix=colorize(GREEN, "Usage: ")): super(PqlopUsageFormatter, self).add_usage(usage, actions, groups, prefix) class PqlopArgumentParser(argparse.ArgumentParser): """Replace error message with usage message.""" def error(self, message): self.print_usage() self.exit() def red_star(): return colorize(RED,"* ") def setmod(numtime): """Add "s" suffix for plural form: days, hours, minutes.""" if numtime == 1: return "" else: return "s" def print_seconds_for_earthlings(timesecs): """Convert seconds into readable form""" seconds = timesecs % 60 timesecs //= 60 minutes = timesecs % 60 timesecs //= 60 hours = timesecs % 24 timesecs //= 24 days = timesecs % 365 timesecs //= 365 years = timesecs if years: print("%s %s," % (colorize(GREEN, days), "year"+setmod(years)), end=' ') if days: print("%s %s," % (colorize(GREEN, days), "day"+setmod(days)), end=' ') if hours: print("%s %s," % (colorize(GREEN, hours), "hour"+setmod(hours)) ,end=' ') if minutes: print("%s %s," % (colorize(GREEN, minutes), "minute"+setmod(minutes)) , end=' ') print("%s %s" % (colorize(GREEN, seconds), "second"+setmod(seconds)) , end=' ') def atom_explode(atom): """Split package atom to category, package name, package version(with rev).""" cpvr = atom.split('/') if len(cpvr) == 1: pvr = cpvr[0] pcat = "" else: pcat = cpvr[0] pvr = cpvr[1] for enum in enumerate(pvr): if (enum[1] == '-' and pvr[enum[0]+1].isdigit()): pname = pvr[:enum[0]] pver = pvr[enum[0]+1:] return [pcat, pname, pver] def elog_string_explode(elog_string): """Parse string from log.""" # elog_string[:10] - time in Epoch format, very bad decision btw if elog_string[13:24] == ">>> unmerge": return [int(elog_string[:10])] + atom_explode(elog_string.split()[-1]) return [int(elog_string[:10])] + atom_explode(elog_string.split()[-3]) def print_pkg_merges(mergelist, name, need_time, verbose): """Print all merges of specified package with times and summary.""" if mergelist: if '/' in name: name = name.split('/')[1] success = 0 for merge in mergelist: if verbose: pkgstring = "{0}/{1}-{2}".format(merge[3], name, merge[2]) else: pkgstring = "{0}/{1}".format(merge[3], name) if merge[1]: success += 1 print("%s: %s:" % (colorize(BLUE, pkgstring), ctime(merge[0])), end=' ') print_seconds_for_earthlings(merge[1]-merge[0]) print() else: print("%s: %s: %s" % (colorize(BLUE, pkgstring), ctime(merge[0]), colorize(RED,"interrupted"))) mergelen = len(mergelist) print("%s: merged %s times" % (colorize(BLUE, name), colorize(GREEN, mergelen)), end=' ') if success != mergelen: print("(%s interrupted)" % colorize(RED, mergelen-success)) else: print() if need_time: print_avg_merge_time(mergelist, name) def print_avg_merge_time(mergelist, name): """Print merges average time.""" if mergelist: if '/' in name: name = name.split('/')[1] interrupted = 0 time_sum = 0 for merge in mergelist: if not merge[1]: interrupted += 1 continue time_sum = time_sum + (merge[1]-merge[0]) print("%s: average merge time:" % colorize(BLUE, name), end=' ') smerge = len(mergelist)-interrupted print_seconds_for_earthlings(time_sum // (smerge)) print ("for %s successfull merges" % colorize(GREEN, smerge)) def print_merge_list(mergelist, name): """Print merge history.""" if mergelist: if '/' in name: name = name.split('/')[1] for merge in mergelist: if merge[1]: print("%s >>> %s" % (ctime(merge[0]), colorize(GREEN, "{0}/{1}-{2}".format(merge[3], name, merge[2])))) def print_unmerge_list(unmergelist, name): """"Print unmerge history.""" if unmergelist: if '/' in name: name = name.split('/')[1] for unmerge in unmergelist: print("%s <<< %s" % (ctime(unmerge[0]), colorize(RED, "{0}/{1}-{2}".format(unmerge[2], name, unmerge[1])))) def current_merges_iterator(): """Helper for create_current_merge_list.""" for path in os.listdir('/proc/'): try: procstring = open('/proc/'+path+'/cmdline').read() except IOError: continue if "] sandbox" in procstring: tmpsplit = procstring.split() atom = tmpsplit[0].strip('[]') # status - unpack, prepare, configure, compile, install status = tmpsplit[2][:-1] # 0 reserverd fo time if status != "depend": yield [atom, status, 0] def create_current_merge_list(): """Return current emerging packages list.""" return [merge for merge in current_merges_iterator() if merge != None] def print_current_emerge(cmergelist): """Print current emerging packages.""" for cmerge in cmergelist: print(" %s %s" % ('*', colorize(BLUE, cmerge[0]))) print("\tstarted: %s" % colorize(GREEN, ctime(cmerge[2]))) print("\telapsed:", end=' ') print_seconds_for_earthlings(int(time()-cmerge[2])) print() print("\tstatus: %s" % colorize(GREEN, cmerge[1])) def print_sync_history(synclist): """Print sync history and average frequency of syncs""" if synclist: for sync in synclist: print("%s >>> %s" % (ctime(sync[0]), colorize(GREEN, sync[1]))) interval = synclist[-1][0] - synclist[0][0] weeks_sync = interval // (3600*24*7) synclen = len(synclist) avg_sync = synclen / weeks_sync print("Total %s syncs in" % (colorize(GREEN, synclen)), end=' ') print_seconds_for_earthlings(interval) print() print("~ %s sync%s in a week." % (colorize(GREEN,avg_sync),setmod(avg_sync))) def main(): """Main script.""" # full usage message (dirty hack :E) myusage = (colorize(YELLOW,"pqlop")+" " +colorize(BLUE,"")+colorize(RED," : ") +"another emerge log analyzer\n\n" +colorize(GREEN,"Options: ")+"-[tglucf:v]\n" +" -t, --time\t\t"+red_star() +"Calculate average merge time\n" +" -g, --gauge\t\t"+red_star() +"Gauge number of times a package has been merged\n" +" -l, --list\t\t"+red_star() +"Show merge history\n" +" -u, --unlist\t\t"+red_star() +"Show unmerge history\n" +" -s, --sync\t\t"+red_star() +"Show sync history\n" +" -c, --current\t\t"+red_star() +"Show current emerging packages\n" +" -f, --logfile"+colorize(BLUE," \t")+red_star() +"Read emerge logfile instead of /var/log/emerge.log\n" +" -v, --verbose\t\t"+red_star() +"Show additional info about merges") parser = PqlopArgumentParser(usage = myusage, formatter_class=PqlopUsageFormatter, add_help=False) parser.add_argument('pkgnames', nargs='*') parser.add_argument('-t', '--time', dest='need_time', action='store_true') parser.add_argument('-g', '--gauge', dest='need_gauge', action='store_true') parser.add_argument('-l', '--list', dest='need_list', action='store_true') parser.add_argument('-u', '--unlist', dest='need_unlist', action='store_true') parser.add_argument('-f', '--logfile', dest='elog_filename', default='/var/log/emerge.log') parser.add_argument('-s', '--sync', dest='need_sync', action='store_true') parser.add_argument('-c', '--current', dest='need_current', action='store_true') parser.add_argument('-v', '--verbose', dest='verbose', action='store_true') args = parser.parse_args() # exit if no package specified, but -t,-g,-l or -u opts exist if args.pkgnames: if not (args.need_time or args.need_gauge or args.need_list or args.need_unlist): parser.error("") # if no package and no -c opt - exit elif not (args.need_current or args.need_sync): parser.error("") # mergelist - list of tuples: # (,,,) mergelists = [[] for _ in range(len(args.pkgnames))] # unmergelist - list of tuples: # (,,) unmergelists = [[] for _ in range(len(args.pkgnames))] synclist = [] save_struct = [[] for _ in range(len(args.pkgnames))] cmergelist = None if args.need_current: cmergelist = create_current_merge_list() # merging variable indicates, that we locate # between begin and end of merging in elog merging = [False]*len(args.pkgnames) # open file and read it line by line try: elog = open(args.elog_filename) except IOError: print("%s: %s" % (colorize(RED,"There is no such file: ") , args.elog_filename)) exit(1) for line in elog: if line[13:25] == ">>> emerge (": for num, pkgname in enumerate(args.pkgnames): if pkgname in line: pkggroup = elog_string_explode(line) if (pkggroup[2] == pkgname or "{0}/{1}".format(pkggroup[1], pkggroup[2]) == pkgname): if not merging[num]: save_struct[num] = \ [pkggroup[0], pkggroup[1], pkggroup[3]] merging[num] = True else: # if merge was interrupted write 0 as end time, # append merge to list and continue with next merge mergelists[num].append((save_struct[num][0], 0, save_struct[num][2], save_struct[num][1])) save_struct[num] = \ [pkggroup[0], pkggroup[1], pkggroup[3]] break if args.need_current: for cmerge in cmergelist: if cmerge[0] in line: cmerge[2] = int(line[:10]) break elif line[13:26] == "::: completed": for num, pkgname in enumerate(args.pkgnames): if pkgname in line and merging[num]: pkggroup = elog_string_explode(line) if ((pkggroup[2] == pkgname or "{0}/{1}".format(pkggroup[1], pkggroup[2]) == pkgname)): end_time = pkggroup[0] merging[num] = False mergelists[num].append((save_struct[num][0], end_time, save_struct[num][2], save_struct[num][1])) break # create unmerge list if -u specified elif args.need_unlist and line[13:24] == ">>> unmerge": for num, pkgname in enumerate(args.pkgnames): if pkgname in line: pkggroup = elog_string_explode(line) if (pkggroup[2] == pkgname or "{0}/{1}".format(pkggroup[1], pkggroup[2]) == pkgname): unmergelists[num].append((pkggroup[0], pkggroup[3],pkggroup[1])) break elif args.need_sync: if line[12:30] == ">>> Starting rsync": sync_struct = [int(line[:10]), line.split()[-1]] elif line[12:30] == "=== Sync completed": synclist.append(sync_struct) elog.close() # if last emerge is failed - problems with current emerge, # rarely, but strong overhead without using --current really_merging = [True]*len(args.pkgnames) for num, pkgname in enumerate(args.pkgnames): if merging[num]: really_merging[num] = False if not cmergelist: cmergelist = create_current_merge_list() for cmerge in cmergelist: pcn = atom_explode(cmerge[0]) if (pkgname == pcn[1] or pkgname == "{0}/{1}".format(pcn[0], pcn[1])): really_merging[num] = True break if not really_merging[num]: mergelists[num].append((save_struct[num][0], 0, save_struct[num][2], save_struct[num][1])) if args.need_sync: print_sync_history(synclist) if args.need_list: for num, pkgname in enumerate(args.pkgnames): print_merge_list(mergelists[num], pkgname) if args.need_unlist: for num, pkgname in enumerate(args.pkgnames): print_unmerge_list(unmergelists[num], pkgname) if args.need_gauge: for num, pkgname in enumerate(args.pkgnames): print_pkg_merges(mergelists[num], pkgname, args.need_time, args.verbose) if args.need_current: print_current_emerge(cmergelist) if __name__ == "__main__": #import profile #sys.exit(profile.run("main()")) sys.exit(main())