/usr/share/arm/util/procTools.py is in tor-arm 1.4.5.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | """
Helper functions for querying process and system information from the /proc
contents. Fetching information this way provides huge performance benefits
over lookups via system utilities (ps, netstat, etc). For instance, resolving
connections this way cuts the runtime by around 90% verses the alternatives.
These functions may not work on all platforms (only Linux?).
All functions raise IOErrors if unable to read their respective proc files.
The method for reading these files (and some of the code) are borrowed from
psutil:
https://code.google.com/p/psutil/
which was written by Jay Loden, Dave Daeschler, Giampaolo Rodola' and is under
the BSD license.
"""
import os
import sys
import time
import socket
import base64
from util import enum, log
# cached system values
SYS_START_TIME, SYS_PHYSICAL_MEMORY = None, None
CLOCK_TICKS = os.sysconf(os.sysconf_names["SC_CLK_TCK"])
Stat = enum.Enum("COMMAND", "CPU_UTIME", "CPU_STIME", "START_TIME")
CONFIG = {"queries.useProc": True,
"log.procCallMade": log.DEBUG}
def loadConfig(config):
config.update(CONFIG)
def isProcAvailable():
"""
Provides true if configured to use proc resolution and it's available on the
platform, false otherwise.
"""
return CONFIG["queries.useProc"] and os.uname()[0] == "Linux"
def getSystemStartTime():
"""
Provides the unix time (seconds since epoch) when the system started.
"""
global SYS_START_TIME
if not SYS_START_TIME:
startTime = time.time()
statFile = open('/proc/stat')
statLines = statFile.readlines()
statFile.close()
for line in statLines:
if line.startswith('btime'):
SYS_START_TIME = float(line.strip().split()[1])
break
_logProcRuntime("system start time", "/proc/stat[btime]", startTime)
return SYS_START_TIME
def getPhysicalMemory():
"""
Provides the total physical memory on the system in bytes.
"""
global SYS_PHYSICAL_MEMORY
if not SYS_PHYSICAL_MEMORY:
startTime = time.time()
memFile = open('/proc/meminfo')
memLines = memFile.readlines()
memFile.close()
for line in memLines:
if line.startswith('MemTotal:'):
SYS_PHYSICAL_MEMORY = int(line.split()[1]) * 1024
_logProcRuntime("system physical memory", "/proc/meminfo[MemTotal]", startTime)
return SYS_PHYSICAL_MEMORY
def getPwd(pid):
"""
Provides the current working directory for the given process.
Arguments:
pid - queried process
"""
startTime = time.time()
if pid == 0: cwd = ""
else: cwd = os.readlink("/proc/%s/cwd" % pid)
_logProcRuntime("cwd", "/proc/%s/cwd" % pid, startTime)
return cwd
def getUid(pid):
"""
Provides the user ID the given process is running under. This is None if it
can't be determined.
Arguments:
pid - queried process
"""
startTime = time.time()
statusFile = open("/proc/%s/status" % pid)
statusFileLines = statusFile.readlines()
statusFile.close()
result = None
for line in statusFileLines:
if line.startswith("Uid:"):
lineComp = line.split()
if len(lineComp) >= 2 and lineComp[1].isdigit():
result = lineComp[1]
_logProcRuntime("uid", "/proc/%s/status[Uid]" % pid, startTime)
return result
def getMemoryUsage(pid):
"""
Provides the memory usage in bytes for the given process of the form:
(residentSize, virtualSize)
Arguments:
pid - queried process
"""
# checks if this is the kernel process
if pid == 0: return (0, 0)
startTime = time.time()
statusFile = open("/proc/%s/status" % pid)
statusFileLines = statusFile.readlines()
statusFile.close()
residentSize, virtualSize = None, None
for line in statusFileLines:
if line.startswith("VmRSS"):
residentSize = int(line.split()[1]) * 1024
if virtualSize != None: break
elif line.startswith("VmSize:"):
virtualSize = int(line.split()[1]) * 1024
if residentSize != None: break
_logProcRuntime("memory usage", "/proc/%s/status[VmRSS|VmSize]" % pid, startTime)
return (residentSize, virtualSize)
def getStats(pid, *statTypes):
"""
Provides process specific information. Options are:
Stat.COMMAND command name under which the process is running
Stat.CPU_UTIME total user time spent on the process
Stat.CPU_STIME total system time spent on the process
Stat.START_TIME when this process began, in unix time
Arguments:
pid - queried process
statTypes - information to be provided back
"""
startTime = time.time()
statFilePath = "/proc/%s/stat" % pid
statFile = open(statFilePath)
statContents = statFile.read().strip()
statFile.close()
# contents are of the form:
# 8438 (tor) S 8407 8438 8407 34818 8438 4202496...
statComp = []
cmdStart, cmdEnd = statContents.find("("), statContents.find(")")
if cmdStart != -1 and cmdEnd != -1:
statComp.append(statContents[:cmdStart])
statComp.append(statContents[cmdStart + 1:cmdEnd])
statComp += statContents[cmdEnd + 1:].split()
if len(statComp) != 44:
raise IOError("stat file had an unexpected format: %s" % statFilePath)
results, queriedStats = [], []
for statType in statTypes:
if statType == Stat.COMMAND:
queriedStats.append("command")
if pid == 0: results.append("sched")
else: results.append(statComp[1])
elif statType == Stat.CPU_UTIME:
queriedStats.append("utime")
if pid == 0: results.append("0")
else: results.append(str(float(statComp[13]) / CLOCK_TICKS))
elif statType == Stat.CPU_STIME:
queriedStats.append("stime")
if pid == 0: results.append("0")
else: results.append(str(float(statComp[14]) / CLOCK_TICKS))
elif statType == Stat.START_TIME:
queriedStats.append("start time")
if pid == 0: return getSystemStartTime()
else:
# According to documentation, starttime is in field 21 and the unit is
# jiffies (clock ticks). We divide it for clock ticks, then add the
# uptime to get the seconds since the epoch.
pStartTime = float(statComp[21]) / CLOCK_TICKS
results.append(str(pStartTime + getSystemStartTime()))
_logProcRuntime("process %s" % ", ".join(queriedStats), "/proc/%s/stat" % pid, startTime)
return results
def getConnections(pid):
"""
Provides a listing of connection tuples of the form:
[(local_ipAddr1, local_port1, foreign_ipAddr1, foreign_port1), ...]
If the information about a connection can't be queried (often due to
permission issues) then it's excluded from the listing.
Arguments:
pid - ID of the process to be resolved
"""
if pid == "0": return []
# fetches the inode numbers for socket file descriptors
startTime = time.time()
inodes = []
for fd in os.listdir("/proc/%s/fd" % pid):
try:
# File descriptor link, such as 'socket:[30899]'
fdName = os.readlink("/proc/%s/fd/%s" % (pid, fd))
if fdName.startswith('socket:['):
inodes.append(fdName[8:-1])
except OSError:
pass # most likely couldn't be read due to permissions
if not inodes:
# unable to fetch any connections for this process
return []
# check for the connection information from the /proc/net contents
conn = []
for procFilePath in ("/proc/net/tcp", "/proc/net/udp"):
procFile = open(procFilePath)
procFile.readline() # skip the first line
for line in procFile:
_, lAddr, fAddr, status, _, _, _, _, _, inode = line.split()[:10]
if inode in inodes:
# if a tcp connection, skip if it isn't yet established
if procFilePath.endswith("/tcp") and status != "01":
continue
localIp, localPort = _decodeProcAddressEncoding(lAddr)
foreignIp, foreignPort = _decodeProcAddressEncoding(fAddr)
conn.append((localIp, localPort, foreignIp, foreignPort))
procFile.close()
_logProcRuntime("process connections", "/proc/net/[tcp|udp]", startTime)
return conn
def _decodeProcAddressEncoding(addr):
"""
Translates an address entry in the /proc/net/* contents to a human readable
form, for instance:
"0500000A:0016" -> ("10.0.0.5", "22")
Reference:
http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html
Arguments:
addr - proc address entry to be decoded
"""
ip, port = addr.split(':')
# the port is represented as a two-byte hexadecimal number
port = str(int(port, 16))
if sys.version_info >= (3,):
ip = ip.encode('ascii')
# The IPv4 address portion is a little-endian four-byte hexadecimal number.
# That is, the least significant byte is listed first, so we need to reverse
# the order of the bytes to convert it to an IP address.
#
# This needs to account for the endian ordering as per...
# http://code.google.com/p/psutil/issues/detail?id=201
# https://trac.torproject.org/projects/tor/ticket/4777
if sys.byteorder == 'little':
ip = socket.inet_ntop(socket.AF_INET, base64.b16decode(ip)[::-1])
else:
ip = socket.inet_ntop(socket.AF_INET, base64.b16decode(ip))
return (ip, port)
def _logProcRuntime(parameter, procLocation, startTime):
msg = "proc call (%s): %s (runtime: %0.4f)" % (parameter, procLocation, time.time() - startTime)
log.log(CONFIG["log.procCallMade"], msg)
|