"""
[3-3단계-패턴] 워크로드 도메인 × 일자별 자원 사용현황 패턴 매트릭스 및 히트맵 빌드
실행: python step3_3_pattern_matrix.py
입력: data/merged/enriched_fixed_7d.parquet (2.5단계 시간 보정본)
출력: data/output/plots/chart_p1_waste_heatmap.png (요일별 워크로드 낭비 히트맵)
data/output/plots/chart_p2_shortage_heatmap.png (요일별 워크로드 부족 히트맵)
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
from config import MERGED_DIR, OUT_DIR
def init_plot_style():
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Arial', 'Helvetica', 'DejaVu Sans', 'Liberation Sans']
plt.rcParams['axes.unicode_minus'] = False
sns.set_theme(style="white")
plt.rcParams['figure.dpi'] = 150
def analyze_resource_pattern_matrix():
src_file = MERGED_DIR / "enriched_fixed_7d.parquet"
if not src_file.exists():
print(f"❌ [에러] {src_file.name} 파일이 없습니다. step2_5 보정 스크립트를 먼저 실행해 주세요.")
return
df = pd.read_parquet(src_file)
df['date'] = df['date'].astype(str)
df['workload_type'] = df['workload_type'].str.upper()
print("🔍 [자원 사용현황 심층 분석] 워크로드 × 요일(날짜) 크로스 매트릭스 연산 시작...")
pivot_waste = df.groupby(["workload_type", "date"])["cpu_waste_core_hours"].sum().unstack().fillna(0)
pivot_shortage = df.groupby(["workload_type", "date"])["cpu_shortage_cores"].sum().unstack().fillna(0)
plots_dir = OUT_DIR / "plots"
plots_dir.mkdir(parents=True, exist_ok=True)
draw_pattern_heatmap(pivot_waste, "Waste (Core-Hours)", "YlOrRd", plots_dir / "chart_p1_waste_heatmap.png")
draw_pattern_heatmap(pivot_shortage, "Shortage (Cores)", "Reds", plots_dir / "chart_p2_shortage_heatmap.png")
print("\n" + "="*95)
print("📢 [자원 현황 파악 최종본] 일자별 각 워크로드 최악의 낭비(Waste) 및 부족(Shortage) 팟 저격 리포트")
print("="*95)
unique_dates = sorted(df['date'].unique())
for dt in unique_dates:
print(f"\n📅 [관측 일자: {dt}]")
df_day = df[df['date'] == dt]
for wl in df_day['workload_type'].unique():
df_day_wl = df_day[df_day['workload_type'] == wl]
if df_day_wl.empty: continue
total_w = df_day_wl["cpu_waste_core_hours"].sum()
total_s = df_day_wl["cpu_shortage_cores"].sum()
print(f" ▶ [{wl:<12}] 총 낭비: {total_w:>7,.1f} CHrs | 총 부족: {total_s:>5,.1f} Cores")
top_w_pod = df_day_wl.sort_values(by="cpu_waste_core_hours", ascending=False).iloc[0]
if top_w_pod["cpu_waste_core_hours"] > 0.5:
print(f" ↳ 📉 낭비 주범 팟: NS: {top_w_pod['namespace']} | Pod: {top_w_pod['pod'][:30]}... ({top_w_pod['cpu_waste_core_hours']:.1f} CHrs 누수)")
top_s_pod = df_day_wl.sort_values(by="cpu_shortage_cores", ascending=False).iloc[0]
if top_s_pod["cpu_shortage_cores"] > 0.1:
status_str = "💥 OOMKilled 위험" if top_s_pod.get("is_oom_killed", False) else "⚠️ 스펙부족"
print(f" ↳ 🚨 부족 위험 팟: NS: {top_s_pod['namespace']} | Pod: {top_s_pod['pod'][:30]}... ({top_s_pod['cpu_shortage_cores']:.1f} C 부족 / 상태: {status_str})")
print("\n" + "="*95)
print("🚀 워크로드 × 요일 기반 2차원 패턴 매트릭스 분석 및 시각화가 완료되었습니다.")
def draw_pattern_heatmap(pivot_df, metric_name, cmap_color, save_path):
fig, ax = plt.subplots(figsize=(12, 6))
sns.heatmap(pivot_df, annot=True, fmt=",.1f", cmap=cmap_color, linewidths=0.5, linecolor='gray',
cbar_kws={'label': f'Total {metric_name} Scale'}, ax=ax, annot_kws={"size": 9, "weight": "bold"})
ax.set_title(f'Infrastructure 2-Dimensional Resource {metric_name} Pattern Matrix', pad=20, weight='bold', fontsize=12)
ax.set_xlabel('Observation Dates (Day Timeline)', labelpad=12, weight='bold')
ax.set_ylabel('Workload Technical Domains', labelpad=12, weight='bold')
plt.tight_layout()
fig.savefig(save_path)
plt.close(fig)
if __name__ == "__main__":
init_plot_style()
analyze_resource_pattern_matrix()