tretool 0.2.1__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tretool/tasklib.py ADDED
@@ -0,0 +1,730 @@
1
+ import os
2
+ import psutil
3
+ import time
4
+ import signal
5
+ import subprocess
6
+ import threading
7
+ from abc import ABC, abstractmethod
8
+ from typing import List, Dict, Optional, Callable, Set, Tuple, Union
9
+ from enum import Enum, auto
10
+ from dataclasses import dataclass, field
11
+ from concurrent.futures import ThreadPoolExecutor, Future
12
+ import logging
13
+ from logging.handlers import RotatingFileHandler
14
+ import platform
15
+ import json
16
+ from collections import defaultdict
17
+
18
+ # 日志配置
19
+ def setup_logging(log_file: str = 'process_manager.log', max_size: int = 10):
20
+ """配置日志记录"""
21
+ logger = logging.getLogger('ProcessManager')
22
+ logger.setLevel(logging.INFO)
23
+
24
+ formatter = logging.Formatter(
25
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
26
+ )
27
+
28
+ # 控制台处理器
29
+ console_handler = logging.StreamHandler()
30
+ console_handler.setFormatter(formatter)
31
+
32
+ # 文件处理器(轮转日志)
33
+ file_handler = RotatingFileHandler(
34
+ log_file, maxBytes=max_size*1024*1024, backupCount=5
35
+ )
36
+ file_handler.setFormatter(formatter)
37
+
38
+ logger.addHandler(console_handler)
39
+ logger.addHandler(file_handler)
40
+ return logger
41
+
42
+ logger = setup_logging()
43
+
44
+ class ProcessStatus(Enum):
45
+ """进程状态枚举"""
46
+ RUNNING = auto()
47
+ STOPPED = auto()
48
+ ZOMBIE = auto()
49
+ UNKNOWN = auto()
50
+ IDLE = auto()
51
+ SLEEPING = auto()
52
+ TRACING_STOP = auto()
53
+
54
+ @dataclass
55
+ class ProcessInfo:
56
+ """进程信息数据类"""
57
+ pid: int
58
+ name: str
59
+ status: ProcessStatus
60
+ cpu_percent: float
61
+ memory_mb: float
62
+ create_time: float
63
+ cmdline: List[str]
64
+ threads: int
65
+ username: str
66
+ connections: List[dict]
67
+ open_files: List[str]
68
+ children: List['ProcessInfo'] = field(default_factory=list)
69
+ exit_code: Optional[int] = None
70
+ cpu_times: Optional[dict] = None
71
+ io_counters: Optional[dict] = None
72
+ environ: Optional[dict] = None
73
+
74
+ class ProcessObserver(ABC):
75
+ """观察者抽象基类"""
76
+
77
+ @abstractmethod
78
+ def on_process_start(self, pid: int, info: ProcessInfo):
79
+ """进程启动回调"""
80
+ pass
81
+
82
+ @abstractmethod
83
+ def on_process_stop(self, pid: int, info: ProcessInfo):
84
+ """进程停止回调"""
85
+ pass
86
+
87
+ @abstractmethod
88
+ def on_process_update(self, pid: int, info: ProcessInfo):
89
+ """进程状态更新回调"""
90
+ pass
91
+
92
+ @abstractmethod
93
+ def on_resource_alert(self, pid: int, alert_type: str, info: ProcessInfo):
94
+ """资源警报回调"""
95
+ pass
96
+
97
+ class LoggingObserver(ProcessObserver):
98
+ """日志记录观察者实现"""
99
+
100
+ def __init__(self, logger: logging.Logger = None):
101
+ self.logger = logger or logging.getLogger('ProcessObserver')
102
+
103
+ def on_process_start(self, pid: int, info: ProcessInfo):
104
+ self.logger.info(
105
+ f"Process started - PID: {pid}, "
106
+ f"Name: {info.name}, "
107
+ f"User: {info.username}, "
108
+ f"CMD: {' '.join(info.cmdline[:1])}..."
109
+ )
110
+
111
+ def on_process_stop(self, pid: int, info: ProcessInfo):
112
+ exit_code = info.exit_code if info.exit_code is not None else 'N/A'
113
+ self.logger.info(
114
+ f"Process stopped - PID: {pid}, "
115
+ f"ExitCode: {exit_code}, "
116
+ f"Duration: {time.time() - info.create_time:.2f}s"
117
+ )
118
+
119
+ def on_process_update(self, pid: int, info: ProcessInfo):
120
+ self.logger.debug(
121
+ f"Process update - PID: {pid}, "
122
+ f"CPU: {info.cpu_percent:.1f}%, "
123
+ f"MEM: {info.memory_mb:.1f}MB, "
124
+ f"Threads: {info.threads}"
125
+ )
126
+
127
+ def on_resource_alert(self, pid: int, alert_type: str, info: ProcessInfo):
128
+ alert_msg = {
129
+ 'high_cpu': f"High CPU usage ({info.cpu_percent:.1f}%)",
130
+ 'high_memory': f"High memory usage ({info.memory_mb:.1f}MB)",
131
+ 'many_threads': f"Too many threads ({info.threads})",
132
+ 'many_connections': f"Too many connections ({len(info.connections)})",
133
+ 'many_files': f"Too many open files ({len(info.open_files)})"
134
+ }.get(alert_type, alert_type)
135
+
136
+ self.logger.warning(
137
+ f"Resource alert - PID: {pid} {alert_msg}, "
138
+ f"Name: {info.name}"
139
+ )
140
+
141
+ class ResourceGuardObserver(ProcessObserver):
142
+ """资源守护观察者(自动终止异常进程)"""
143
+
144
+ def __init__(self,
145
+ max_cpu: float = 90.0,
146
+ max_memory_mb: float = 1024,
147
+ max_threads: int = 100,
148
+ max_connections: int = 50,
149
+ max_files: int = 500,
150
+ max_duration: float = 3600):
151
+ self.max_cpu = max_cpu
152
+ self.max_memory = max_memory_mb
153
+ self.max_threads = max_threads
154
+ self.max_connections = max_connections
155
+ self.max_files = max_files
156
+ self.max_duration = max_duration
157
+ self.violations = defaultdict(dict)
158
+
159
+ def on_process_start(self, pid: int, info: ProcessInfo):
160
+ self.violations[pid] = {
161
+ 'start_time': time.time(),
162
+ 'high_cpu_count': 0,
163
+ 'high_memory_count': 0,
164
+ 'high_threads_count': 0,
165
+ 'high_connections_count': 0,
166
+ 'high_files_count': 0
167
+ }
168
+
169
+ def on_process_stop(self, pid: int, info: ProcessInfo):
170
+ self.violations.pop(pid, None)
171
+
172
+ def on_process_update(self, pid: int, info: ProcessInfo):
173
+ if pid not in self.violations:
174
+ return
175
+
176
+ violations = self.violations[pid]
177
+
178
+ # 检查运行时长
179
+ if time.time() - info.create_time > self.max_duration:
180
+ self._terminate(pid, reason="Timeout exceeded")
181
+ return
182
+
183
+ # 检查资源使用
184
+ if info.cpu_percent > self.max_cpu:
185
+ violations['high_cpu_count'] += 1
186
+ else:
187
+ violations['high_cpu_count'] = 0
188
+
189
+ if info.memory_mb > self.max_memory:
190
+ violations['high_memory_count'] += 1
191
+ else:
192
+ violations['high_memory_count'] = 0
193
+
194
+ if info.threads > self.max_threads:
195
+ violations['high_threads_count'] += 1
196
+ else:
197
+ violations['high_threads_count'] = 0
198
+
199
+ if len(info.connections) > self.max_connections:
200
+ violations['high_connections_count'] += 1
201
+ else:
202
+ violations['high_connections_count'] = 0
203
+
204
+ if len(info.open_files) > self.max_files:
205
+ violations['high_files_count'] += 1
206
+ else:
207
+ violations['high_files_count'] = 0
208
+
209
+ # 连续3次超标则终止
210
+ if any(count >= 3 for count in violations.values() if isinstance(count, int)):
211
+ reason = ", ".join(
212
+ f"{k.replace('_count', '')}"
213
+ for k, v in violations.items()
214
+ if isinstance(v, int) and v >= 3
215
+ )
216
+ self._terminate(pid, reason=f"Resource violation: {reason}")
217
+
218
+ def on_resource_alert(self, pid: int, alert_type: str, info: ProcessInfo):
219
+ pass # 已在update中处理
220
+
221
+ def _terminate(self, pid: int, reason: str):
222
+ try:
223
+ os.kill(pid, signal.SIGTERM)
224
+ logger.warning(f"Terminated process {pid}, reason: {reason}")
225
+ time.sleep(1)
226
+ if psutil.pid_exists(pid):
227
+ os.kill(pid, signal.SIGKILL)
228
+ logger.warning(f"Force killed process {pid}")
229
+ except ProcessLookupError:
230
+ pass
231
+ finally:
232
+ self.violations.pop(pid, None)
233
+
234
+ class ProcessError(Exception):
235
+ """进程操作异常基类"""
236
+ pass
237
+
238
+ class ProcessNotFoundError(ProcessError):
239
+ """进程未找到异常"""
240
+ pass
241
+
242
+ class ProcessPermissionError(ProcessError):
243
+ """进程权限异常"""
244
+ pass
245
+
246
+ class ProcessManager:
247
+ """进程管理器主类"""
248
+
249
+ def __init__(self, monitor_interval: float = 1.0):
250
+ self._observers: List[ProcessObserver] = []
251
+ self._monitor_thread: Optional[threading.Thread] = None
252
+ self._monitor_interval = monitor_interval
253
+ self._should_monitor = False
254
+ self._process_cache: Dict[int, ProcessInfo] = {}
255
+ self._executor = ThreadPoolExecutor(max_workers=8)
256
+ self._lock = threading.RLock()
257
+ self._start_time = time.time()
258
+ self._system = platform.system()
259
+
260
+ # 自动添加日志观察者
261
+ self.add_observer(LoggingObserver(logger))
262
+
263
+ def __enter__(self):
264
+ """上下文管理器入口"""
265
+ self.monitor_start(self._monitor_interval)
266
+ return self
267
+
268
+ def __exit__(self, exc_type, exc_val, exc_tb):
269
+ """上下文管理器退出"""
270
+ self.monitor_stop()
271
+ self._executor.shutdown(wait=True)
272
+
273
+ def start_process(
274
+ self,
275
+ command: Union[str, List[str]],
276
+ cwd: Optional[str] = None,
277
+ env: Optional[Dict[str, str]] = None,
278
+ shell: bool = False,
279
+ capture_output: bool = False,
280
+ **kwargs
281
+ ) -> ProcessInfo:
282
+ """
283
+ 启动新进程
284
+
285
+ 参数:
286
+ command: 命令字符串或列表
287
+ cwd: 工作目录
288
+ env: 环境变量
289
+ shell: 是否使用shell执行
290
+ capture_output: 是否捕获输出
291
+ kwargs: 其他subprocess.Popen参数
292
+
293
+ 返回:
294
+ ProcessInfo对象
295
+
296
+ 抛出:
297
+ ProcessError: 启动失败时
298
+ """
299
+ try:
300
+ if isinstance(command, str) and not shell:
301
+ command = [cmd.strip() for cmd in command.split() if cmd.strip()]
302
+
303
+ stdout = subprocess.PIPE if capture_output else None
304
+ stderr = subprocess.PIPE if capture_output else None
305
+
306
+ proc = psutil.Popen(
307
+ command,
308
+ cwd=cwd,
309
+ env=env or os.environ,
310
+ stdout=stdout,
311
+ stderr=stderr,
312
+ shell=shell,
313
+ **kwargs
314
+ )
315
+
316
+ info = self._get_process_info(proc.pid)
317
+ self._notify_observers('start', proc.pid, info)
318
+
319
+ # 启动线程监控子进程输出
320
+ if capture_output:
321
+ self._executor.submit(self._capture_output, proc)
322
+
323
+ return info
324
+ except Exception as e:
325
+ raise ProcessError(f"Failed to start process: {str(e)}")
326
+
327
+ def stop_process(
328
+ self,
329
+ pid: int,
330
+ timeout: int = 5,
331
+ force: bool = False,
332
+ kill_children: bool = True
333
+ ) -> bool:
334
+ """
335
+ 停止指定进程
336
+
337
+ 参数:
338
+ pid: 进程ID
339
+ timeout: 等待超时(秒)
340
+ force: 是否强制杀死
341
+ kill_children: 是否杀死子进程
342
+
343
+ 返回:
344
+ 是否成功停止
345
+
346
+ 抛出:
347
+ ProcessNotFoundError: 进程不存在时
348
+ ProcessError: 其他错误
349
+ """
350
+ try:
351
+ proc = psutil.Process(pid)
352
+ info = self._get_process_info(pid)
353
+
354
+ if kill_children:
355
+ for child in proc.children(recursive=True):
356
+ try:
357
+ if force:
358
+ child.kill()
359
+ else:
360
+ child.terminate()
361
+ except psutil.NoSuchProcess:
362
+ continue
363
+
364
+ if force:
365
+ proc.kill()
366
+ else:
367
+ proc.terminate()
368
+
369
+ try:
370
+ proc.wait(timeout=timeout)
371
+ except psutil.TimeoutExpired:
372
+ if force:
373
+ return False
374
+ try:
375
+ proc.kill()
376
+ proc.wait(timeout=1)
377
+ except:
378
+ return False
379
+
380
+ self._notify_observers('stop', pid, info)
381
+ return True
382
+ except psutil.NoSuchProcess:
383
+ raise ProcessNotFoundError(f"Process {pid} not found")
384
+ except psutil.AccessDenied:
385
+ raise ProcessPermissionError(f"No permission to access process {pid}")
386
+ except Exception as e:
387
+ raise ProcessError(f"Failed to stop process {pid}: {str(e)}")
388
+
389
+ def list_processes(
390
+ self,
391
+ name_filter: Optional[str] = None,
392
+ user_filter: Optional[str] = None,
393
+ include_children: bool = False,
394
+ only_running: bool = True
395
+ ) -> List[ProcessInfo]:
396
+ """
397
+ 获取进程列表
398
+
399
+ 参数:
400
+ name_filter: 进程名过滤
401
+ user_filter: 用户名过滤
402
+ include_children: 是否包含子进程
403
+ only_running: 是否只返回运行中的进程
404
+
405
+ 返回:
406
+ 进程信息列表
407
+ """
408
+ processes = []
409
+ for proc in psutil.process_iter(['pid', 'name', 'status', 'username']):
410
+ try:
411
+ if only_running and proc.info['status'] != psutil.STATUS_RUNNING:
412
+ continue
413
+
414
+ if name_filter and name_filter.lower() not in proc.info['name'].lower():
415
+ continue
416
+
417
+ if user_filter and user_filter.lower() != proc.info['username'].lower():
418
+ continue
419
+
420
+ info = self._get_process_info(proc.info['pid'])
421
+ processes.append(info)
422
+
423
+ if include_children:
424
+ for child in proc.children(recursive=True):
425
+ try:
426
+ processes.append(self._get_process_info(child.pid))
427
+ except psutil.NoSuchProcess:
428
+ continue
429
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
430
+ continue
431
+ return processes
432
+
433
+ def get_process_info(self, pid: int) -> ProcessInfo:
434
+ """
435
+ 获取指定进程详细信息
436
+
437
+ 参数:
438
+ pid: 进程ID
439
+
440
+ 返回:
441
+ ProcessInfo对象
442
+
443
+ 抛出:
444
+ ProcessNotFoundError: 进程不存在时
445
+ """
446
+ try:
447
+ return self._get_process_info(pid)
448
+ except psutil.NoSuchProcess:
449
+ raise ProcessNotFoundError(f"Process {pid} not found")
450
+
451
+ def monitor_start(self, interval: float = None) -> None:
452
+ """启动后台监控"""
453
+ if self._monitor_thread and self._monitor_thread.is_alive():
454
+ return
455
+
456
+ if interval is not None:
457
+ self._monitor_interval = interval
458
+
459
+ self._should_monitor = True
460
+ self._monitor_thread = threading.Thread(
461
+ target=self._monitor_loop,
462
+ daemon=True,
463
+ name="ProcessMonitor"
464
+ )
465
+ self._monitor_thread.start()
466
+ logger.info("Process monitor started")
467
+
468
+ def monitor_stop(self) -> None:
469
+ """停止监控"""
470
+ if not self._should_monitor:
471
+ return
472
+
473
+ self._should_monitor = False
474
+ if self._monitor_thread:
475
+ self._monitor_thread.join(timeout=2.0)
476
+ logger.info("Process monitor stopped")
477
+
478
+ def add_observer(self, observer: ProcessObserver) -> None:
479
+ """添加观察者"""
480
+ with self._lock:
481
+ if observer not in self._observers:
482
+ self._observers.append(observer)
483
+ logger.debug(f"Added observer: {observer.__class__.__name__}")
484
+
485
+ def remove_observer(self, observer: ProcessObserver) -> None:
486
+ """移除观察者"""
487
+ with self._lock:
488
+ if observer in self._observers:
489
+ self._observers.remove(observer)
490
+ logger.debug(f"Removed observer: {observer.__class__.__name__}")
491
+
492
+ def get_stats(self) -> dict:
493
+ """获取管理器统计信息"""
494
+ return {
495
+ "uptime": time.time() - self._start_time,
496
+ "monitoring": self._should_monitor,
497
+ "monitor_interval": self._monitor_interval,
498
+ "observed_processes": len(self._process_cache),
499
+ "observers": len(self._observers)
500
+ }
501
+
502
+ def _monitor_loop(self) -> None:
503
+ """监控主循环"""
504
+ logger.info("Monitor loop started")
505
+ while self._should_monitor:
506
+ start_time = time.time()
507
+ current_processes = {}
508
+
509
+ try:
510
+ for proc in psutil.process_iter(['pid', 'name', 'status']):
511
+ try:
512
+ pid = proc.info['pid']
513
+ info = self._get_process_info(pid)
514
+ current_processes[pid] = info
515
+
516
+ # 检查资源使用情况
517
+ self._check_resources(pid, info)
518
+
519
+ # 处理新增或状态变化的进程
520
+ if pid not in self._process_cache:
521
+ self._notify_observers('start', pid, info)
522
+ elif info.status != self._process_cache[pid].status:
523
+ self._notify_observers('update', pid, info)
524
+
525
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
526
+ continue
527
+
528
+ # 处理已退出的进程
529
+ for pid in set(self._process_cache) - set(current_processes):
530
+ info = self._process_cache[pid]
531
+ info.status = ProcessStatus.STOPPED
532
+ self._notify_observers('stop', pid, info)
533
+
534
+ self._process_cache = current_processes
535
+
536
+ except Exception as e:
537
+ logger.error(f"Monitor loop error: {str(e)}", exc_info=True)
538
+
539
+ # 精确控制间隔时间
540
+ elapsed = time.time() - start_time
541
+ sleep_time = max(0, self._monitor_interval - elapsed)
542
+ time.sleep(sleep_time)
543
+
544
+ def _get_process_info(self, pid: int) -> ProcessInfo:
545
+ """获取进程详细信息"""
546
+ proc = psutil.Process(pid)
547
+ with proc.oneshot():
548
+ mem_info = proc.memory_info()
549
+ status = self._convert_status(proc.status())
550
+
551
+ try:
552
+ connections = proc.connections()
553
+ except (psutil.AccessDenied, psutil.Error):
554
+ connections = []
555
+
556
+ try:
557
+ open_files = [f.path for f in proc.open_files()]
558
+ except (psutil.AccessDenied, psutil.Error):
559
+ open_files = []
560
+
561
+ try:
562
+ cpu_times = proc.cpu_times()._asdict()
563
+ except (psutil.AccessDenied, psutil.Error):
564
+ cpu_times = None
565
+
566
+ try:
567
+ io_counters = proc.io_counters()._asdict()
568
+ except (psutil.AccessDenied, psutil.Error):
569
+ io_counters = None
570
+
571
+ try:
572
+ environ = proc.environ()
573
+ except (psutil.AccessDenied, psutil.Error):
574
+ environ = None
575
+
576
+ children = []
577
+ try:
578
+ for child in proc.children(recursive=False):
579
+ try:
580
+ children.append(self._get_process_info(child.pid))
581
+ except psutil.NoSuchProcess:
582
+ continue
583
+ except psutil.AccessDenied:
584
+ pass
585
+
586
+ return ProcessInfo(
587
+ pid=pid,
588
+ name=proc.name(),
589
+ status=status,
590
+ cpu_percent=proc.cpu_percent(),
591
+ memory_mb=mem_info.rss / (1024 * 1024),
592
+ create_time=proc.create_time(),
593
+ cmdline=proc.cmdline(),
594
+ threads=proc.num_threads(),
595
+ username=proc.username(),
596
+ connections=connections,
597
+ open_files=open_files,
598
+ children=children,
599
+ exit_code=proc.returncode if status == ProcessStatus.STOPPED else None,
600
+ cpu_times=cpu_times,
601
+ io_counters=io_counters,
602
+ environ=environ
603
+ )
604
+
605
+ def _check_resources(self, pid: int, info: ProcessInfo) -> None:
606
+ """检查资源使用情况并触发警报"""
607
+ thresholds = [
608
+ ('high_cpu', info.cpu_percent > 90),
609
+ ('high_memory', info.memory_mb > 1024),
610
+ ('many_threads', info.threads > 100),
611
+ ('many_connections', len(info.connections) > 50),
612
+ ('many_files', len(info.open_files) > 500)
613
+ ]
614
+
615
+ for alert_type, condition in thresholds:
616
+ if condition:
617
+ self._notify_observers('alert', pid, info, alert_type)
618
+
619
+ def _notify_observers(self, event_type: str, pid: int, info: ProcessInfo, alert_type: str = None):
620
+ """通知所有观察者"""
621
+ def notify_task():
622
+ with self._lock:
623
+ observers = self._observers.copy()
624
+
625
+ for observer in observers:
626
+ try:
627
+ if event_type == 'start':
628
+ observer.on_process_start(pid, info)
629
+ elif event_type == 'stop':
630
+ observer.on_process_stop(pid, info)
631
+ elif event_type == 'update':
632
+ observer.on_process_update(pid, info)
633
+ elif event_type == 'alert':
634
+ observer.on_resource_alert(pid, alert_type, info)
635
+ except Exception as e:
636
+ logger.error(f"Observer notification failed: {str(e)}", exc_info=True)
637
+
638
+ self._executor.submit(notify_task)
639
+
640
+ def _capture_output(self, proc: psutil.Popen):
641
+ """捕获进程输出"""
642
+ def reader(stream, callback):
643
+ try:
644
+ for line in iter(stream.readline, b''):
645
+ if line:
646
+ callback(line.decode('utf-8', errors='replace').strip())
647
+ except ValueError:
648
+ pass # Stream closed
649
+
650
+ def stdout_callback(line):
651
+ logger.info(f"Process {proc.pid} stdout: {line}")
652
+
653
+ def stderr_callback(line):
654
+ logger.warning(f"Process {proc.pid} stderr: {line}")
655
+
656
+ stdout_thread = threading.Thread(
657
+ target=reader,
658
+ args=(proc.stdout, stdout_callback),
659
+ daemon=True
660
+ )
661
+
662
+ stderr_thread = threading.Thread(
663
+ target=reader,
664
+ args=(proc.stderr, stderr_callback),
665
+ daemon=True
666
+ )
667
+
668
+ stdout_thread.start()
669
+ stderr_thread.start()
670
+
671
+ stdout_thread.join()
672
+ stderr_thread.join()
673
+
674
+ def _convert_status(self, status: str) -> ProcessStatus:
675
+ """转换状态字符串为枚举"""
676
+ status = status.upper()
677
+ status_map = {
678
+ "RUNNING": ProcessStatus.RUNNING,
679
+ "SLEEPING": ProcessStatus.SLEEPING,
680
+ "IDLE": ProcessStatus.IDLE,
681
+ "STOPPED": ProcessStatus.STOPPED,
682
+ "TRACING_STOP": ProcessStatus.TRACING_STOP,
683
+ "ZOMBIE": ProcessStatus.ZOMBIE
684
+ }
685
+ return status_map.get(status, ProcessStatus.UNKNOWN)
686
+
687
+ # 示例用法
688
+ if __name__ == "__main__":
689
+ # 创建进程管理器
690
+ with ProcessManager(monitor_interval=2.0) as manager:
691
+ # 添加资源守护观察者
692
+ guard = ResourceGuardObserver(
693
+ max_cpu=80.0,
694
+ max_memory_mb=512,
695
+ max_threads=50,
696
+ max_duration=30
697
+ )
698
+ manager.add_observer(guard)
699
+
700
+ # 启动一个进程
701
+ try:
702
+ print("Starting a process...")
703
+ proc_info = manager.start_process(
704
+ ["python", "-c", "while True: pass"], # 模拟CPU密集型进程
705
+ capture_output=True
706
+ )
707
+ print(f"Started process PID: {proc_info.pid}")
708
+
709
+ # 查看进程列表
710
+ print("\nProcess list:")
711
+ for p in manager.list_processes(only_running=True)[:5]:
712
+ print(f"PID: {p.pid}, Name: {p.name}, CPU: {p.cpu_percent}%")
713
+
714
+ # 让监控运行一段时间
715
+ print("\nMonitoring for 10 seconds...")
716
+ time.sleep(10)
717
+
718
+ except KeyboardInterrupt:
719
+ print("\nInterrupted by user")
720
+ except Exception as e:
721
+ print(f"Error: {str(e)}")
722
+ finally:
723
+ # 停止所有我们启动的进程
724
+ for p in manager.list_processes():
725
+ if "python" in p.name.lower():
726
+ try:
727
+ print(f"Stopping process {p.pid}...")
728
+ manager.stop_process(p.pid)
729
+ except Exception as e:
730
+ print(f"Error stopping process {p.pid}: {str(e)}")