PyXR

c:\python24\lib\site-packages\win32\lib \ win32pdhquery.py



0001 '''
0002 Performance Data Helper (PDH) Query Classes
0003 
0004 Wrapper classes for end-users and high-level access to the PDH query
0005 mechanisms.  PDH is a win32-specific mechanism for accessing the
0006 performance data made available by the system.  The Python for Windows
0007 PDH module does not implement the "Registry" interface, implementing
0008 the more straightforward Query-based mechanism.
0009 
0010 The basic idea of a PDH Query is an object which can query the system
0011 about the status of any number of "counters."  The counters are paths
0012 to a particular piece of performance data.  For instance, the path 
0013 '\\Memory\\Available Bytes' describes just about exactly what it says
0014 it does, the amount of free memory on the default computer expressed 
0015 in Bytes.  These paths can be considerably more complex than this, 
0016 but part of the point of this wrapper module is to hide that
0017 complexity from the end-user/programmer.
0018 
0019 EXAMPLE: A more complex Path
0020         '\\\\RAISTLIN\\PhysicalDisk(_Total)\\Avg. Disk Bytes/Read'
0021         Raistlin --> Computer Name
0022         PhysicalDisk --> Object Name
0023         _Total --> The particular Instance (in this case, all instances, i.e. all drives)
0024         Avg. Disk Bytes/Read --> The piece of data being monitored.
0025 
0026 EXAMPLE: Collecting Data with a Query
0027         As an example, the following code implements a logger which allows the
0028         user to choose what counters they would like to log, and logs those
0029         counters for 30 seconds, at two-second intervals.
0030         
0031         query = Query()
0032         query.addcounterbybrowsing()
0033         query.collectdatafor(30,2)
0034         
0035         The data is now stored in a list of lists as:
0036         query.curresults
0037         
0038         The counters(paths) which were used to collect the data are:
0039         query.curpaths
0040         
0041         You can use the win32pdh.ParseCounterPath(path) utility function
0042         to turn the paths into more easily read values for your task, or
0043         write the data to a file, or do whatever you want with it.
0044 
0045 OTHER NOTABLE METHODS:
0046         query.collectdatawhile(period) # start a logging thread for collecting data
0047         query.collectdatawhile_stop() # signal the logging thread to stop logging
0048         query.collectdata() # run the query only once
0049         query.addperfcounter(object, counter, machine=None) # add a standard performance counter
0050         query.addinstcounter(object, counter,machine=None,objtype = 'Process',volatile=1,format = win32pdh.PDH_FMT_LONG) # add a possibly volatile counter
0051 
0052 ### Known bugs and limitations ###
0053 Due to a problem with threading under the PythonWin interpreter, there
0054 will be no data logged if the PythonWin window is not the foreground
0055 application.  Workaround: scripts using threading should be run in the
0056 python.exe interpreter.
0057 
0058 The volatile-counter handlers are possibly buggy, they haven't been
0059 tested to any extent.  The wrapper Query makes it safe to pass invalid
0060 paths (a -1 will be returned, or the Query will be totally ignored,
0061 depending on the missing element), so you should be able to work around
0062 the error by including all possible paths and filtering out the -1's.
0063 
0064 There is no way I know of to stop a thread which is currently sleeping,
0065 so you have to wait until the thread in collectdatawhile is activated
0066 again.  This might become a problem in situations where the collection
0067 period is multiple minutes (or hours, or whatever).
0068 
0069 Should make the win32pdh.ParseCounter function available to the Query
0070 classes as a method or something similar, so that it can be accessed
0071 by programmes that have just picked up an instance from somewhere.
0072 
0073 Should explicitly mention where QueryErrors can be raised, and create a
0074 full test set to see if there are any uncaught win32api.error's still
0075 hanging around.
0076 
0077 When using the python.exe interpreter, the addcounterbybrowsing-
0078 generated browser window is often hidden behind other windows.  No known
0079 workaround other than Alt-tabing to reach the browser window.
0080 
0081 ### Other References ###
0082 The win32pdhutil module (which should be in the %pythonroot%/win32/lib 
0083 directory) provides quick-and-dirty utilities for one-off access to
0084 variables from the PDH.  Almost everything in that module can be done
0085 with a Query object, but it provides task-oriented functions for a
0086 number of common one-off tasks.
0087 
0088 If you can access the MS Developers Network Library, you can find
0089 information about the PDH API as MS describes it.  For a background article,
0090 try:
0091 http://msdn.microsoft.com/library/en-us/dnperfmo/html/msdn_pdhlib.asp
0092 
0093 The reference guide for the PDH API was last spotted at:
0094 http://msdn.microsoft.com/library/en-us/perfmon/base/using_the_pdh_interface.asp
0095 
0096 
0097 In general the Python version of the API is just a wrapper around the
0098 Query-based version of this API (as far as I can see), so you can learn what
0099 you need to from there.  From what I understand, the MSDN Online 
0100 resources are available for the price of signing up for them.  I can't
0101 guarantee how long that's supposed to last. (Or anything for that
0102 matter).
0103 http://premium.microsoft.com/isapi/devonly/prodinfo/msdnprod/msdnlib.idc?theURL=/msdn/library/sdkdoc/perfdata_4982.htm
0104 
0105 The eventual plan is for my (Mike Fletcher's) Starship account to include
0106 a section on NT Administration, and the Query is the first project
0107 in this plan.  There should be an article describing the creation of
0108 a simple logger there, but the example above is 90% of the work of
0109 that project, so don't sweat it if you don't find anything there.
0110 (currently the account hasn't been set up).
0111 http://starship.skyport.net/crew/mcfletch/
0112 
0113 If you need to contact me immediately, (why I can't imagine), you can
0114 email me at mcfletch@golden.net, or just post your question to the
0115 Python newsgroup with a catchy subject line.
0116 news:comp.lang.python
0117 
0118 ### Other Stuff ###
0119 The Query classes are by Mike Fletcher, with the working code
0120 being corruptions of Mark Hammonds win32pdhutil module.
0121 
0122 Use at your own risk, no warranties, no guarantees, no assurances,
0123 if you use it, you accept the risk of using it, etceteras.
0124 
0125 '''
0126 # Feb 12, 98 - MH added "rawaddcounter" so caller can get exception details.
0127 
0128 import win32pdh, win32api,time, thread,copy
0129 
0130 class BaseQuery:
0131         '''
0132         Provides wrapped access to the Performance Data Helper query
0133         objects, generally you should use the child class Query
0134         unless you have need of doing weird things :)
0135 
0136         This class supports two major working paradigms.  In the first,
0137         you open the query, and run it as many times as you need, closing
0138         the query when you're done with it.  This is suitable for static
0139         queries (ones where processes being monitored don't disappear).
0140 
0141         In the second, you allow the query to be opened each time and
0142         closed afterward.  This causes the base query object to be
0143         destroyed after each call.  Suitable for dynamic queries (ones
0144         which watch processes which might be closed while watching.)
0145         '''
0146         def __init__(self,paths=None):
0147                 '''
0148                 The PDH Query object is initialised with a single, optional
0149                 list argument, that must be properly formatted PDH Counter
0150                 paths.  Generally this list will only be provided by the class
0151                 when it is being unpickled (removed from storage).  Normal
0152                 use is to call the class with no arguments and use the various
0153                 addcounter functions (particularly, for end user's, the use of
0154                 addcounterbybrowsing is the most common approach)  You might
0155                 want to provide the list directly if you want to hard-code the
0156                 elements with which your query deals (and thereby avoid the
0157                 overhead of unpickling the class).
0158                 '''
0159                 self.counters = []
0160                 if paths:
0161                         self.paths = paths
0162                 else:
0163                         self.paths = []
0164                 self._base = None
0165                 self.active = 0
0166                 self.curpaths = []
0167         def addcounterbybrowsing(self, flags = win32pdh.PERF_DETAIL_WIZARD, windowtitle="Python Browser"):
0168                 '''
0169                 Adds possibly multiple paths to the paths attribute of the query,
0170                 does this by calling the standard counter browsing dialogue.  Within
0171                 this dialogue, find the counter you want to log, and click: Add,
0172                 repeat for every path you want to log, then click on close.  The
0173                 paths are appended to the non-volatile paths list for this class,
0174                 subclasses may create a function which parses the paths and decides
0175                 (via heuristics) whether to add the path to the volatile or non-volatile
0176                 path list.
0177                 e.g.:
0178                         query.addcounter()
0179                 '''
0180                 win32pdh.BrowseCounters(None,0, self.paths.append, flags, windowtitle)
0181         def rawaddcounter(self,object, counter, instance = None, inum=-1, machine=None):
0182                 '''
0183                 Adds a single counter path, without catching any exceptions.
0184                 
0185                 See addcounter for details.
0186                 '''
0187                 path = win32pdh.MakeCounterPath( (machine,object,instance, None, inum,counter) )
0188                 self.paths.append(path)
0189         
0190         def addcounter(self,object, counter, instance = None, inum=-1, machine=None):
0191                 '''
0192                 Adds a single counter path to the paths attribute.  Normally
0193                 this will be called by a child class' speciality functions,
0194                 rather than being called directly by the user. (Though it isn't
0195                 hard to call manually, since almost everything is given a default)
0196                 This method is only functional when the query is closed (or hasn't
0197                 yet been opened).  This is to prevent conflict in multi-threaded
0198                 query applications).
0199                 e.g.:
0200                         query.addcounter('Memory','Available Bytes')
0201                 '''
0202                 if not self.active:
0203                         try:
0204                                 self.rawaddcounter(object, counter, instance, inum, machine)
0205                                 return 0
0206                         except win32api.error:
0207                                 return -1
0208                 else:
0209                         return -1
0210                 
0211         def open(self):
0212                 '''
0213                 Build the base query object for this wrapper,
0214                 then add all of the counters required for the query.
0215                 Raise a QueryError if we can't complete the functions.
0216                 If we are already open, then do nothing.
0217                 '''
0218                 if not self.active: # to prevent having multiple open queries
0219                         # curpaths are made accessible here because of the possibility of volatile paths
0220                         # which may be dynamically altered by subclasses.
0221                         self.curpaths = copy.copy(self.paths)
0222                         try:
0223                                 base = win32pdh.OpenQuery()
0224                                 for path in self.paths:
0225                                         try:
0226                                                 self.counters.append(win32pdh.AddCounter(base, path))
0227                                         except win32api.error: # we passed a bad path
0228                                                 self.counters.append(0)
0229                                                 pass
0230                                 self._base = base
0231                                 self.active = 1
0232                                 return 0 # open succeeded
0233                         except: # if we encounter any errors, kill the Query
0234                                 try:
0235                                         self.killbase(base)
0236                                 except NameError: # failed in creating query
0237                                         pass
0238                                 self.active = 0
0239                                 self.curpaths = []
0240                                 raise QueryError(self)
0241                 return 1 # already open
0242                 
0243         def killbase(self,base=None):
0244                 '''
0245                 ### This is not a public method
0246                 Mission critical function to kill the win32pdh objects held
0247                 by this object.  User's should generally use the close method
0248                 instead of this method, in case a sub-class has overridden
0249                 close to provide some special functionality.
0250                 '''
0251                 # Kill Pythonic references to the objects in this object's namespace
0252                 self._base = None
0253                 counters = self.counters
0254                 self.counters = []
0255                 # we don't kill the curpaths for convenience, this allows the
0256                 # user to close a query and still access the last paths
0257                 self.active = 0
0258                 # Now call the delete functions on all of the objects
0259                 try:
0260                         map(win32pdh.RemoveCounter,counters)
0261                 except:
0262                         pass
0263                 try:
0264                         win32pdh.CloseQuery(base)
0265                 except:
0266                         pass
0267                 del(counters)
0268                 del(base)
0269         def close(self):
0270                 '''
0271                 Makes certain that the underlying query object has been closed,
0272                 and that all counters have been removed from it.  This is
0273                 important for reference counting.
0274                 You should only need to call close if you have previously called
0275                 open.  The collectdata methods all can handle opening and
0276                 closing the query.  Calling close multiple times is acceptable.
0277                 '''
0278                 try:
0279                         self.killbase(self._base)
0280                 except AttributeError:
0281                         self.killbase()
0282         __del__ = close
0283         def collectdata(self,format = win32pdh.PDH_FMT_LONG):
0284                 '''
0285                 Returns the formatted current values for the Query
0286                 '''
0287                 if self._base: # we are currently open, don't change this
0288                         return self.collectdataslave(format)
0289                 else: # need to open and then close the _base, should be used by one-offs and elements tracking application instances
0290                         self.open() # will raise QueryError if couldn't open the query
0291                         temp = self.collectdataslave(format)
0292                         self.close() # will always close
0293                         return temp
0294         def collectdataslave(self,format = win32pdh.PDH_FMT_LONG):
0295                 '''
0296                 ### Not a public method
0297                 Called only when the Query is known to be open, runs over
0298                 the whole set of counters, appending results to the temp,
0299                 returns the values as a list.
0300                 '''
0301                 try:
0302                         win32pdh.CollectQueryData(self._base)
0303                         temp = []
0304                         for counter in self.counters:
0305                                 ok = 0
0306                                 try:
0307                                         if counter:
0308                                                 temp.append(win32pdh.GetFormattedCounterValue(counter, format)[1])
0309                                                 ok = 1
0310                                 except win32api.error:
0311                                         pass
0312                                 if not ok:
0313                                         temp.append(-1) # a better way to signal failure???
0314                         return temp
0315                 except win32api.error: # will happen if, for instance, no counters are part of the query and we attempt to collect data for it.
0316                         return [-1] * len(self.counters)
0317         # pickle functions
0318         def __getinitargs__(self):
0319                 '''
0320                 ### Not a public method
0321                 '''
0322                 return (self.paths,)
0323                 
0324 class Query(BaseQuery):
0325         '''
0326         Performance Data Helper(PDH) Query object:
0327         
0328         Provides a wrapper around the native PDH query object which
0329         allows for query reuse, query storage, and general maintenance
0330         functions (adding counter paths in various ways being the most
0331         obvious ones).
0332         '''
0333         def __init__(self,*args,**namedargs):
0334                 '''
0335                 The PDH Query object is initialised with a single, optional
0336                 list argument, that must be properly formatted PDH Counter
0337                 paths.  Generally this list will only be provided by the class
0338                 when it is being unpickled (removed from storage).  Normal
0339                 use is to call the class with no arguments and use the various
0340                 addcounter functions (particularly, for end user's, the use of
0341                 addcounterbybrowsing is the most common approach)  You might
0342                 want to provide the list directly if you want to hard-code the
0343                 elements with which your query deals (and thereby avoid the
0344                 overhead of unpickling the class).
0345                 '''
0346                 self.volatilecounters = []
0347                 apply(BaseQuery.__init__, (self,)+args, namedargs)
0348         def addperfcounter(self, object, counter, machine=None):
0349                 '''
0350                 A "Performance Counter" is a stable, known, common counter,
0351                 such as Memory, or Processor.  The use of addperfcounter by 
0352                 end-users is deprecated, since the use of 
0353                 addcounterbybrowsing is considerably more flexible and general.
0354                 It is provided here to allow the easy development of scripts
0355                 which need to access variables so common we know them by name
0356                 (such as Memory|Available Bytes), and to provide symmetry with
0357                 the add inst counter method.
0358                 usage:
0359                         query.addperfcounter('Memory', 'Available Bytes')
0360                 It is just as easy to access addcounter directly, the following
0361                 has an identicle effect.
0362                         query.addcounter('Memory', 'Available Bytes')
0363                 '''
0364                 BaseQuery.addcounter(self, object=object, counter=counter, machine=machine)
0365         def addinstcounter(self, object, counter,machine=None,objtype = 'Process',volatile=1,format = win32pdh.PDH_FMT_LONG):
0366                 '''
0367                 The purpose of using an instcounter is to track particular
0368                 instances of a counter object (e.g. a single processor, a single
0369                 running copy of a process).  For instance, to track all python.exe
0370                 instances, you would need merely to ask:
0371                         query.addinstcounter('python','Virtual Bytes')
0372                 You can find the names of the objects and their available counters 
0373                 by doing an addcounterbybrowsing() call on a query object (or by
0374                 looking in performance monitor's add dialog.)
0375                 
0376                 Beyond merely rearranging the call arguments to make more sense,
0377                 if the volatile flag is true, the instcounters also recalculate
0378                 the paths of the available instances on every call to open the
0379                 query.
0380                 '''
0381                 if volatile:
0382                         self.volatilecounters.append((object,counter,machine,objtype,format))
0383                 else:
0384                         self.paths[len(self.paths):] = self.getinstpaths(object,counter,machine,objtype,format)
0385                                 
0386         def getinstpaths(self,object,counter,machine=None,objtype='Process',format = win32pdh.PDH_FMT_LONG):
0387                 '''
0388                 ### Not an end-user function
0389                 Calculate the paths for an instance object. Should alter
0390                 to allow processing for lists of object-counter pairs.
0391                 '''
0392                 items, instances = win32pdh.EnumObjectItems(None,None,objtype, -1)
0393                 # find out how many instances of this element we have...
0394                 instances.sort()
0395                 try:
0396                         cur = instances.index(object)
0397                 except ValueError:
0398                         return [] # no instances of this object
0399                 temp = [object]
0400                 try:
0401                         while instances[cur+1] == object:
0402                                 temp.append(object)
0403                                 cur = cur+1
0404                 except IndexError: # if we went over the end
0405                         pass
0406                 paths = []
0407                 for ind in range(len(temp)):
0408                         # can this raise an error?
0409                         paths.append(win32pdh.MakeCounterPath( (machine,'Process',object,None,ind,counter) ) )
0410                 return paths # should also return the number of elements for naming purposes
0411 
0412         def open(self,*args,**namedargs):
0413                 '''
0414                 Explicitly open a query:
0415                 When you are needing to make multiple calls to the same query,
0416                 it is most efficient to open the query, run all of the calls,
0417                 then close the query, instead of having the collectdata method
0418                 automatically open and close the query each time it runs.
0419                 There are currently no arguments to open.
0420                 '''
0421                 # do all the normal opening stuff, self._base is now the query object
0422                 apply(BaseQuery.open,(self,)+args, namedargs)
0423                 # should rewrite getinstpaths to take a single tuple
0424                 paths = []
0425                 for tup in self.volatilecounters:
0426                         paths[len(paths):] = apply(self.getinstpaths, tup)
0427                 for path in paths:
0428                         try:
0429                                 self.counters.append(win32pdh.AddCounter(self._base, path))
0430                                 self.curpaths.append(path) # if we fail on the line above, this path won't be in the table or the counters
0431                         except win32api.error:
0432                                 pass # again, what to do with a malformed path???
0433         def collectdatafor(self, totalperiod, period=1):
0434                 '''
0435                 Non-threaded collection of performance data:
0436                 This method allows you to specify the total period for which you would
0437                 like to run the Query, and the time interval between individual
0438                 runs.  The collected data is stored in query.curresults at the
0439                 _end_ of the run.  The pathnames for the query are stored in
0440                 query.curpaths.
0441                 e.g.:
0442                         query.collectdatafor(30,2)
0443                 Will collect data for 30seconds at 2 second intervals
0444                 '''
0445                 tempresults = []
0446                 try:
0447                         self.open()
0448                         for ind in xrange(totalperiod/period):
0449                                 tempresults.append(self.collectdata())
0450                                 time.sleep(period)
0451                         self.curresults = tempresults
0452                 finally:
0453                         self.close()
0454         def collectdatawhile(self, period=1):
0455                 '''
0456                 Threaded collection of performance data:
0457                 This method sets up a simple semaphor system for signalling 
0458                 when you would like to start and stop a threaded data collection
0459                 method.  The collection runs every period seconds until the
0460                 semaphor attribute is set to a non-true value (which normally
0461                 should be done by calling query.collectdatawhile_stop() .)
0462                 e.g.:
0463                         query.collectdatawhile(2)
0464                         # starts the query running, returns control to the caller immediately
0465                         # is collecting data every two seconds.
0466                         # do whatever you want to do while the thread runs, then call:
0467                         query.collectdatawhile_stop()
0468                         # when you want to deal with the data.  It is generally a good idea
0469                         # to sleep for period seconds yourself, since the query will not copy
0470                         # the required data until the next iteration:
0471                         time.sleep(2)
0472                         # now you can access the data from the attributes of the query
0473                         query.curresults
0474                         query.curpaths
0475                 '''
0476                 self.collectdatawhile_active = 1
0477                 thread.start_new_thread(self.collectdatawhile_slave,(period,))
0478         def collectdatawhile_stop(self):
0479                 '''
0480                 Signals the collectdatawhile slave thread to stop collecting data
0481                 on the next logging iteration.
0482                 '''
0483                 self.collectdatawhile_active = 0
0484         def collectdatawhile_slave(self, period):
0485                 '''
0486                 ### Not a public function
0487                 Does the threaded work of collecting the data and storing it
0488                 in an attribute of the class.
0489                 '''
0490                 tempresults = []
0491                 try:
0492                         self.open() # also sets active, so can't be changed.
0493                         while self.collectdatawhile_active:
0494                                 tempresults.append(self.collectdata())
0495                                 time.sleep(period)
0496                         self.curresults = tempresults
0497                 finally:
0498                         self.close()
0499                 
0500         # pickle functions
0501         def __getinitargs__(self):
0502                 return (self.paths,)
0503         def __getstate__(self):
0504                 return self.volatilecounters
0505         def __setstate__(self, volatilecounters):
0506                 self.volatilecounters = volatilecounters
0507 
0508 
0509 class QueryError:
0510         def __init__(self, query):
0511                 self.query = query
0512         def __repr__(self):
0513                 return '<Query Error in %s>'%repr(self.query)
0514         __str__ = __repr__
0515         
0516 

Generated by PyXR 0.9.4
SourceForge.net Logo