diff --git a/guider/guider.py b/guider/guider.py index 2ebae0f4..9812a9c5 100755 --- a/guider/guider.py +++ b/guider/guider.py @@ -7,7 +7,7 @@ __credits__ = "Peace Lee" __license__ = "GPLv2" __version__ = "3.9.8" -__revision__ = "240909" +__revision__ = "240910" __maintainer__ = "Peace Lee" __email__ = "iipeace5@gmail.com" __repository__ = "https://github.com/iipeace/guider" @@ -27480,6 +27480,7 @@ class SysMgr(object): idList = [] commCache = {} commFdCache = {} + ppidCache = {} fdCache = {} libCache = {} rawFdCache = {} @@ -35596,16 +35597,8 @@ def getCmdline(pid, retList=False, cache=False, default=""): @staticmethod def getTracerId(pid): - try: - statusPath = "%s/%s/status" % (SysMgr.procPath, pid) - with open(statusPath, "r") as fd: - for line in fd.readlines(): - if line.startswith("TracerPid"): - return long(line.split(":")[1].split(None, 1)[0]) - except SystemExit: - sys.exit(0) - except: - return 0 + tpid = SysMgr.getTaskAttr(pid, "TracerPid") + return tpid if tpid else 0 @staticmethod def getUid(pid, itype="real"): @@ -35636,30 +35629,37 @@ def getUid(pid, itype="real"): return 0 @staticmethod - def getPpid(pid, target="PPid"): + def getTaskAttr(pid, target): statusPath = "%s/%s/status" % (SysMgr.procPath, pid) try: with open(statusPath, "r") as fd: - for line in fd.readlines(): - if line.startswith(target): - return line.split(":")[1].split(None, 1)[0] + return ( + fd.read() + .split(target + ":", 1)[1] + .lstrip() + .split("\n", 1)[0] + ) except SystemExit: sys.exit(0) except: return None + @staticmethod + def getPpid(pid, cache=False): + if cache: + ppid = SysMgr.ppidCache.get(pid) + if ppid: + return ppid + + ppid = SysMgr.getTaskAttr(pid, "PPid") + if ppid and cache: + SysMgr.ppidCache[pid] = ppid + + return ppid + @staticmethod def getTgid(pid): - statusPath = "%s/%s/status" % (SysMgr.procPath, pid) - try: - with open(statusPath, "r") as fd: - for line in fd.readlines(): - if line.startswith("Tgid"): - return line.split(":")[1].split(None, 1)[0] - except SystemExit: - sys.exit(0) - except: - return None + return SysMgr.getTaskAttr(pid, "Tgid") @staticmethod def resizeShm(shm, size): @@ -35759,8 +35759,10 @@ def clearCommCache(): @staticmethod def getComm(pid, cache=False, save=False, commCache=True, default=None): - if commCache and pid in SysMgr.commCache: - return SysMgr.commCache[pid] + if commCache: + comm = SysMgr.commCache.get(pid) + if comm: + return comm # linux # try: @@ -38182,6 +38184,9 @@ def printHelp(force=False, isExit=True): - {2:1} except for load plots including CPU and I/O usage # {0:1} {1:1} {4:1} -q NOLOADPLOT + - {2:1} except for PSI plots + # {0:1} {1:1} {4:1} -q NOPSIPLOT + - {2:1} except for specific cpu plots # {0:1} {1:1} {4:1} -q MAXCPUCOND:10 # {0:1} {1:1} {4:1} -q AVGCPUCOND:10 @@ -43369,11 +43374,13 @@ def _getDesc(s, t=0): - {4:1} the current mount point with process info {3:1} {2:1} # {0:1} {1:1} -q INMOUNT + # {0:1} {1:1} -q INMOUNT, NOPROC # {0:1} {1:1} -q INMOUNT, TARGETCOMM:"kworker*", EXCEPTCOMM:"*1234" # {0:1} {1:1} -q INMOUNT, TARGETFILE:"*.data", EXCEPTFILE:"temp*" - {4:1} all mount points with process info {3:1} {2:1} # {0:1} {1:1} -q ALLMOUNT + # {0:1} {1:1} -q ALLMOUNT, NOPROC # {0:1} {1:1} -q ALLMOUNT, TARGETCOMM:"kworker*", EXCEPTCOMM:"*1234" # {0:1} {1:1} -q ALLMOUNT, TARGETFILE:"*.data", EXCEPTFILE:"temp*" # {0:1} {1:1} -q ALLMOUNT, EVENTCMD:"ls -lha", EVENTCMD:"touch PATH&" @@ -66782,11 +66789,18 @@ def _printSummary(fileSummary={}, procSummary={}, final=False): if not path: continue + # get size and path # + fsize = UtilMgr.getFileSizeStr(path) + faccess = convNum(vals.get("TOTAL", 0)) full = path.rsplit("/", 1) if len(full) == 2: path = full[0] + "/" + convColor(full[1], "GREEN") + outStr += path + " (%s)" % faccess + fsize + "\n" + + # skip process info # + if "NOPROC" in SysMgr.environList: + continue - outStr += path + "\n" for proc, subvals in vals.items(): if proc == "TOTAL": continue @@ -66830,6 +66844,8 @@ def _printSummary(fileSummary={}, procSummary={}, final=False): if not fileSummary: outStr += " None\n%s\n" % oneLine + elif "NOPROC" in SysMgr.environList: + outStr += oneLine + "\n" # print file list # if isTopMode: @@ -67188,8 +67204,8 @@ def _alarmHandler(signum, frame): if len(item) == 6: ppid, pcomm = item[4:6] else: - ppid = SysMgr.getPpid(epid) - pcomm = SysMgr.getComm(ppid) + ppid = SysMgr.getPpid(epid, cache=True) + pcomm = SysMgr.getComm(ppid, save=True) # check file condition # if targetInfo: @@ -118733,6 +118749,7 @@ def getStatsFile( netRead = [] netWrite = [] gpuUsage = {} + psiUsage = {} cpuProcUsage = {} cpuProcDelay = {} cpuProcPrio = {} @@ -119232,6 +119249,21 @@ def getStatsFile( memProcUsage[pname]["rssUsage"] = intervalList intervalList = None + # PSI # + elif context == "PSI": + if slen == 3: + gname = sline[0].strip() + if gname == "RES": + continue + intervalList = sline[2] + elif slen == 2: + if intervalList: + intervalList += sline[1] + elif intervalList: + # save previous info # + psiUsage[gname] = intervalList + intervalList = None + # Block # elif context == "Block": if slen == 3: @@ -119580,33 +119612,27 @@ def getStatsFile( imax = timeline.index(vals[-1]) # trim intervals # - for name, value in cpuProcUsage.items(): - value["usage"] = value["usage"].split()[imin:imax] - value["usage"] = " ".join(value["usage"]) - - for name, value in blkProcUsage.items(): - value["usage"] = value["usage"].split()[imin:imax] - value["usage"] = " ".join(value["usage"]) + for resUsage in (cpuProcUsage, blkProcUsage): + for name, value in resUsage.items(): + value["usage"] = value["usage"].split()[imin:imax] + value["usage"] = " ".join(value["usage"]) for name, value in memProcUsage.items(): - if "vssUsage" in value: - value["vssUsage"] = value["vssUsage"].split()[imin:imax] - value["vssUsage"] = " ".join(value["vssUsage"]) - if "rssUsage" in value: - value["rssUsage"] = value["rssUsage"].split()[imin:imax] - value["rssUsage"] = " ".join(value["rssUsage"]) - - for name, value in gpuUsage.items(): - value = value.split()[imin:imax] - gpuUsage[name] = " ".join(value) + for nmUsage in ("vssUsage", "rssUsage"): + if not nmUsage in value: + continue + value[nmUsage] = value[nmUsage].split()[imin:imax] + value[nmUsage] = " ".join(value[nmUsage]) - for name, dev in storageUsage.items(): - for item, value in dev.items(): - storageUsage[name][item] = value[imin:imax] + for resUsage in (gpuUsage, psiUsage): + for name, value in resUsage.items(): + value = value.split()[imin:imax] + resUsage[name] = " ".join(value) - for name, dev in networkUsage.items(): - for item, value in dev.items(): - networkUsage[name][item] = value[imin:imax] + for resUsage in (storageUsage, networkUsage): + for name, dev in resUsage.items(): + for item, value in dev.items(): + resUsage[name][item] = value[imin:imax] else: # set range index # imin = 0 @@ -119662,6 +119688,7 @@ def getStatsFile( "memKernel": memKernel[imin:imax], "memProcUsage": memProcUsage, "gpuUsage": gpuUsage, + "psiUsage": psiUsage, "totalRam": totalRam, "swapUsage": swapUsage[imin:imax], "totalSwap": totalSwap, @@ -121166,21 +121193,21 @@ def _mergeTasks(atype): # TODO: dict format stats # if n in ( + "blkRead", + "blkWait", + "blkWrite", "cpuUsage", - "memFree", + "eventList", "memAnon", "memCache", + "memFree", "memKernel", - "blkWait", - "blkRead", - "blkWrite", "netRead", "netWrite", - "swapUsage", + "nrCore", "reclaimBg", "reclaimDr", - "nrCore", - "eventList", + "swapUsage", ): diffList = UtilMgr.calcLists( "add" if n == "eventList" else "sub", @@ -121207,6 +121234,8 @@ def _mergeTasks(atype): pass elif n in ("storageUsage"): pass + elif n in ("psiUsage"): + pass else: pass @@ -122985,10 +123014,68 @@ def _drawCpu(graphStats, xtype, pos, size, delay=False): for idx, item in enumerate(blkWait): blkWait[idx] += cpuUsage[idx] - for targetUsage, color, name in ( - (blkWait, "pink", "CPU(%s)+IOWAIT" % conv(maxCore)), - (cpuUsage, "red", "CPU(%s)" % conv(maxCore)), - ): + # define PSI colors # + psiColors = { + "cpu": "pink", + "mem": "blue", + "io": "cyan", + } + + # define stat list # + coreStatList = [ + ( + blkWait, + "pink", + "CPU(%s)+IOWAIT" % conv(maxCore), + bold, + "", + ), + (cpuUsage, "red", "CPU(%s)" % conv(maxCore), bold, ""), + ] + + # process PSI stats # + psiStats = ( + {} + if "NOPSIPLOT" in SysMgr.environList + else graphStats.get("psiUsage", {}) + ) + for psiRes, psiStat in sorted(psiStats.items()): + # convert lists # + someList = [] + fullList = [] + for p in psiStat.split(): + if p == "0": + some = full = 0 + else: + some, full = list(map(long, p.split("/"))) + someList.append(some) + fullList.append(full) + + # add lists # + for pattr, plist in ( + ("some", someList), + ("full", fullList), + ): + if max(plist) <= 0: + continue + pname = "PSI(%s-%s)" % (psiRes.upper(), pattr) + coreStatList.append( + ( + plist, + psiColors[psiRes], + pname, + False, + pname + " -", + ) + ) + + for ( + targetUsage, + color, + name, + boldattr, + tname, + ) in coreStatList: # skip useless plot # if ( targetUsage == blkWait @@ -123008,14 +123095,14 @@ def _drawCpu(graphStats, xtype, pos, size, delay=False): if prefix: color = None - # draw total CPU + IOWAIT graph # + # draw total graph # plot( timeline, targetUsage, c=color, linestyle="-", - linewidth=1 if bold else 0.1, - marker="." if bold else None, + linewidth=1 if boldattr else 0.5, + marker="." if boldattr else None, markersize=1, path_effects=_getPathEffect(), solid_capstyle="round", @@ -123053,16 +123140,17 @@ def _drawCpu(graphStats, xtype, pos, size, delay=False): text( timeline[idx], targetUsage[maxIdx], - "%s Max_%d%% | Avg_%.1f%% | Total_%s%%" + "%s %sMax_%d%% | Avg_%.1f%% | Total_%s%%" % ( prefix, + tname, maxUsage, avgUsage, conv(totalUsage), ), fontsize=self.lfsize + 1, color=color, - fontweight="bold", + fontweight="bold" if boldattr else None, bbox=dict( boxstyle="round", facecolor="wheat", @@ -124482,7 +124570,7 @@ def __drawSystemMem(statList, color, ymax, name): margin = self.getMargin() def __drawMemPlots(ymax, stacked=False): - # set target function # + # set system memory drawing function # if stacked: # add stats to stacked list # for skipFlag, mval in ( @@ -124496,9 +124584,20 @@ def __drawMemPlots(ymax, stacked=False): continue stackedStats.append(mval) + # get ymax of stack # + stackYmax = max( + [ + sum(x) + for x in zip( + memFree, memAnon, memCache, memKernel + ) + ] + ) + + # define system memory drawer # __drawSysMem = lambda a, b, c, d: ( convSize(a[-1] << 20) if a else None, - max(max(a), c), + stackYmax, ) else: __drawSysMem = __drawSystemMem @@ -126228,13 +126327,13 @@ def __drawSystemMem(statList, color, ymax): timeline = xrange(len(graphStats["timeline"])) lent = len(timeline) - totalRam = graphStats["totalRam"] - memFree = graphStats["memFree"] - memAnon = graphStats["memAnon"] - memCache = graphStats["memCache"] - memProc = graphStats["memProcUsage"] - totalSwap = graphStats["totalSwap"] - swapUsage = graphStats["swapUsage"] + totalRam = graphStats.get("totalRam", 0) + memFree = graphStats.get("memFree", []) + memAnon = graphStats.get("memAnon", []) + memCache = graphStats.get("memCache", []) + memProc = graphStats.get("memProcUsage", {}) + totalSwap = graphStats.get("totalSwap", 0) + swapUsage = graphStats.get("swapUsage", []) # get margin # margin = self.getMargin() @@ -131333,7 +131432,21 @@ def parseProcLine(index, procLine): # save temporary data on this interval # try: procIntData["total"].update(TA.procTempData) - TA.procTempData = {} + + psiStats = TA.procTempData.get("psi", {}) + for s, v in psiStats.items(): + TA.procTotData["total"].setdefault("psi", {}) + totalStats = TA.procTotData["total"]["psi"] + if s in totalStats: + totalStats[s]["min"] = min(totalStats[s]["min"], v) + totalStats[s]["max"] = max(totalStats[s]["max"], v) + totalStats[s]["total"] += v + else: + totalStats[s] = { + "min": v, + "max": v, + "total": v, + } except: pass @@ -132493,7 +132606,99 @@ def _conv(val): @staticmethod def printDlyInterval(): - TaskAnalyzer.printCpuInterval(attr="dly") + TA = TaskAnalyzer + + # print scheduler delay # + TA.printCpuInterval(attr="dly") + + # check PSI stats # + if "NOPSISUMMARY" in SysMgr.environList: + return + elif not "psi" in TA.procTotData["total"]: + return + + if SysMgr.jsonEnable: + _printer = lambda x: x + else: + _printer = SysMgr.printPipe + + _printer("\n[Top PSI Info] (Unit: %%)\n%s\n" % twoLine) + + # Print menu # + psiInfo = "{0:>10} | {1:^30} |".format("RES", "Min/Avg/Max/Tot") + psiInfoLen = len(psiInfo) + maxLineLen = SysMgr.lineLength + margin = 5 + + # Print timeline # + TA.printTimelineInterval(margin, psiInfoLen, psiInfo, 1) + + # Merge PSI stats # + mergedStats = {} + for psi, stat in sorted( + TA.procTotData["total"]["psi"].items(), reverse=True + ): + try: + res, attr = psi.split("_") + except: + SysMgr.printWarn( + "failed to parse PSI name '%s'" % psi, True, True + ) + continue + + token = ":" if res in mergedStats else "" + mergedStats.setdefault( + res, {"min": "", "max": "", "avg": "", "total": ""} + ) + mergedStats[res]["min"] += token + str(long(stat["min"])) + mergedStats[res]["max"] += token + str(long(stat["max"])) + mergedStats[res]["total"] += token + str(long(stat["total"])) + mergedStats[res]["avg"] += token + str( + long(stat["total"] / len(TA.procIntData)) + ) + + usage = [] + for idx in xrange(len(TA.procIntData)): + usage.append(long(TA.procIntData[idx]["total"]["psi"][psi])) + mergedStats[res][attr] = usage + + # Merge PSI stats # + for res, stats in dict(mergedStats).items(): + mergedStats[res]["usage"] = [ + "0" if some == full == 0 else "%d/%d" % (some, full) + for some, full in zip(stats["some"], stats["full"]) + ] + mergedStats[res].pop("some") + mergedStats[res].pop("full") + + for res, stat in sorted(mergedStats.items()): + # get stats # + stats = "%s/%s/%s/%s" % ( + stat["min"], + stat["avg"], + stat["max"], + stat["total"], + ) + + psiInfo = "{0:>10} | {1:^30} |".format(res, stats) + psiInfoLen = len(psiInfo) + maxLineLen = SysMgr.lineLength + + timeLine = "" + lineLen = len(psiInfo) + total = 0 + margin = 5 + for usage in stat["usage"]: + if lineLen + margin > maxLineLen: + timeLine += "\n" + (" " * (psiInfoLen - 1)) + "| " + lineLen = len(psiInfo) + + timeLine += "{0:>6} ".format(usage) + lineLen += 7 + + _printer( + ("{0:1} {1:1}\n{2:1}\n").format(psiInfo, timeLine, oneLine) + ) @staticmethod def printGpuInterval(): @@ -133809,6 +134014,7 @@ def _initData(): TaskAnalyzer.procIntData = [] TaskAnalyzer.procTreeData = {} TaskAnalyzer.procPrioData = {} + TaskAnalyzer.procTempData = {} # shrink heap # SysMgr.shrinkHeap()