26J29d3

Young-Kyoo Kim·2026년 6월 28일
"""
[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("🔍 [자원 사용현황 심층 분석] 워크로드 × 요일(날짜) 크로스 매트릭스 연산 시작...")

    # 1. 히트맵 전용 피벗 테이블 생성 (가로축: 날짜, 세로축: 워크로드)
    # Core-Hours 기준 진짜 누수 비용 매핑
    pivot_waste = df.groupby(["workload_type", "date"])["cpu_waste_core_hours"].sum().unstack().fillna(0)
    # Core-Hours 기준 진짜 공급 부족(Shortage) 매핑
    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)

    # 2. 2대 자원 매트릭스 히트맵 드로잉
    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")

    # ── 📌 3. 패턴 매트릭스 기반 요일별 '실제 주범 팟(Pod) 저격' 리포팅 연산 ──
    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 1 추출
            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 1 추출
            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()

0개의 댓글