Package pysys :: Package process :: Module monitor
[hide private]
[frames] | no frames]

Source Code for Module pysys.process.monitor

  1  #!/usr/bin/env python 
  2  # PySys System Test Framework, Copyright (C) 2006-2016  M.B.Grieve 
  3   
  4  # This library is free software; you can redistribute it and/or 
  5  # modify it under the terms of the GNU Lesser General Public 
  6  # License as published by the Free Software Foundation; either 
  7  # version 2.1 of the License, or (at your option) any later version. 
  8   
  9  # This library is distributed in the hope that it will be useful, 
 10  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 12  # Lesser General Public License for more details. 
 13   
 14  # You should have received a copy of the GNU Lesser General Public 
 15  # License along with this library; if not, write to the Free Software 
 16  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 
 17   
 18  # Contact: moraygrieve@users.sourceforge.net 
 19   
 20  import os, sys, string, time, thread, logging, win32api, win32pdh 
 21   
 22  from pysys import log 
 23  from pysys.constants import * 
 24   
 25   
26 -class ProcessMonitor:
27 """Process monitor for the logging of process statistics. 28 29 The process monitor uses either the win32pdh module (windows systems) or the ps command line utility 30 (unix systems) to obtain and log to file statistics on a given process as determined by the process id. 31 Usage of the class is to create an instance specifying the process id, the logging interval and the log 32 file. Once created, the process monitor is started and stopped via its L{start} and L{stop} methods. 33 Process monitors are started as a separate thread, so control passes back to the caller of the start method 34 immediately. 35 36 On windows systems, statistics obtained include the CPU usage (%), the working set (memory pages allocated), 37 the virtual bytes (virtual address space including shared memory segments), the private bytes (virtual 38 address space not including shared memory segments), the number of process threads and the number of 39 handles. All memory values are quoted in KBytes and the CPU precentage represents the usage over all available 40 processors. A CPU usage of 100% represents a single CPU fully utilized; it is therefore possible to obtain CPU 41 usage figures of over 100% on multi-core processors. The format of the log file is tab separated, with 42 timestamps used to denote the time each measurement was obtained, e.g. :: 43 44 Time CPU Working Virtual Private Threads Handles 45 ------------------------------------------------------------------------ 46 09/16/08 14:20:44 80 125164 212948 118740 44 327 47 09/16/08 14:20:49 86 125676 213972 120128 44 328 48 09/16/08 14:20:54 84 125520 212948 119116 44 328 49 09/16/08 14:20:59 78 125244 212948 119132 44 328 50 51 52 On unix systems, statistics obtained include the CPU usage (%), the resident memory (via the rss format specifier 53 to ps), and the virtual memory (via the vsz format spepcifier to ps). All memory values are quoted in KBytes and 54 the CPU precentage represents the usage over all available processors. A CPU usage of 100% represents a single 55 CPU fully utilized; it is therefore possible to obtain CPU usage figures of over 100% on multi-core processors. 56 The format of the log file is tab separated, with timestamps used to denote the time each measurement was obtained, 57 e.g. :: 58 59 Time CPU Resident Virtual 60 ---------------------------------------------------- 61 09/16/08 14:24:10 69.5 89056 1421672 62 09/16/08 14:24:20 73.1 101688 1436804 63 09/16/08 14:24:30 82.9 102196 1436516 64 09/16/08 14:24:40 89.1 102428 1436372 65 09/16/08 14:24:50 94.2 104404 1438420 66 67 68 Both windows and unix operating systems support the numProcessors argument in the variable argument list in order 69 to normalise the CPU statistics gathered by the number of available CPUs. 70 71 """ 72
73 - def __init__(self, pid, interval, file=None, **kwargs):
74 """Construct an instance of the process monitor. 75 76 @param pid: The process id to monitor 77 @param interval: The interval in seconds to record the process statistics 78 @param file: The full path to the file to log the process statistics 79 @param kwargs: Keyword arguments to allow platform specific configurations 80 81 """ 82 self.pid = pid 83 self.interval = interval 84 if file: 85 self.file = open(file, 'w', 0) 86 else: 87 self.file = sys.stdout 88 89 # normalise the CPU readings by the supplied factor 90 self.numProcessors=1 91 if kwargs.has_key("numProcessors"): 92 self.numProcessors = int(kwargs["numProcessors"])
93 94
95 - def __win32GetInstance(self, pid, bRefresh=0):
96 if bRefresh: win32pdh.EnumObjects(None, None, 0, 1) 97 98 # get the list of processes running 99 items, instances = win32pdh.EnumObjectItems(None, None, "Process", -1) 100 101 # convert to a dictionary of process instance, to number of instances 102 instanceDict = {} 103 for i in instances: 104 try: instanceDict[i] = instanceDict[i] + 1 105 except KeyError: instanceDict[i] = 0 106 107 # loop through to locate the instance and inum of the supplied pid 108 instance = None 109 inum = -1 110 for instance, numInstances in instanceDict.items(): 111 for inum in xrange(numInstances+1): 112 try: 113 value = self.__win32getProfileAttribute("Process", instance, inum, "ID Process") 114 if value == pid: 115 return instance, inum 116 except: 117 pass 118 119 return instance, inum
120 121
122 - def __win32GetThreads(self, pid, bRefresh=0):
123 if bRefresh: win32pdh.EnumObjects(None, None, 0, 1) 124 125 # get the list of threads running 126 items, instances = win32pdh.EnumObjectItems(None, None, "Thread", -1) 127 128 # convert to a dictionary of thread instance, to number of instances 129 instanceNum = [] 130 instanceDict = {} 131 for i in instances: 132 try: instanceDict[i] = instanceDict[i] + 1 133 except KeyError: instanceDict[i] = 0 134 instanceNum.append(instanceDict[i]) 135 136 # loop through to locate the instance and inum of each thread for the supplied process id 137 threads=[] 138 for i in range(0, len(instances)): 139 try: 140 value = self.__win32getProfileAttribute("Thread", instances[i], instanceNum[i], "ID Process") 141 if value == pid: threads.append((instances[i], instanceNum[i])) 142 except: 143 pass 144 return threads
145 146
147 - def __win32getProfileAttribute(self, object, instance, inum, counter):
148 # make the path, open and collect the query 149 path = win32pdh.MakeCounterPath((None, object, instance, None, inum, counter)) 150 query = win32pdh.OpenQuery() 151 hcounter = win32pdh.AddCounter(query, path) 152 win32pdh.CollectQueryData(query) 153 154 # format the counter value 155 value = None 156 try: 157 value = win32pdh.GetFormattedCounterValue(hcounter, win32pdh.PDH_FMT_LONG)[1] 158 except: 159 pass 160 161 # tidy up and return the value 162 win32pdh.RemoveCounter(hcounter) 163 win32pdh.CloseQuery(query) 164 return value
165 166
167 - def __win32LogProfile(self, instance, inum, threads, interval, file):
168 169 # create the process performance counters 170 process_counters=[] 171 process_query=win32pdh.OpenQuery() 172 for counter in "Working Set", "Virtual Bytes", "Private Bytes", "Thread Count", "Handle Count": 173 path = win32pdh.MakeCounterPath( (None, "Process", instance, None, inum, counter) ) 174 process_counters.append(win32pdh.AddCounter(process_query, path)) 175 win32pdh.CollectQueryData(process_query) 176 177 # create the thread performance counter 178 thread_counters=[] 179 thread_query=win32pdh.OpenQuery() 180 for (instance, inum) in threads: 181 path=win32pdh.MakeCounterPath( (None, "Thread", instance, None, inum, "% Processor Time") ) 182 thread_counters.append(win32pdh.AddCounter(thread_query, path)) 183 win32pdh.CollectQueryData(thread_query) 184 185 # perform the continual data collection until the thread is no longer active 186 data = [0]*(len(process_counters)+1) 187 while self.active: 188 win32pdh.CollectQueryData(process_query) 189 win32pdh.CollectQueryData(thread_query) 190 191 for i in range(len(process_counters)): 192 try: 193 data[i+1] = win32pdh.GetFormattedCounterValue(process_counters[i], win32pdh.PDH_FMT_LONG)[1] 194 except win32api.error: 195 data[i+1] = -1 196 197 data[0]=0 198 for i in range(0, len(thread_counters)): 199 try: 200 data[0]=data[0]+win32pdh.GetFormattedCounterValue(thread_counters[i], win32pdh.PDH_FMT_LONG)[1] 201 except: 202 pass 203 204 currentTime = time.strftime("%d/%m/%y %H:%M:%S", time.gmtime(time.time())) 205 file.write( "%s\t%s\t%d\t%d\t%d\t%d\t%d\n" % (currentTime, data[0]/self.numProcessors, float(data[1])/1024, 206 float(data[2])/1024, float(data[3])/1024, float(data[4]), float(data[5]))) 207 time.sleep(interval) 208 209 # clean up 210 for c in process_counters: 211 win32pdh.RemoveCounter(c) 212 win32pdh.CloseQuery(process_query) 213 for c in thread_counters: 214 win32pdh.RemoveCounter(c) 215 win32pdh.CloseQuery(thread_query) 216 if file != sys.stdout: file.close()
217
218 - def running(self):
219 """Return the running status of the process monitor. 220 221 @return: The running status (True | False) 222 @rtype: integer 223 """ 224 return self.active
225 226
227 - def start(self):
228 """Start the process monitor. 229 230 """ 231 self.active = 1 232 233 # get the instance and instance number for this process id 234 instance, inum = self.__win32GetInstance(pid=self.pid, bRefresh=1) 235 236 # get the instance and instance number for each thread of this process id 237 threads = self.__win32GetThreads(pid=self.pid, bRefresh=1) 238 239 # log the stats in a separate thread 240 thread.start_new_thread(self.__win32LogProfile, (instance, inum, threads, self.interval, self.file))
241 242
243 - def stop(self):
244 """Stop the process monitor. 245 246 """ 247 self.active = 0
248 249 250 if __name__ == "__main__": 251 items, instances = win32pdh.EnumObjectItems(None, None, "System", -1) 252 print "System - available counters are; " 253 for item in items: print item 254 print 255 256 items, instances = win32pdh.EnumObjectItems(None, None, "Processor", -1) 257 print "Processor - available counters are; " 258 for item in items: print item 259 print 260 261 items, instances = win32pdh.EnumObjectItems(None, None, "Process", -1) 262 print "Process - available counters are; " 263 for item in items: print item 264 print 265 266 items, instances = win32pdh.EnumObjectItems(None, None, "Thread", -1) 267 print "Thread - available counters are; " 268 for item in items: print item 269 print 270