This repository has been archived by the owner on Mar 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
win32pdhquery.py
515 lines (473 loc) · 19.8 KB
/
win32pdhquery.py
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
'''
Performance Data Helper (PDH) Query Classes
Wrapper classes for end-users and high-level access to the PDH query
mechanisms. PDH is a win32-specific mechanism for accessing the
performance data made available by the system. The Python for Windows
PDH module does not implement the "Registry" interface, implementing
the more straightforward Query-based mechanism.
The basic idea of a PDH Query is an object which can query the system
about the status of any number of "counters." The counters are paths
to a particular piece of performance data. For instance, the path
'\\Memory\\Available Bytes' describes just about exactly what it says
it does, the amount of free memory on the default computer expressed
in Bytes. These paths can be considerably more complex than this,
but part of the point of this wrapper module is to hide that
complexity from the end-user/programmer.
EXAMPLE: A more complex Path
'\\\\RAISTLIN\\PhysicalDisk(_Total)\\Avg. Disk Bytes/Read'
Raistlin --> Computer Name
PhysicalDisk --> Object Name
_Total --> The particular Instance (in this case, all instances, i.e. all drives)
Avg. Disk Bytes/Read --> The piece of data being monitored.
EXAMPLE: Collecting Data with a Query
As an example, the following code implements a logger which allows the
user to choose what counters they would like to log, and logs those
counters for 30 seconds, at two-second intervals.
query = Query()
query.addcounterbybrowsing()
query.collectdatafor(30,2)
The data is now stored in a list of lists as:
query.curresults
The counters(paths) which were used to collect the data are:
query.curpaths
You can use the win32pdh.ParseCounterPath(path) utility function
to turn the paths into more easily read values for your task, or
write the data to a file, or do whatever you want with it.
OTHER NOTABLE METHODS:
query.collectdatawhile(period) # start a logging thread for collecting data
query.collectdatawhile_stop() # signal the logging thread to stop logging
query.collectdata() # run the query only once
query.addperfcounter(object, counter, machine=None) # add a standard performance counter
query.addinstcounter(object, counter,machine=None,objtype = 'Process',volatile=1,format = win32pdh.PDH_FMT_LONG) # add a possibly volatile counter
### Known bugs and limitations ###
Due to a problem with threading under the PythonWin interpreter, there
will be no data logged if the PythonWin window is not the foreground
application. Workaround: scripts using threading should be run in the
python.exe interpreter.
The volatile-counter handlers are possibly buggy, they haven't been
tested to any extent. The wrapper Query makes it safe to pass invalid
paths (a -1 will be returned, or the Query will be totally ignored,
depending on the missing element), so you should be able to work around
the error by including all possible paths and filtering out the -1's.
There is no way I know of to stop a thread which is currently sleeping,
so you have to wait until the thread in collectdatawhile is activated
again. This might become a problem in situations where the collection
period is multiple minutes (or hours, or whatever).
Should make the win32pdh.ParseCounter function available to the Query
classes as a method or something similar, so that it can be accessed
by programmes that have just picked up an instance from somewhere.
Should explicitly mention where QueryErrors can be raised, and create a
full test set to see if there are any uncaught win32api.error's still
hanging around.
When using the python.exe interpreter, the addcounterbybrowsing-
generated browser window is often hidden behind other windows. No known
workaround other than Alt-tabing to reach the browser window.
### Other References ###
The win32pdhutil module (which should be in the %pythonroot%/win32/lib
directory) provides quick-and-dirty utilities for one-off access to
variables from the PDH. Almost everything in that module can be done
with a Query object, but it provides task-oriented functions for a
number of common one-off tasks.
If you can access the MS Developers Network Library, you can find
information about the PDH API as MS describes it. For a background article,
try:
http://msdn.microsoft.com/library/en-us/dnperfmo/html/msdn_pdhlib.asp
The reference guide for the PDH API was last spotted at:
http://msdn.microsoft.com/library/en-us/perfmon/base/using_the_pdh_interface.asp
In general the Python version of the API is just a wrapper around the
Query-based version of this API (as far as I can see), so you can learn what
you need to from there. From what I understand, the MSDN Online
resources are available for the price of signing up for them. I can't
guarantee how long that's supposed to last. (Or anything for that
matter).
http://premium.microsoft.com/isapi/devonly/prodinfo/msdnprod/msdnlib.idc?theURL=/msdn/library/sdkdoc/perfdata_4982.htm
The eventual plan is for my (Mike Fletcher's) Starship account to include
a section on NT Administration, and the Query is the first project
in this plan. There should be an article describing the creation of
a simple logger there, but the example above is 90% of the work of
that project, so don't sweat it if you don't find anything there.
(currently the account hasn't been set up).
http://starship.skyport.net/crew/mcfletch/
If you need to contact me immediately, (why I can't imagine), you can
email me at [email protected], or just post your question to the
Python newsgroup with a catchy subject line.
news:comp.lang.python
### Other Stuff ###
The Query classes are by Mike Fletcher, with the working code
being corruptions of Mark Hammonds win32pdhutil module.
Use at your own risk, no warranties, no guarantees, no assurances,
if you use it, you accept the risk of using it, etceteras.
'''
# Feb 12, 98 - MH added "rawaddcounter" so caller can get exception details.
import win32pdh, win32api,time, _thread,copy
class BaseQuery:
'''
Provides wrapped access to the Performance Data Helper query
objects, generally you should use the child class Query
unless you have need of doing weird things :)
This class supports two major working paradigms. In the first,
you open the query, and run it as many times as you need, closing
the query when you're done with it. This is suitable for static
queries (ones where processes being monitored don't disappear).
In the second, you allow the query to be opened each time and
closed afterward. This causes the base query object to be
destroyed after each call. Suitable for dynamic queries (ones
which watch processes which might be closed while watching.)
'''
def __init__(self,paths=None):
'''
The PDH Query object is initialised with a single, optional
list argument, that must be properly formatted PDH Counter
paths. Generally this list will only be provided by the class
when it is being unpickled (removed from storage). Normal
use is to call the class with no arguments and use the various
addcounter functions (particularly, for end user's, the use of
addcounterbybrowsing is the most common approach) You might
want to provide the list directly if you want to hard-code the
elements with which your query deals (and thereby avoid the
overhead of unpickling the class).
'''
self.counters = []
if paths:
self.paths = paths
else:
self.paths = []
self._base = None
self.active = 0
self.curpaths = []
def addcounterbybrowsing(self, flags = win32pdh.PERF_DETAIL_WIZARD, windowtitle="Python Browser"):
'''
Adds possibly multiple paths to the paths attribute of the query,
does this by calling the standard counter browsing dialogue. Within
this dialogue, find the counter you want to log, and click: Add,
repeat for every path you want to log, then click on close. The
paths are appended to the non-volatile paths list for this class,
subclasses may create a function which parses the paths and decides
(via heuristics) whether to add the path to the volatile or non-volatile
path list.
e.g.:
query.addcounter()
'''
win32pdh.BrowseCounters(None,0, self.paths.append, flags, windowtitle)
def rawaddcounter(self,object, counter, instance = None, inum=-1, machine=None):
'''
Adds a single counter path, without catching any exceptions.
See addcounter for details.
'''
path = win32pdh.MakeCounterPath( (machine,object,instance, None, inum,counter) )
self.paths.append(path)
def addcounter(self,object, counter, instance = None, inum=-1, machine=None):
'''
Adds a single counter path to the paths attribute. Normally
this will be called by a child class' speciality functions,
rather than being called directly by the user. (Though it isn't
hard to call manually, since almost everything is given a default)
This method is only functional when the query is closed (or hasn't
yet been opened). This is to prevent conflict in multi-threaded
query applications).
e.g.:
query.addcounter('Memory','Available Bytes')
'''
if not self.active:
try:
self.rawaddcounter(object, counter, instance, inum, machine)
return 0
except win32api.error:
return -1
else:
return -1
def open(self):
'''
Build the base query object for this wrapper,
then add all of the counters required for the query.
Raise a QueryError if we can't complete the functions.
If we are already open, then do nothing.
'''
if not self.active: # to prevent having multiple open queries
# curpaths are made accessible here because of the possibility of volatile paths
# which may be dynamically altered by subclasses.
self.curpaths = copy.copy(self.paths)
try:
base = win32pdh.OpenQuery()
for path in self.paths:
try:
self.counters.append(win32pdh.AddCounter(base, path))
except win32api.error: # we passed a bad path
self.counters.append(0)
pass
self._base = base
self.active = 1
return 0 # open succeeded
except: # if we encounter any errors, kill the Query
try:
self.killbase(base)
except NameError: # failed in creating query
pass
self.active = 0
self.curpaths = []
raise QueryError(self)
return 1 # already open
def killbase(self,base=None):
'''
### This is not a public method
Mission critical function to kill the win32pdh objects held
by this object. User's should generally use the close method
instead of this method, in case a sub-class has overridden
close to provide some special functionality.
'''
# Kill Pythonic references to the objects in this object's namespace
self._base = None
counters = self.counters
self.counters = []
# we don't kill the curpaths for convenience, this allows the
# user to close a query and still access the last paths
self.active = 0
# Now call the delete functions on all of the objects
try:
map(win32pdh.RemoveCounter,counters)
except:
pass
try:
win32pdh.CloseQuery(base)
except:
pass
del(counters)
del(base)
def close(self):
'''
Makes certain that the underlying query object has been closed,
and that all counters have been removed from it. This is
important for reference counting.
You should only need to call close if you have previously called
open. The collectdata methods all can handle opening and
closing the query. Calling close multiple times is acceptable.
'''
try:
self.killbase(self._base)
except AttributeError:
self.killbase()
__del__ = close
def collectdata(self,format = win32pdh.PDH_FMT_LONG):
'''
Returns the formatted current values for the Query
'''
if self._base: # we are currently open, don't change this
return self.collectdataslave(format)
else: # need to open and then close the _base, should be used by one-offs and elements tracking application instances
self.open() # will raise QueryError if couldn't open the query
temp = self.collectdataslave(format)
self.close() # will always close
return temp
def collectdataslave(self,format = win32pdh.PDH_FMT_LONG):
'''
### Not a public method
Called only when the Query is known to be open, runs over
the whole set of counters, appending results to the temp,
returns the values as a list.
'''
try:
win32pdh.CollectQueryData(self._base)
temp = []
for counter in self.counters:
ok = 0
try:
if counter:
temp.append(win32pdh.GetFormattedCounterValue(counter, format)[1])
ok = 1
except win32api.error:
pass
if not ok:
temp.append(-1) # a better way to signal failure???
return temp
except win32api.error: # will happen if, for instance, no counters are part of the query and we attempt to collect data for it.
return [-1] * len(self.counters)
# pickle functions
def __getinitargs__(self):
'''
### Not a public method
'''
return (self.paths,)
class Query(BaseQuery):
'''
Performance Data Helper(PDH) Query object:
Provides a wrapper around the native PDH query object which
allows for query reuse, query storage, and general maintenance
functions (adding counter paths in various ways being the most
obvious ones).
'''
def __init__(self,*args,**namedargs):
'''
The PDH Query object is initialised with a single, optional
list argument, that must be properly formatted PDH Counter
paths. Generally this list will only be provided by the class
when it is being unpickled (removed from storage). Normal
use is to call the class with no arguments and use the various
addcounter functions (particularly, for end user's, the use of
addcounterbybrowsing is the most common approach) You might
want to provide the list directly if you want to hard-code the
elements with which your query deals (and thereby avoid the
overhead of unpickling the class).
'''
self.volatilecounters = []
BaseQuery.__init__(*(self,)+args, **namedargs)
def addperfcounter(self, object, counter, machine=None):
'''
A "Performance Counter" is a stable, known, common counter,
such as Memory, or Processor. The use of addperfcounter by
end-users is deprecated, since the use of
addcounterbybrowsing is considerably more flexible and general.
It is provided here to allow the easy development of scripts
which need to access variables so common we know them by name
(such as Memory|Available Bytes), and to provide symmetry with
the add inst counter method.
usage:
query.addperfcounter('Memory', 'Available Bytes')
It is just as easy to access addcounter directly, the following
has an identicle effect.
query.addcounter('Memory', 'Available Bytes')
'''
BaseQuery.addcounter(self, object=object, counter=counter, machine=machine)
def addinstcounter(self, object, counter,machine=None,objtype = 'Process',volatile=1,format = win32pdh.PDH_FMT_LONG):
'''
The purpose of using an instcounter is to track particular
instances of a counter object (e.g. a single processor, a single
running copy of a process). For instance, to track all python.exe
instances, you would need merely to ask:
query.addinstcounter('python','Virtual Bytes')
You can find the names of the objects and their available counters
by doing an addcounterbybrowsing() call on a query object (or by
looking in performance monitor's add dialog.)
Beyond merely rearranging the call arguments to make more sense,
if the volatile flag is true, the instcounters also recalculate
the paths of the available instances on every call to open the
query.
'''
if volatile:
self.volatilecounters.append((object,counter,machine,objtype,format))
else:
self.paths[len(self.paths):] = self.getinstpaths(object,counter,machine,objtype,format)
def getinstpaths(self,object,counter,machine=None,objtype='Process',format = win32pdh.PDH_FMT_LONG):
'''
### Not an end-user function
Calculate the paths for an instance object. Should alter
to allow processing for lists of object-counter pairs.
'''
items, instances = win32pdh.EnumObjectItems(None,None,objtype, -1)
# find out how many instances of this element we have...
instances.sort()
try:
cur = instances.index(object)
except ValueError:
return [] # no instances of this object
temp = [object]
try:
while instances[cur+1] == object:
temp.append(object)
cur = cur+1
except IndexError: # if we went over the end
pass
paths = []
for ind in range(len(temp)):
# can this raise an error?
paths.append(win32pdh.MakeCounterPath( (machine,'Process',object,None,ind,counter) ) )
return paths # should also return the number of elements for naming purposes
def open(self,*args,**namedargs):
'''
Explicitly open a query:
When you are needing to make multiple calls to the same query,
it is most efficient to open the query, run all of the calls,
then close the query, instead of having the collectdata method
automatically open and close the query each time it runs.
There are currently no arguments to open.
'''
# do all the normal opening stuff, self._base is now the query object
BaseQuery.open(*(self,)+args, **namedargs)
# should rewrite getinstpaths to take a single tuple
paths = []
for tup in self.volatilecounters:
paths[len(paths):] = self.getinstpaths(*tup)
for path in paths:
try:
self.counters.append(win32pdh.AddCounter(self._base, path))
self.curpaths.append(path) # if we fail on the line above, this path won't be in the table or the counters
except win32api.error:
pass # again, what to do with a malformed path???
def collectdatafor(self, totalperiod, period=1):
'''
Non-threaded collection of performance data:
This method allows you to specify the total period for which you would
like to run the Query, and the time interval between individual
runs. The collected data is stored in query.curresults at the
_end_ of the run. The pathnames for the query are stored in
query.curpaths.
e.g.:
query.collectdatafor(30,2)
Will collect data for 30seconds at 2 second intervals
'''
tempresults = []
try:
self.open()
for ind in range(totalperiod/period):
tempresults.append(self.collectdata())
time.sleep(period)
self.curresults = tempresults
finally:
self.close()
def collectdatawhile(self, period=1):
'''
Threaded collection of performance data:
This method sets up a simple semaphor system for signalling
when you would like to start and stop a threaded data collection
method. The collection runs every period seconds until the
semaphor attribute is set to a non-true value (which normally
should be done by calling query.collectdatawhile_stop() .)
e.g.:
query.collectdatawhile(2)
# starts the query running, returns control to the caller immediately
# is collecting data every two seconds.
# do whatever you want to do while the thread runs, then call:
query.collectdatawhile_stop()
# when you want to deal with the data. It is generally a good idea
# to sleep for period seconds yourself, since the query will not copy
# the required data until the next iteration:
time.sleep(2)
# now you can access the data from the attributes of the query
query.curresults
query.curpaths
'''
self.collectdatawhile_active = 1
_thread.start_new_thread(self.collectdatawhile_slave,(period,))
def collectdatawhile_stop(self):
'''
Signals the collectdatawhile slave thread to stop collecting data
on the next logging iteration.
'''
self.collectdatawhile_active = 0
def collectdatawhile_slave(self, period):
'''
### Not a public function
Does the threaded work of collecting the data and storing it
in an attribute of the class.
'''
tempresults = []
try:
self.open() # also sets active, so can't be changed.
while self.collectdatawhile_active:
tempresults.append(self.collectdata())
time.sleep(period)
self.curresults = tempresults
finally:
self.close()
# pickle functions
def __getinitargs__(self):
return (self.paths,)
def __getstate__(self):
return self.volatilecounters
def __setstate__(self, volatilecounters):
self.volatilecounters = volatilecounters
class QueryError:
def __init__(self, query):
self.query = query
def __repr__(self):
return '<Query Error in %s>'%repr(self.query)
__str__ = __repr__