0
# Exception Hierarchy
1
2
psutil defines a comprehensive exception hierarchy for handling various error conditions that can occur during system monitoring and process management operations. Proper exception handling is essential for robust applications using psutil.
3
4
## Exception Hierarchy
5
6
```python
7
# psutil exception hierarchy:
8
# Exception (built-in)
9
# └── Error (psutil base exception)
10
# ├── NoSuchProcess
11
# ├── ZombieProcess
12
# ├── AccessDenied
13
# └── TimeoutExpired
14
```
15
16
## Base Exception Class
17
18
### Error
19
20
```python
21
import psutil
22
23
# Base exception class for all psutil exceptions
24
try:
25
p = psutil.Process(99999) # Non-existent PID
26
print(p.name())
27
except psutil.Error as e:
28
print(f"psutil error occurred: {e}")
29
print(f"Exception type: {type(e).__name__}")
30
```
31
{ .api }
32
33
## Core Exception Types
34
35
### NoSuchProcess
36
37
Raised when a process no longer exists or was never found.
38
39
```python
40
# NoSuchProcess - Process not found or terminated
41
try:
42
p = psutil.Process(99999) # Non-existent PID
43
print(p.name())
44
except psutil.NoSuchProcess as e:
45
print(f"Process not found: {e}")
46
print(f"PID: {e.pid}")
47
print(f"Process name: {e.name}")
48
print(f"Message: {e.msg}")
49
50
# Common scenario - process terminates between operations
51
def monitor_process_safely(pid):
52
"""Safely monitor a process that might terminate."""
53
try:
54
p = psutil.Process(pid)
55
56
while True:
57
try:
58
print(f"CPU: {p.cpu_percent()}")
59
print(f"Memory: {p.memory_info().rss / 1024**2:.1f} MB")
60
time.sleep(1)
61
except psutil.NoSuchProcess:
62
print(f"Process {pid} terminated")
63
break
64
65
except psutil.NoSuchProcess:
66
print(f"Process {pid} not found")
67
```
68
{ .api }
69
70
### AccessDenied
71
72
Raised when insufficient permissions prevent accessing process information.
73
74
```python
75
# AccessDenied - Permission denied
76
import os
77
78
try:
79
# Try to access system process (may require admin/root)
80
if psutil.WINDOWS:
81
system_pid = 4 # System process on Windows
82
else:
83
system_pid = 1 # Init process on Unix
84
85
p = psutil.Process(system_pid)
86
print(p.cmdline()) # May require elevated privileges
87
88
except psutil.AccessDenied as e:
89
print(f"Access denied: {e}")
90
print(f"PID: {e.pid}")
91
print(f"Process name: {e.name}")
92
print(f"Current user: {os.getenv('USER') or os.getenv('USERNAME')}")
93
print("Try running with elevated privileges")
94
95
# Graceful handling of access denied
96
def get_accessible_process_info(pid):
97
"""Get process info with graceful handling of access denied."""
98
try:
99
p = psutil.Process(pid)
100
info = {'pid': pid, 'accessible': True}
101
102
# Try to get basic info
103
try:
104
info['name'] = p.name()
105
except psutil.AccessDenied:
106
info['name'] = 'Access Denied'
107
108
try:
109
info['status'] = p.status()
110
except psutil.AccessDenied:
111
info['status'] = 'Unknown'
112
113
try:
114
info['cpu_percent'] = p.cpu_percent()
115
except psutil.AccessDenied:
116
info['cpu_percent'] = None
117
118
return info
119
120
except psutil.NoSuchProcess:
121
return {'pid': pid, 'accessible': False, 'error': 'Process not found'}
122
123
# Get info for current process (should work)
124
info = get_accessible_process_info(os.getpid())
125
print(f"Current process info: {info}")
126
```
127
{ .api }
128
129
### ZombieProcess
130
131
Raised when trying to access a zombie process (Unix systems).
132
133
```python
134
# ZombieProcess - Process is in zombie state
135
try:
136
# Zombie processes are common on Unix systems
137
for proc in psutil.process_iter():
138
try:
139
if proc.status() == psutil.STATUS_ZOMBIE:
140
print(f"Found zombie: PID {proc.pid}")
141
# Trying to access zombie process info may raise ZombieProcess
142
print(proc.name()) # This might raise ZombieProcess
143
144
except psutil.ZombieProcess as e:
145
print(f"Zombie process detected: {e}")
146
print(f"PID: {e.pid}")
147
print(f"Process name: {e.name}")
148
# Zombie processes can't provide most information
149
150
except (psutil.NoSuchProcess, psutil.AccessDenied):
151
pass # Skip inaccessible processes
152
153
# Handling zombie processes in monitoring
154
def safe_process_iterator():
155
"""Iterate processes safely handling zombies."""
156
accessible_procs = []
157
zombie_count = 0
158
159
for proc in psutil.process_iter(['pid', 'name', 'status']):
160
try:
161
# Check if process is zombie
162
if proc.info['status'] == psutil.STATUS_ZOMBIE:
163
zombie_count += 1
164
continue
165
166
# Try to get additional info
167
proc.cpu_percent() # This might raise ZombieProcess
168
accessible_procs.append(proc)
169
170
except psutil.ZombieProcess:
171
zombie_count += 1
172
except (psutil.NoSuchProcess, psutil.AccessDenied):
173
pass
174
175
print(f"Found {len(accessible_procs)} accessible processes")
176
print(f"Found {zombie_count} zombie processes")
177
return accessible_procs
178
179
# accessible_procs = safe_process_iterator()
180
```
181
{ .api }
182
183
### TimeoutExpired
184
185
Raised when an operation exceeds the specified timeout.
186
187
```python
188
# TimeoutExpired - Operation timed out
189
import time
190
191
try:
192
# Create a process to demonstrate timeout
193
import subprocess
194
proc = subprocess.Popen(['sleep', '10']) # Unix command
195
196
p = psutil.Process(proc.pid)
197
198
# Wait with timeout
199
p.wait(timeout=2) # Will timeout after 2 seconds
200
201
except psutil.TimeoutExpired as e:
202
print(f"Operation timed out: {e}")
203
print(f"PID: {e.pid}")
204
print(f"Process name: {e.name}")
205
print(f"Timeout seconds: {e.seconds}")
206
207
# Clean up the process
208
try:
209
p.terminate()
210
p.wait(timeout=5) # Give it time to terminate gracefully
211
except psutil.TimeoutExpired:
212
p.kill() # Force kill if it won't terminate
213
214
finally:
215
# Ensure cleanup
216
try:
217
proc.terminate()
218
except:
219
pass
220
221
# Timeout handling in monitoring
222
def wait_for_process_with_retry(pid, timeout=30, retries=3):
223
"""Wait for process termination with retry logic."""
224
for attempt in range(retries):
225
try:
226
p = psutil.Process(pid)
227
p.wait(timeout=timeout)
228
print(f"Process {pid} terminated normally")
229
return True
230
231
except psutil.TimeoutExpired:
232
print(f"Attempt {attempt + 1}: Process {pid} did not terminate within {timeout}s")
233
if attempt < retries - 1:
234
print("Retrying...")
235
else:
236
print("Giving up - process still running")
237
return False
238
239
except psutil.NoSuchProcess:
240
print(f"Process {pid} already terminated")
241
return True
242
243
return False
244
```
245
{ .api }
246
247
## Exception Handling Patterns
248
249
### Comprehensive Exception Handling
250
251
```python
252
def robust_process_operation(pid, operation_name="operation"):
253
"""Perform process operations with comprehensive exception handling."""
254
try:
255
p = psutil.Process(pid)
256
257
# Perform the actual operation
258
if operation_name == "get_info":
259
return {
260
'pid': p.pid,
261
'name': p.name(),
262
'status': p.status(),
263
'cpu_percent': p.cpu_percent(),
264
'memory_info': p.memory_info()
265
}
266
elif operation_name == "terminate":
267
p.terminate()
268
p.wait(timeout=10)
269
return {"result": "terminated"}
270
271
except psutil.NoSuchProcess as e:
272
return {
273
"error": "NoSuchProcess",
274
"message": f"Process {e.pid} ({e.name}) not found or terminated",
275
"details": str(e)
276
}
277
278
except psutil.AccessDenied as e:
279
return {
280
"error": "AccessDenied",
281
"message": f"Permission denied for process {e.pid} ({e.name})",
282
"details": str(e),
283
"suggestion": "Try running with elevated privileges"
284
}
285
286
except psutil.ZombieProcess as e:
287
return {
288
"error": "ZombieProcess",
289
"message": f"Process {e.pid} ({e.name}) is in zombie state",
290
"details": str(e)
291
}
292
293
except psutil.TimeoutExpired as e:
294
return {
295
"error": "TimeoutExpired",
296
"message": f"Operation on process {e.pid} ({e.name}) timed out after {e.seconds}s",
297
"details": str(e)
298
}
299
300
except psutil.Error as e:
301
return {
302
"error": "GeneralPsutilError",
303
"message": f"Unexpected psutil error: {str(e)}",
304
"details": str(e)
305
}
306
307
except Exception as e:
308
return {
309
"error": "UnexpectedError",
310
"message": f"Unexpected error: {str(e)}",
311
"details": str(e)
312
}
313
314
# Example usage
315
result = robust_process_operation(os.getpid(), "get_info")
316
print(f"Operation result: {result}")
317
```
318
{ .api }
319
320
### Batch Operation Exception Handling
321
322
```python
323
def process_batch_operation(pids, operation_func):
324
"""Perform operations on multiple processes with proper exception handling."""
325
results = {
326
'successful': [],
327
'failed': [],
328
'summary': {
329
'total': len(pids),
330
'successful': 0,
331
'no_such_process': 0,
332
'access_denied': 0,
333
'zombie_process': 0,
334
'timeout_expired': 0,
335
'other_errors': 0
336
}
337
}
338
339
for pid in pids:
340
try:
341
result = operation_func(pid)
342
results['successful'].append({
343
'pid': pid,
344
'result': result
345
})
346
results['summary']['successful'] += 1
347
348
except psutil.NoSuchProcess as e:
349
results['failed'].append({
350
'pid': pid,
351
'error': 'NoSuchProcess',
352
'details': str(e)
353
})
354
results['summary']['no_such_process'] += 1
355
356
except psutil.AccessDenied as e:
357
results['failed'].append({
358
'pid': pid,
359
'error': 'AccessDenied',
360
'details': str(e)
361
})
362
results['summary']['access_denied'] += 1
363
364
except psutil.ZombieProcess as e:
365
results['failed'].append({
366
'pid': pid,
367
'error': 'ZombieProcess',
368
'details': str(e)
369
})
370
results['summary']['zombie_process'] += 1
371
372
except psutil.TimeoutExpired as e:
373
results['failed'].append({
374
'pid': pid,
375
'error': 'TimeoutExpired',
376
'details': str(e)
377
})
378
results['summary']['timeout_expired'] += 1
379
380
except Exception as e:
381
results['failed'].append({
382
'pid': pid,
383
'error': 'OtherError',
384
'details': str(e)
385
})
386
results['summary']['other_errors'] += 1
387
388
return results
389
390
# Example: Get CPU usage for multiple processes
391
def get_cpu_percent(pid):
392
p = psutil.Process(pid)
393
return p.cpu_percent()
394
395
# Test with some PIDs
396
test_pids = [os.getpid(), 1, 99999] # Current, init, non-existent
397
batch_results = process_batch_operation(test_pids, get_cpu_percent)
398
399
print("Batch operation results:")
400
print(f"Summary: {batch_results['summary']}")
401
```
402
{ .api }
403
404
### Context Manager for Process Operations
405
406
```python
407
class SafeProcess:
408
"""Context manager for safe process operations."""
409
410
def __init__(self, pid):
411
self.pid = pid
412
self.process = None
413
self.error = None
414
415
def __enter__(self):
416
try:
417
self.process = psutil.Process(self.pid)
418
return self.process
419
except psutil.Error as e:
420
self.error = e
421
return None
422
423
def __exit__(self, exc_type, exc_val, exc_tb):
424
# Handle any cleanup if needed
425
if exc_type and issubclass(exc_type, psutil.Error):
426
# Log the psutil exception
427
print(f"psutil exception in context: {exc_val}")
428
return True # Suppress the exception
429
return False
430
431
# Usage example
432
def monitor_with_context_manager(pid):
433
"""Monitor process using context manager."""
434
with SafeProcess(pid) as p:
435
if p is None:
436
print(f"Could not access process {pid}")
437
return None
438
439
try:
440
return {
441
'name': p.name(),
442
'cpu_percent': p.cpu_percent(),
443
'memory_info': p.memory_info()
444
}
445
except psutil.Error as e:
446
print(f"Error during monitoring: {e}")
447
return None
448
449
# Test the context manager
450
info = monitor_with_context_manager(os.getpid())
451
print(f"Process info: {info}")
452
```
453
{ .api }
454
455
## Exception Analysis and Debugging
456
457
### Exception Information Extraction
458
459
```python
460
def analyze_psutil_exception(exception):
461
"""Analyze a psutil exception and extract useful information."""
462
analysis = {
463
'type': type(exception).__name__,
464
'message': str(exception),
465
'is_psutil_exception': isinstance(exception, psutil.Error)
466
}
467
468
# Extract exception-specific attributes
469
if hasattr(exception, 'pid'):
470
analysis['pid'] = exception.pid
471
if hasattr(exception, 'name'):
472
analysis['name'] = exception.name
473
if hasattr(exception, 'msg'):
474
analysis['msg'] = exception.msg
475
if hasattr(exception, 'seconds'):
476
analysis['seconds'] = exception.seconds
477
478
# Provide suggestions based on exception type
479
if isinstance(exception, psutil.NoSuchProcess):
480
analysis['suggestion'] = "Process may have terminated - check if PID is still valid"
481
elif isinstance(exception, psutil.AccessDenied):
482
analysis['suggestion'] = "Run with elevated privileges or check process ownership"
483
elif isinstance(exception, psutil.ZombieProcess):
484
analysis['suggestion'] = "Process is zombie - limited information available"
485
elif isinstance(exception, psutil.TimeoutExpired):
486
analysis['suggestion'] = "Increase timeout value or check if process is responsive"
487
488
return analysis
489
490
# Example exception analysis
491
try:
492
p = psutil.Process(99999)
493
p.name()
494
except psutil.Error as e:
495
analysis = analyze_psutil_exception(e)
496
print("Exception analysis:", analysis)
497
```
498
{ .api }
499
500
### Logging Integration
501
502
```python
503
import logging
504
505
# Set up logging for psutil exceptions
506
logging.basicConfig(level=logging.INFO)
507
logger = logging.getLogger(__name__)
508
509
def logged_process_operation(pid, operation_name):
510
"""Process operation with comprehensive logging."""
511
logger.info(f"Starting {operation_name} for PID {pid}")
512
513
try:
514
p = psutil.Process(pid)
515
516
if operation_name == "info":
517
result = p.as_dict(['pid', 'name', 'status', 'cpu_percent'])
518
logger.info(f"Successfully retrieved info for PID {pid}: {result}")
519
return result
520
521
except psutil.NoSuchProcess as e:
522
logger.warning(f"Process not found - PID: {e.pid}, Name: {e.name}")
523
raise
524
525
except psutil.AccessDenied as e:
526
logger.error(f"Access denied - PID: {e.pid}, Name: {e.name}")
527
raise
528
529
except psutil.ZombieProcess as e:
530
logger.warning(f"Zombie process - PID: {e.pid}, Name: {e.name}")
531
raise
532
533
except psutil.TimeoutExpired as e:
534
logger.error(f"Timeout expired - PID: {e.pid}, Name: {e.name}, Timeout: {e.seconds}s")
535
raise
536
537
except psutil.Error as e:
538
logger.error(f"General psutil error for PID {pid}: {e}")
539
raise
540
541
# Example with logging
542
try:
543
info = logged_process_operation(os.getpid(), "info")
544
except psutil.Error:
545
pass # Exception already logged
546
```
547
{ .api }
548
549
## Best Practices for Exception Handling
550
551
### Defensive Programming
552
553
```python
554
def defensive_process_monitor():
555
"""Example of defensive programming with psutil."""
556
557
# Always expect processes to disappear
558
active_processes = set()
559
560
while True:
561
try:
562
# Get current processes
563
current_pids = set(p.pid for p in psutil.process_iter())
564
565
# Find new processes
566
new_pids = current_pids - active_processes
567
for pid in new_pids:
568
try:
569
p = psutil.Process(pid)
570
print(f"New process: {pid} ({p.name()})")
571
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
572
pass # Process already gone or inaccessible
573
574
# Update active set
575
active_processes = current_pids
576
577
time.sleep(5)
578
579
except KeyboardInterrupt:
580
print("Monitoring stopped")
581
break
582
except Exception as e:
583
print(f"Unexpected error: {e}")
584
time.sleep(1) # Brief pause before retry
585
586
# Run defensive monitor
587
# defensive_process_monitor()
588
```
589
{ .api }
590
591
## Related Documentation
592
593
- [Process Management](process.md) - Process operations that may raise exceptions
594
- [System Information](system-info.md) - System operations and their exceptions
595
- [Constants](constants.md) - Constants used in exception handling
596
- [Sensors](sensors.md) - Sensor operations that may raise AttributeError