py-alaska 0.1.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.
@@ -0,0 +1,550 @@
1
+ """
2
+ ╔══════════════════════════════════════════════════════════════════════════════╗
3
+ ║ ALASK v2.0 Project ║
4
+ ║ Task Performance - RMI 성능 측정 및 통계 관리 모듈 ║
5
+ ╠══════════════════════════════════════════════════════════════════════════════╣
6
+ ║ Version : 2.0.0 ║
7
+ ║ Date : 2026-02-01 ║
8
+ ╠══════════════════════════════════════════════════════════════════════════════╣
9
+ ║ Classes: ║
10
+ ║ - MethodPerformance : 메서드별 성능 통계 (IPC/FUNC min/avg/max) ║
11
+ ║ - TaskPerformance : Task별 성능 통계 집계 ║
12
+ ║ - SystemPerformance : 시스템 전체 성능 통계 ║
13
+ ║ - TimingRecorder : TaskWorker용 로컬 타이밍 기록 및 배치 플러시 ║
14
+ ║ - PerformanceCollector: TaskManager용 시스템 통계 수집기 ║
15
+ ╚══════════════════════════════════════════════════════════════════════════════╝
16
+ """
17
+
18
+ from __future__ import annotations
19
+ from dataclasses import dataclass, field
20
+ from typing import Dict, List, Any, Optional, Tuple, TYPE_CHECKING, ClassVar
21
+ import time
22
+
23
+ if TYPE_CHECKING:
24
+ from .task_manager import TaskInfo
25
+
26
+
27
+ # ═══════════════════════════════════════════════════════════════════════════════
28
+ # 데이터 클래스 (PERF-003, PERF-002, PERF-001)
29
+ # ═══════════════════════════════════════════════════════════════════════════════
30
+
31
+ @dataclass
32
+ class MethodPerformance:
33
+ """PERF-003: 메서드별 성능 통계
34
+
35
+ 각 RMI 메서드의 호출 횟수와 IPC/FUNC 시간 통계를 관리합니다.
36
+ 최근 WINDOW_SIZE(10)개의 측정값을 기반으로 min/avg/max를 계산합니다.
37
+
38
+ Attributes:
39
+ name: 메서드 이름
40
+ call_count: 호출 횟수
41
+ ipc_window: IPC 시간 윈도우 (최근 10개, ms)
42
+ func_window: FUNC 시간 윈도우 (최근 10개, ms)
43
+ """
44
+ name: str
45
+ call_count: int = 0
46
+ ipc_window: List[float] = field(default_factory=list)
47
+ func_window: List[float] = field(default_factory=list)
48
+
49
+ WINDOW_SIZE: ClassVar[int] = 10
50
+
51
+ @property
52
+ def ipc_stats(self) -> Dict[str, float]:
53
+ """IPC 시간 통계 (min/avg/max in ms)"""
54
+ if not self.ipc_window:
55
+ return {'min': 0.0, 'avg': 0.0, 'max': 0.0}
56
+ return {
57
+ 'min': round(min(self.ipc_window), 2),
58
+ 'avg': round(sum(self.ipc_window) / len(self.ipc_window), 2),
59
+ 'max': round(max(self.ipc_window), 2)
60
+ }
61
+
62
+ @property
63
+ def func_stats(self) -> Dict[str, float]:
64
+ """FUNC 시간 통계 (min/avg/max in ms)"""
65
+ if not self.func_window:
66
+ return {'min': 0.0, 'avg': 0.0, 'max': 0.0}
67
+ return {
68
+ 'min': round(min(self.func_window), 2),
69
+ 'avg': round(sum(self.func_window) / len(self.func_window), 2),
70
+ 'max': round(max(self.func_window), 2)
71
+ }
72
+
73
+ @property
74
+ def total_time_avg(self) -> float:
75
+ """평균 총 소요 시간 (IPC + FUNC)"""
76
+ return round(self.ipc_stats['avg'] + self.func_stats['avg'], 2)
77
+
78
+ def to_dict(self) -> Dict[str, Any]:
79
+ """JSON 직렬화용 딕셔너리 변환"""
80
+ return {
81
+ 'name': self.name,
82
+ 'count': self.call_count,
83
+ 'ipc': self.ipc_stats,
84
+ 'func': self.func_stats,
85
+ 'total_avg': self.total_time_avg,
86
+ }
87
+
88
+
89
+ @dataclass
90
+ class TaskPerformance:
91
+ """PERF-002: Task별 성능 통계
92
+
93
+ 개별 Task(프로세스/스레드)의 성능 통계를 집계합니다.
94
+
95
+ Attributes:
96
+ task_id: Task 식별자
97
+ task_class: Task 클래스명
98
+ mode: 실행 모드 ('thread' | 'process')
99
+ alive: Task 활성 여부
100
+ methods: 메서드별 성능 데이터
101
+ """
102
+ task_id: str
103
+ task_class: str = ""
104
+ mode: str = "thread"
105
+ alive: bool = False
106
+ methods: Dict[str, MethodPerformance] = field(default_factory=dict)
107
+
108
+ @property
109
+ def total_calls(self) -> int:
110
+ """전체 메서드 호출 수"""
111
+ return sum(m.call_count for m in self.methods.values())
112
+
113
+ @property
114
+ def method_count(self) -> int:
115
+ """등록된 메서드 수"""
116
+ return len(self.methods)
117
+
118
+ @property
119
+ def avg_ipc_time(self) -> float:
120
+ """평균 IPC 시간 (모든 메서드의 윈도우 평균)"""
121
+ all_ipc = []
122
+ for m in self.methods.values():
123
+ all_ipc.extend(m.ipc_window)
124
+ return round(sum(all_ipc) / len(all_ipc), 2) if all_ipc else 0.0
125
+
126
+ @property
127
+ def avg_func_time(self) -> float:
128
+ """평균 FUNC 시간 (모든 메서드의 윈도우 평균)"""
129
+ all_func = []
130
+ for m in self.methods.values():
131
+ all_func.extend(m.func_window)
132
+ return round(sum(all_func) / len(all_func), 2) if all_func else 0.0
133
+
134
+ @property
135
+ def avg_total_time(self) -> float:
136
+ """평균 총 소요 시간 (IPC + FUNC)"""
137
+ return round(self.avg_ipc_time + self.avg_func_time, 2)
138
+
139
+ def to_dict(self) -> Dict[str, Any]:
140
+ """JSON 직렬화용 딕셔너리 변환"""
141
+ return {
142
+ 'task_id': self.task_id,
143
+ 'task_class': self.task_class,
144
+ 'mode': self.mode,
145
+ 'alive': self.alive,
146
+ 'total_calls': self.total_calls,
147
+ 'method_count': self.method_count,
148
+ 'avg_ipc_time': self.avg_ipc_time,
149
+ 'avg_func_time': self.avg_func_time,
150
+ 'avg_total_time': self.avg_total_time,
151
+ 'methods': {k: v.to_dict() for k, v in self.methods.items()}
152
+ }
153
+
154
+
155
+ @dataclass
156
+ class SystemPerformance:
157
+ """PERF-001: 시스템 전체 성능 통계
158
+
159
+ 모든 Task의 성능 데이터를 집계하여 시스템 수준 통계를 제공합니다.
160
+
161
+ Attributes:
162
+ tasks: Task별 성능 데이터
163
+ measurement_enabled: 측정 활성화 여부
164
+ start_time: 측정 시작 시간 (Unix timestamp)
165
+ """
166
+ tasks: Dict[str, TaskPerformance] = field(default_factory=dict)
167
+ measurement_enabled: bool = False
168
+ start_time: float = field(default_factory=time.time)
169
+
170
+ @property
171
+ def total_calls(self) -> int:
172
+ """시스템 전체 RMI 호출 수"""
173
+ return sum(t.total_calls for t in self.tasks.values())
174
+
175
+ @property
176
+ def total_tasks(self) -> int:
177
+ """등록된 Task 수"""
178
+ return len(self.tasks)
179
+
180
+ @property
181
+ def active_tasks(self) -> int:
182
+ """활성 Task 수 (alive=True)"""
183
+ return sum(1 for t in self.tasks.values() if t.alive)
184
+
185
+ @property
186
+ def total_methods(self) -> int:
187
+ """전체 메서드 수"""
188
+ return sum(t.method_count for t in self.tasks.values())
189
+
190
+ @property
191
+ def avg_ipc_time(self) -> float:
192
+ """시스템 평균 IPC 시간"""
193
+ all_ipc = []
194
+ for t in self.tasks.values():
195
+ for m in t.methods.values():
196
+ all_ipc.extend(m.ipc_window)
197
+ return round(sum(all_ipc) / len(all_ipc), 2) if all_ipc else 0.0
198
+
199
+ @property
200
+ def avg_func_time(self) -> float:
201
+ """시스템 평균 FUNC 시간"""
202
+ all_func = []
203
+ for t in self.tasks.values():
204
+ for m in t.methods.values():
205
+ all_func.extend(m.func_window)
206
+ return round(sum(all_func) / len(all_func), 2) if all_func else 0.0
207
+
208
+ @property
209
+ def avg_total_time(self) -> float:
210
+ """시스템 평균 총 소요 시간"""
211
+ return round(self.avg_ipc_time + self.avg_func_time, 2)
212
+
213
+ @property
214
+ def uptime_seconds(self) -> int:
215
+ """측정 시작 후 경과 시간 (초)"""
216
+ return int(time.time() - self.start_time)
217
+
218
+ def to_dict(self) -> Dict[str, Any]:
219
+ """JSON 직렬화용 딕셔너리 변환"""
220
+ return {
221
+ 'total_calls': self.total_calls,
222
+ 'total_tasks': self.total_tasks,
223
+ 'active_tasks': self.active_tasks,
224
+ 'total_methods': self.total_methods,
225
+ 'avg_ipc_time': self.avg_ipc_time,
226
+ 'avg_func_time': self.avg_func_time,
227
+ 'avg_total_time': self.avg_total_time,
228
+ 'uptime_seconds': self.uptime_seconds,
229
+ 'measurement_enabled': self.measurement_enabled,
230
+ 'tasks': {k: v.to_dict() for k, v in self.tasks.items()}
231
+ }
232
+
233
+
234
+ # ═══════════════════════════════════════════════════════════════════════════════
235
+ # TimingRecorder (PERF-003, PERF-007: 기존 로직 이관)
236
+ # ═══════════════════════════════════════════════════════════════════════════════
237
+
238
+ class TimingRecorder:
239
+ """TaskWorker에서 사용하는 로컬 타이밍 기록기
240
+
241
+ 기존 TaskWorker의 _local_methods, _local_timing,
242
+ _record_timing, _flush_* 메서드를 캡슐화합니다.
243
+
244
+ 로컬 캐시를 사용하여 IPC 오버헤드를 줄이고,
245
+ 주기적으로 공유 메모리로 배치 플러시합니다.
246
+
247
+ Thread-safe: 단일 워커 내에서만 사용 (IPC 없음)
248
+
249
+ Attributes:
250
+ WINDOW_SIZE: 타이밍 윈도우 크기 (기본 10)
251
+ FLUSH_INTERVAL: 자동 플러시 간격 (기본 50회)
252
+ """
253
+
254
+ WINDOW_SIZE: int = 10
255
+ FLUSH_INTERVAL: int = 50
256
+
257
+ __slots__ = ('_local_methods', '_local_timing', '_flush_count',
258
+ '_rmi_methods', '_rmi_timing')
259
+
260
+ def __init__(self, rmi_methods, rmi_timing):
261
+ """
262
+ Args:
263
+ rmi_methods: Manager.dict() - 공유 메서드 카운터
264
+ rmi_timing: Manager.dict() - 공유 타이밍 데이터
265
+ """
266
+ self._local_methods: Dict[str, int] = {}
267
+ self._local_timing: Dict[str, Tuple[List[float], List[float]]] = {}
268
+ self._flush_count: int = 0
269
+ self._rmi_methods = rmi_methods
270
+ self._rmi_timing = rmi_timing
271
+
272
+ def record(self, method: str, ipc_ms: float, func_ms: float) -> None:
273
+ """타이밍 데이터 기록 (로컬 캐시)
274
+
275
+ 기존 TaskWorker._record_timing() 이관
276
+
277
+ Args:
278
+ method: 메서드 이름
279
+ ipc_ms: IPC 소요 시간 (ms)
280
+ func_ms: FUNC 소요 시간 (ms)
281
+ """
282
+ # 메서드 호출 수 증가
283
+ self._local_methods[method] = self._local_methods.get(method, 0) + 1
284
+
285
+ # 타이밍 데이터 기록
286
+ if method not in self._local_timing:
287
+ self._local_timing[method] = ([], [])
288
+ ipc_list, func_list = self._local_timing[method]
289
+ ipc_list.append(ipc_ms)
290
+ func_list.append(func_ms)
291
+
292
+ # 로컬 캐시 크기 제한
293
+ if len(ipc_list) > self.WINDOW_SIZE:
294
+ self._local_timing[method] = (
295
+ ipc_list[-self.WINDOW_SIZE:],
296
+ func_list[-self.WINDOW_SIZE:]
297
+ )
298
+
299
+ # 주기적 플러시
300
+ self._flush_count += 1
301
+ if self._flush_count >= self.FLUSH_INTERVAL:
302
+ self.flush()
303
+ self._flush_count = 0
304
+
305
+ def record_count_only(self, method: str) -> None:
306
+ """호출 횟수만 기록 (타이밍 없음)
307
+
308
+ t0가 없는 경우 (one-way 호출 등)에 사용
309
+
310
+ Args:
311
+ method: 메서드 이름
312
+ """
313
+ self._local_methods[method] = self._local_methods.get(method, 0) + 1
314
+ self._flush_count += 1
315
+ if self._flush_count >= self.FLUSH_INTERVAL:
316
+ self.flush()
317
+ self._flush_count = 0
318
+
319
+ def flush(self) -> None:
320
+ """로컬 데이터를 공유 메모리로 플러시
321
+
322
+ 기존 TaskWorker._flush_method_counts(), _flush_timing() 통합
323
+ """
324
+ # 메서드 카운터 플러시
325
+ if self._local_methods:
326
+ for m, cnt in self._local_methods.items():
327
+ self._rmi_methods[m] = self._rmi_methods.get(m, 0) + cnt
328
+ self._local_methods.clear()
329
+
330
+ # 타이밍 데이터 플러시
331
+ if self._local_timing:
332
+ for m, (ipc_list, func_list) in self._local_timing.items():
333
+ existing = self._rmi_timing.get(m)
334
+ if existing:
335
+ ex_ipc, ex_func = existing
336
+ merged_ipc = (list(ex_ipc) + ipc_list)[-self.WINDOW_SIZE:]
337
+ merged_func = (list(ex_func) + func_list)[-self.WINDOW_SIZE:]
338
+ self._rmi_timing[m] = (merged_ipc, merged_func)
339
+ else:
340
+ self._rmi_timing[m] = (
341
+ ipc_list[-self.WINDOW_SIZE:],
342
+ func_list[-self.WINDOW_SIZE:]
343
+ )
344
+ self._local_timing.clear()
345
+
346
+
347
+ # ═══════════════════════════════════════════════════════════════════════════════
348
+ # PerformanceCollector (PERF-001, PERF-002, PERF-006)
349
+ # ═══════════════════════════════════════════════════════════════════════════════
350
+
351
+ class PerformanceCollector:
352
+ """시스템 성능 데이터 수집기
353
+
354
+ TaskManager에서 생성하여 모든 Task의 성능 데이터를 집계합니다.
355
+ TaskMonitor가 이 객체를 통해 API 응답을 생성합니다.
356
+
357
+ 3계층 성능 데이터 접근:
358
+ - 시스템 레벨: get_system_performance()
359
+ - Task 레벨: get_task_performance(task_id)
360
+ - 메서드 레벨: get_method_performance(task_id, method)
361
+
362
+ Process-safe: Manager 객체 기반 공유 데이터 사용
363
+
364
+ Attributes:
365
+ _tasks: TaskManager._tasks 참조
366
+ _workers: TaskManager._workers 참조 (alive 상태 확인용)
367
+ _measurement: 측정 ON/OFF 공유 변수
368
+ _start_time: 수집기 시작 시간
369
+ """
370
+
371
+ __slots__ = ('_tasks', '_workers', '_measurement', '_start_time')
372
+
373
+ def __init__(self, tasks: Dict[str, 'TaskInfo'], workers: dict, measurement):
374
+ """
375
+ Args:
376
+ tasks: TaskManager._tasks 참조
377
+ workers: TaskManager._workers 참조
378
+ measurement: Manager.Value('b', bool) - 측정 ON/OFF
379
+ """
380
+ self._tasks = tasks
381
+ self._workers = workers
382
+ self._measurement = measurement
383
+ self._start_time = time.time()
384
+
385
+ # ─────────────────────────────────────────────────────────────────────────
386
+ # PERF-001: 시스템 레벨 API
387
+ # ─────────────────────────────────────────────────────────────────────────
388
+
389
+ def get_system_performance(self) -> SystemPerformance:
390
+ """시스템 전체 성능 통계 반환
391
+
392
+ Returns:
393
+ SystemPerformance 객체
394
+ """
395
+ sys_perf = SystemPerformance(
396
+ measurement_enabled=self._measurement.value if self._measurement else False,
397
+ start_time=self._start_time
398
+ )
399
+
400
+ for tid in self._tasks.keys():
401
+ task_perf = self.get_task_performance(tid)
402
+ if task_perf:
403
+ sys_perf.tasks[tid] = task_perf
404
+
405
+ return sys_perf
406
+
407
+ # ─────────────────────────────────────────────────────────────────────────
408
+ # PERF-002: Task 레벨 API
409
+ # ─────────────────────────────────────────────────────────────────────────
410
+
411
+ def get_task_performance(self, task_id: str) -> Optional[TaskPerformance]:
412
+ """특정 Task의 성능 통계 반환
413
+
414
+ Args:
415
+ task_id: Task 식별자
416
+
417
+ Returns:
418
+ TaskPerformance 객체 또는 None
419
+ """
420
+ ti = self._tasks.get(task_id)
421
+ if not ti:
422
+ return None
423
+
424
+ # alive 상태 확인
425
+ worker = self._workers.get(task_id) if self._workers else None
426
+ alive = worker.is_alive() if worker else False
427
+
428
+ task_perf = TaskPerformance(
429
+ task_id=task_id,
430
+ task_class=ti.task_class_name,
431
+ mode=ti.mode,
432
+ alive=alive
433
+ )
434
+
435
+ # 메서드별 데이터 수집
436
+ methods = dict(ti.rmi_methods) if ti.rmi_methods else {}
437
+ timing = dict(ti.rmi_timing) if ti.rmi_timing else {}
438
+
439
+ for method_name, count in methods.items():
440
+ method_perf = MethodPerformance(name=method_name, call_count=count)
441
+ t = timing.get(method_name)
442
+ if t:
443
+ ipc_list, func_list = t
444
+ method_perf.ipc_window = list(ipc_list) if ipc_list else []
445
+ method_perf.func_window = list(func_list) if func_list else []
446
+ task_perf.methods[method_name] = method_perf
447
+
448
+ return task_perf
449
+
450
+ def get_all_tasks_performance(self) -> List[TaskPerformance]:
451
+ """모든 Task의 성능 통계 반환
452
+
453
+ Returns:
454
+ TaskPerformance 리스트
455
+ """
456
+ result = []
457
+ for tid in self._tasks.keys():
458
+ perf = self.get_task_performance(tid)
459
+ if perf:
460
+ result.append(perf)
461
+ return result
462
+
463
+ # ─────────────────────────────────────────────────────────────────────────
464
+ # PERF-003: 메서드 레벨 API
465
+ # ─────────────────────────────────────────────────────────────────────────
466
+
467
+ def get_method_performance(self, task_id: str, method_name: str) -> Optional[MethodPerformance]:
468
+ """특정 메서드의 성능 통계 반환
469
+
470
+ Args:
471
+ task_id: Task 식별자
472
+ method_name: 메서드명
473
+
474
+ Returns:
475
+ MethodPerformance 객체 또는 None
476
+ """
477
+ task_perf = self.get_task_performance(task_id)
478
+ if not task_perf:
479
+ return None
480
+ return task_perf.methods.get(method_name)
481
+
482
+ def get_task_methods_performance(self, task_id: str) -> List[MethodPerformance]:
483
+ """특정 Task의 모든 메서드 성능 통계 반환
484
+
485
+ Args:
486
+ task_id: Task 식별자
487
+
488
+ Returns:
489
+ MethodPerformance 리스트 (호출 수 내림차순 정렬)
490
+ """
491
+ task_perf = self.get_task_performance(task_id)
492
+ if not task_perf:
493
+ return []
494
+ # 호출 수 기준 내림차순 정렬
495
+ return sorted(task_perf.methods.values(), key=lambda m: m.call_count, reverse=True)
496
+
497
+ # ─────────────────────────────────────────────────────────────────────────
498
+ # PERF-009, PERF-010: 제어 API
499
+ # ─────────────────────────────────────────────────────────────────────────
500
+
501
+ def set_measurement_enabled(self, enabled: bool) -> None:
502
+ """측정 ON/OFF 설정
503
+
504
+ Args:
505
+ enabled: True=측정 활성화, False=비활성화
506
+ """
507
+ if self._measurement:
508
+ self._measurement.value = enabled
509
+
510
+ def is_measurement_enabled(self) -> bool:
511
+ """측정 활성화 여부 반환"""
512
+ return self._measurement.value if self._measurement else False
513
+
514
+ def toggle_measurement(self) -> bool:
515
+ """측정 ON/OFF 토글
516
+
517
+ Returns:
518
+ 토글 후 상태
519
+ """
520
+ if self._measurement:
521
+ self._measurement.value = not self._measurement.value
522
+ return self._measurement.value
523
+ return False
524
+
525
+ def clear_all(self) -> None:
526
+ """모든 성능 데이터 초기화"""
527
+ for ti in self._tasks.values():
528
+ if ti.rmi_methods:
529
+ ti.rmi_methods.clear()
530
+ if ti.rmi_timing:
531
+ ti.rmi_timing.clear()
532
+ self._start_time = time.time()
533
+
534
+ def clear_task(self, task_id: str) -> bool:
535
+ """특정 Task의 성능 데이터 초기화
536
+
537
+ Args:
538
+ task_id: Task 식별자
539
+
540
+ Returns:
541
+ 성공 여부
542
+ """
543
+ ti = self._tasks.get(task_id)
544
+ if not ti:
545
+ return False
546
+ if ti.rmi_methods:
547
+ ti.rmi_methods.clear()
548
+ if ti.rmi_timing:
549
+ ti.rmi_timing.clear()
550
+ return True