17 minute read

CSS

common.css

.indexBody .yarn .slideTitleWrap{width: 1900px;top:45%;}
.indexBody .yarn .slideTitleWrap .chartWrap{height:400px;display:flex;}
.indexBody .yarn .slideTitleWrap .chartWrap .chart{/*border:1px solid #ccc;*/flex:0.33;height:fit-content;background:#FFFFFF;}
.indexBody .yarn .slideTitleWrap .chartWrap .chart{flex-basis: auto;}
.indexBody .yarn .slideTitleWrap .chartWrap .chart:first-child{margin-left:190px;}
.indexBody .yarn .slideTitleWrap .chartWrap .chart:last-child{margin-right:20px;}

.indexBody .yarn .slideTitleWrap .chartWrap>span{position:absolute;white-space:pre-line;font-size:22px;color:#505050;top:75%;padding:5px;border-radius:4px;}
.indexBody .yarn .slideTitleWrap .chartWrap>span#la1{left:20%;transform:translate(-50%,-50%);}
.indexBody .yarn .slideTitleWrap .chartWrap>span#la2{left:52.1%;transform:translate(-50%,-50%);}

.indexBody .yarn .slideMenuWrap{bottom:40px;}
.indexBody .yarn .slideMenuWrap .gridWrap{display:flex;bottom:30px;flex-flow:nowrap;}
.indexBody .yarn .slideMenuWrap .gridWrap .grid>*{font-family:'KBFGTextM';flex-flow:nowrap;}
.indexBody .yarn .slideMenuWrap .gridWrap .grid{/*border:4px solid #ccc;*/height:fit-content;background:#FFFFFF;height:355px;}

.indexBody .yarn .slideMenuWrap .gridWrap .grdPanel1{flex-flow:column;display:flex;flex:0 0 auto;width:1165px;margin-left:190px;}
.indexBody .yarn .slideMenuWrap .gridWrap .grdPanel2{flex-flow:column;display:flex;flex:0 0 auto;width:520px;margin-left:20px;margin-right:20px;}
.indexBody .yarn .slideMenuWrap .gridWrap .grdPanel1>p{text-align:left;margin-bottom:5px;font-weight:bold;}
.indexBody .yarn .slideMenuWrap .gridWrap .grdPanel2>p{text-align:left;margin-bottom:5px;font-weight:bold;}

JSP

main.jsp

<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<head>
<jsp:iclude page="/WEB-INF/jsp/common/toast_ui.jsp" flush="false">
</head>
<body>

<!-- 차트 -->
<div class="slideTitleWrap">
    <div class="chartWrap">
        <div id="pie-chart1" class="chart"></div>   <!-- 파이 차트1 -->
        <div id="pie-chart2" class="chart"></div>   <!-- 파이 차트2 -->
        <div id="line-chart1" class="chart"></div>  <!-- 라인 차트1 -->
        <span id="la1">Total\n값</span>
        <span id="la2">Total\n값</span>
    </div>
</div>

<!-- 그리드 -->
<div class="gridWrap">
    <div class="grdPanel1">
        <p>현재 실행중인 작업</p>
        <div id="grid1" class="grid"></div>
    </div>
    <div class="grdPanel2">
        <p>실행 대기중인 작업</p>
        <div id="grid2" class="grid"></div>
    </div>
</div>
</body>


<script type="text/javascript">
/**********************************************************************
* 공통ajax함수
**********************************************************************/
function fnSyncAjax1(paramMethod, paramUrl, paramJsonData, paramCallbackFunction) {
    var queryStringJsonData = {};

    var ajaxParam = {
        "url": paramUrl,
        "type": paramMethod,
        "dataType": "json",
        "contentType": "application/json; charset=UTF-8", 
        "async": false, 
        "cache": false, 
        "beforeSend": function(xhr) {
            xhr.setReqeustHeader("AJAX", true); //ajax 호출을 header 기록
        }, 
        "success": function(result) {
            paramCallbackFunction(result);
        }, 
        "error": function(request, status, error) {
            if(request.responseJSON === undefined)
            {
                if((request.readyState == 4 || request.readyState == 0) && request.status == 0) {
                    alert("ERR_CONNECTION_REFUSED: "+request.status+", "+error);
                } else {
                    alert("INTERNAL_SERVER_ERROR: "+request.status+", "+error);
                } 
                if(request.status == 401) {
                    location.href = "/";
                }
            }
            else
            {
                alert("예외가 발생했습니다. 관리자 문의");
            }
        }
    };

    //HTTP 메소드가 GET인 경우: json data ==> query string
    //HTTP 메소드가 GET이 아닌경우: json data ==> request body
    if(paramMethod == "GET") {
        queryStringJsonData = paramJsonData;

    } else {
        ajaxParam.data = JSON.stringify(paramJsonData);
    }

    // URL에 query string 추가
    if(!$.isEmptyObject(queryStringJsonData)) {
        ajaxParam.url = ajaxParam.url + "?" $.param(queryStringJsonData);
    }

    //ajax 통신요청
    $.ajax(ajaxParam);

}

/**********************************************************************
* 초기화
**********************************************************************/
var clusterMetrics = ""; //얀리소스 매트릭
var clusterRunning = ""; //얀리소스 러닝
var clusterAccepted = ""; //얀리소스 어셉트

var pieChart1; //파이차트1(클러스터 메모리)
var pieChart2; //파이차트2(클러스터 코어)
var lineChart1; //라인차트1(현재실행중인작업)
var grid1; //그리드1(현재실행중인작업)
var grid2; //그리드2(실행대기중인작업)

/**********************************************************************
* Toast Ui Chart 초기기렌더링을 위한 초기값 셋팅
**********************************************************************/
var paramData = {};
fnSyncAjax("POST", "/yarn/getYarnResource", paramData, function(result) {
    clusterMetrics = result[0].metrics[0];
    clusterRunning = result[1].running;
    clusterAccepted = result[2].accepted;
    console.log(clusterMetrics);
    console.log(clusterRunning);
    console.log(clusterAccepted);
});

/**********************************************************************
* Toast Ui Grid, Chart
**********************************************************************/

$(document).ready(function() {


    $("#la1").text("Total\n" + bytesToGiga(clusterMetrics.totalMB) + " GB");
    $("#la2").text("Total\n" + clusterMetrics.totalVirtualCores);

    // Toast UI Create
    pieChart1 = fnCreatePieChart1(Chart, clusterMetrics, clusterRunning); //파이차트1(클러스터 메모리)
    pieChart2 = fnCreatePieChart2(Chart, clusterMetrics, clusterRunning); //파이차트2(클러스터 코어)
    lineChart1 = fnCreateLineChart1(Chart, clusterMetrics); //라인차트1(현재실행중인작업)
    grid1 = fnCreateGrid1(Grid, clusterRunning); //그리드1(현재실행중인작업)
    grid2 = fnCreateGrid2(Grid, clusterAccepted); //그리드2(실행대기중인작업)

    // 실시간데이터
    setInterval(function() {
        var paramData = {};
        fnSyncAjax1("POST", "/yarn/getYarnResource", paramData, function(result) {
            /* $.each(result, function(idx, data) {....} )*/
           
            // STEP1. GET DATA
            clusterMetrics = result[0].metrics[0];
            clusterRunning = result[1].running;
            clusterAccepted = result[2].accepted;

            // STEP2. DATA 가공 및 검증
            let resultData1 = fnPcssDataPieChart1(clusterMetrics, clusterRunning);
            let resultData2 = fnPcssDataPieChart2(clusterMetrics, clusterRunning);
            let resultData3 = fnPcssDataLineChart1(clusterMetrics);
            let resultData4 = fnPcssDataGrid1(clusterRunning);
            let resultData5 = fnPcssDataGrid2(clusterAccepted);

            // STEP3. SET DATA
            pieChart1.setData(resultData1, 'memory-mb-usages');
            pieChart2.setData(resultData2, 'vcores-usages');
            lineChart1.addData(resultData3, lineChart1.store.state.categories[4]+1);
            grid1.resetData(resultData4);
            grid2.resetData(resultData5);

            // 파이차트 컬러 커스텀
            fnPieUpdateOption(pieChart1, clusterRunning);
            fnPieUpdateOption(pieChart2, clusterRunning);

            $("#la1").text("Total\n" + bytesToGiga(clusterMetrics.totalMB) + " GB");
            $("#la2").text("Total\n" + clusterMetrics.totalVirtualCores);
        });

    }, 10000);
});

</script>

toast_ui.jsp

<%-- toastUI Chart--%>
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/toastui-chart.min.css" media="screen" />
<script src="${pageContext.request.contextPath}/resources/js/toastui-chart.min.js"></script>

<%-- toastUI Grid --%>
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/tui-grid.min.css" media="screen" />
<script src="${pageContext.request.contextPath}/resources/js/tui-grid.min.js"></script>

<script>
/**********************************************************************
* TOAST UI CHART
**********************************************************************/
let Chart = new toastui.Chart;

/**********************************************************************
* TOAST UI Grid
**********************************************************************/
let Grid = tui.Grid;
var extOptions = {
    selection: {
        background: '#4daaf9', 
        border: '#004082'
    }, 
    row: {
        even: {
            background: '#eaeaea'
        },
        hover: {
            background: '#ccc'
        }
    }, 
    cell: {
        normal: {
            background: '#fbfbfb', 
            border: '#e0e0e0', 
            showVerticalBorder: true
        }, 
        header: {
            background: '#eee', 
            border: '#ccc', 
            showVerticalBorder: true
        }, 
        rowheader: {
            background: '#ccc', 
            showVerticalBorder: true
        }, 
        editable: {
            background: '#fbfbfb'
        },
        selectedHeader: {
            background: '#d8d8d8'
        }, 
        focused: {
            border: '#418ed4'
        }, 
        disabled: {
            text: '#b0b0b0'
        }, 
        eventRow: {
            background: '#fee'
        }
    },
    theme{ noData: { fontSize: 30, fontFamily: 'Verdana', fontWeight: 'bold', color: '#3ee' } }
};

Grid.applyTheme('default', extOptions);

/**********************************************************************
* Pie 차트1
**********************************************************************/
function fnCreatePieChart1(Chart, metrics, running) {

    let dynamicColors = [];
    dynamicColors = fnGetColor(running);

    const el = document.getElementById('pie-chart1');
    const data = fnPcssDataPieChart1(metrics, running);
    const options = {
        chart: { 
            title: '클러스터 메모리', width: 525, height: 375, background: {color: 'rgba(0,0,0,0)'}, 
            animation: {duration: 500}, align: 'center'
        },
        legend: { visible: true, showCheckbox: false }, 
        exportMenu: { visible: false }, 
        series:{
            dataLabels: {
                visible: true 
            }, 
            radiusRange: {
                inner: 70, 
                outer: 120
            }, 
            selectable: true 
        }, 
        theme: {
            title: {
                fontSize: 35, 
                fontWeight: 600
            },
            series: {
                //colors: ['#F15F5F', '#93CC8D'], 
                colors: dynamicColors, 
                lineWidth: 2, 
                strokeStyle: '#000000', 
                dataLabels: {
                    useSeriesColor: false, 
                    lineWidth: 2, 
                    fontSize: 18, 
                    color: '#404040', 
                    textStrokeColor: '#ffffff', 
                    shadowColor: '#ffffff', 
                    shadowBlur: 4 
                }
            }, 
            chart: {
                fontFamily: 'KBFGTextM'
                //backgroundColor: 'rgba(0,0,0,0)'
            }, 
            legend: {
                visible: true, 
                label: {
                    fontSize: 14, 
                    fontWeight: 500
                }
            }
        }
    };
    
    return Chart.constructor.pieChart({ el, data, options });
}

/**********************************************************************
* Pie 차트2 (vcores-usages : 클러스터코어)
**********************************************************************/
function fnCreatePieChart2(Chart, metrics, running) {

    let dynamicColors = [];
    dynamicColors = fnGetColor(running);

    const el = document.getElementById('pie-chart2');
    const data = fnPcssDataPieChart2(metrics, running);
    const options = {
        chart: { 
            title: '클러스터 코어', width: 525, height: 375, align: 'center'
        },
        legend: { visible: true, showCheckbox: false }, 
        exportMenu: { visible: false }, 
        series:{
            dataLabels: {
                visible: true 
            }, 
            radiusRange: {
                inner: 70, 
                outer: 120
            }, 
            selectable: true 
        }, 
        theme: {
            title: {
                fontSize: 35, 
                fontWeight: 600
            },
            series: {
                //colors: ['#F15F5F', '#93CC8D'], 
                colors: dynamicColors, 
                lineWidth: 2, 
                strokeStyle: '#000000', 
                dataLabels: {
                    useSeriesColor: false, 
                    lineWidth: 2, 
                    fontSize: 18, 
                    color: '#404040', 
                    textStrokeColor: '#ffffff', 
                    shadowColor: '#ffffff', 
                    shadowBlur: 4 
                }
            }, 
            chart: {
                fontFamily: 'KBFGTextM'
                //backgroundColor: 'rgba(0,0,0,0)'
            }, 
            legend: {
                visible: true, 
                label: {
                    fontSize: 14, 
                    fontWeight: 500
                }
            }
        }
    };
    
    return Chart.constructor.pieChart({ el, data, options });
}

/**********************************************************************
* line 차트1 (Running-Apps-From-All-Users)
**********************************************************************/
function fnCreateLineChart1(Chart, metrics) {
    const el = document.getElementById('line-chart1');
    let data = fnPcssDataLineChart1(metrics);

    data = {
        categories: [0, 1, 2, 3, 4], 
        series: [{name: 'Running', data: [data[0], data[0], data[0], data[0]]}
                {name: 'Pending', data: [data[1], data[1], data[1], data[1]]}]
    };
    const options = {
        chart: { title: '실시간 작업 상태', width: 370, height: 350, align: 'center' },
        legend: { visible: true, showCheckbox: false }, 
        exportMenu: { visible: false }, 
        xAxis: { title: '모니터링 시간 (1 = 10sec)' }, 
        yAxis: { title: '작업' }, 
        series:{
            shift: true, 
            dataLabels: {
                visible: true,  //데이터라벨표시
                offsetY: -10
            }, 
            selectable: true,  //차트클릭효과 
            spline: true //부드러운곡선
        }, 
        theme: {
            title: {
                fontSize: 35, 
                fontWeight: 600
            },
            lineWidth: 20, 
            legend: {
                visible: true,
                label: {
                    fontSize: 14, 
                    fontWeight: 500
                }
            }, 
            series: {
                colors: ['#F15F5F', '#93CC8D'], 
                dataLabels: {
                    fontSize: 13, 
                    fontWeight: 300, 
                    lineWidth: 10, 
                    useSeriesColor: true, 
                    textBubble: {
                        visible: true, 
                        paddingY: 3, 
                        paddingX: 6, 
                        arrow: {
                            visible: true, 
                            width: 5, 
                            height: 5, 
                            direction: 'bottom'
                        }
                    }
                }
            }
        }
    };
    return Chart.constructor.lineChart({ el, data, options });
}


/**********************************************************************
* Grid1 (실행중인작업)
**********************************************************************/
function fnCreateGrid1(Grid, running) {
    return new Grid({
        el: document.getElementById('grid1');
        rowHeaders: [
            {
                type: 'rowNum', 
                header: 'No'
            }
        ], 
        columns: [
            {
                header: '사용자ID', 
                name: 'user', 
                width: 170
            }, 
            {
                header: '할당된 코어', 
                name: 'allocatedVCores', 
                width: 110
            }, 
            {
                header: '할당된 메모리', 
                name: 'allocatedMB', 
                width: 140
            }, 
            {
                header: '작업 시작 요청 시간', 
                name: 'startedTime', 
                width: 230
            }, 
            /*
            {
                header: 'User', 
                name: 'user', 
                width: 230
            },
            */ 
            {
                header: '실제 시작 시간', 
                name: 'launchTime', 
                width: 230
            }, 
            {
                header: '작업 소요 시간', 
                name: 'elapsedTime', 
                width: 230
            }, 
            /*
            {
                header: 'Queue', 
                name: 'queue', 
                width: 110
            }, 
            {
                header: 'QueueUsagePercentage', 
                name: 'queueUsagePercentage', 
                width: 190
            }, 
            {
                header: 'ClusterUsagePercentage', 
                name: 'clusterUsagePercentage', 
                width: 206
            }
            */
        ], 
        data: fnPcssDataGrid1(running), 
        scrollX: false, 
        scrollY: true, 
        bodyHeight: 335 
    });
}

/**********************************************************************
* Grid2 (수락된 작업)
**********************************************************************/
function fnCreateGrid2(Grid, clusterAccepted) {
    return new Grid({
        el: document.getElementById('grid2');
        rowHeaders: [
            {
                type: 'rowNum', 
                header: 'No'
            }
        ], 
        columns: [
            {
                header: '사용자ID', 
                name: 'user', 
                width: 180
            }, 
            {
                header: '작업 시작 요청 시간', 
                name: 'startedTime', 
                width: 295
            }
        ], 
        data: fnPcssDataGrid2(clusterAccepted), 
        scrollX: false, 
        scrollY: true, 
        bodyHeight: 335 

    });
}

/**********************************************************************
* TOAST UI DATA SET
**********************************************************************/
//클러스터 메모리
function fnPcssDataPieChart1(metrics, running) {

    let series = [];
    let availableMBPercent = fnRound(((metrics.availableMB / metrics.totalMB) * 100), 2); //현재여유메모리(%)
    let allocatedMBPercent; //현재사용메모리
    let userNm; //사용자

    if(running) {
        let arrAllocatedMB = [];
        for(var i=0; i<running.length; i++) {
            arrAllocatedMB.push(running[i].clusterUsagePercentage); //러닝중인 각각의 사용중인 메모리 arr생성
        }

        arrAllocatedMB.sort((a, b) => b - a); //정렬

        for(var i=0; i<running.length; i++) {
            userNm = running[i].user;
            allocatedMBPercent = fnRound(arrAllocatedMB[i], 2);
            series.push({ name: userNm + '= ' + allocatedMBPercent + '₩%', data: availableMBPercent });
        }
        series.push({name: '사용가능 메모리 = '+ availableMBpercent + '% (' + bytesToGiga(metrics.availableMB) + ' GB)', data: availableMBpercent });
    }
    
    let resultData = 
        !metrics || !running ?
        {
            categories: ['memory-mb-usages'], 
            series: [{ name: 'Used = 0 GB', data: 0.0 } 
                    , { name: '사용가능 메모리 = ' + availableMBPercent + '% (' + bytesToGiga(metrics.availableMB) +'GB)', data: availableMBPercent }]
        }
        :
        {
            categories: ['memory-mb-usages']
            , series
        };

    return resultData;
    
}

//클러스터 코어
function fnPcssDataPieChart2(metrics, running) {

    let series = [];
    let availableVCoresPercent = fnRound(((metrics.availableVirtualCores / metrics.totalVirtualCores) * 100), 2); //현재여유코어(%)
    let allocatedVCorePercent; //현재사용자별 사용코어(%)
    let userNm; //사용자

    if(running) {
        let arrAllocatedVCores = [];
        for(var i=0; i<running.length; i++) {
            arrAllocatedVCores.push( (running[i].allocatedVCores / metrics[i].totalVirtualCores ) * 100); //러닝중인 각각의 사용중인 코어의 점유율 arr생성
        }

        arrAllocatedVCores.sort((a, b) => b - a); //정렬

        for(var i=0; i<running.length; i++) {
            userNm = running[i].user;
            allocatedVCorePercent = fnRound(arrAllocatedVCores[i], 2);
            series.push({ name: userNm + '= ' + allocatedVCorePercent + '\%', data: allocatedVCorePercent }); //각각의 사용자별 코어 점유율 출력
        }
        series.push({ name: '사용가능 코어 = ' + availableVCoresPercent + '% (' + metrics.availableVirtualCores + ')', data: availableVCoresPercent }); //여유 코어 출력
    }

    let resultData = 
        !metrics || !running ?
        {
            categories: ['vcores-usages'], 
            series: [{ name: 'Used = 0', data: 0.0 } 
                    , { name: '사용가능 코어 = ' + availableVCoresPercent + '% (' + metrics.availableVirtualCores + ')', data: availableVCoresPercent }]
        }
        :
        {
            categories: ['vcores-usages'], 
            , series
        };

    return resultData;    
}

//실시간 작업 상태
function fnPcssDataLineChart1(metrics) {
    let resultData = [];
    if(metrics) {
        resultData.push(metrics.appsRunning);
        resultData.push(metrics.appsPending);
    }
    return resultData;
}

//현재 실행중인 작업
function fnPcssDataGrid1(running) {
    let resultData = [];
    if (running) {
        for(var i=0; i<running.length; i++) {
            resultData.push({
                user: running[i].user, 
                allocatedVCores: running[i].allocatedVCores, 
                allocatedMB: running[i].allocatedMB, 
                startedTime: running[i].startedTime, 
                launchTime: running[i].launchTime, 
                elapsedTime: running[i].elapsedTime, 
                //queue: running[i].queue, 
                //queueUsagePercentage: running[i].queueUsagePercentage, 
                //clusterUsagePercentage: running[i].clusterUsagePercentage
            });
        }
    }
    return resultData;
}

//현재 실행중인 작업
function fnPcssDataGrid2(accepted) {
    let resultData = [];
    if (accepted) {
        for(var i=0; i<accepted.length; i++) {
            resultData.push({
                user: accepted[i].user, 
                startedTime: accepted[i].startedTime
            });
        }
    }
    return resultData;
}

/**********************************************************************
* Pie 차트 옵션 업데이트
**********************************************************************/
function fnPieUpdateOption(chart, running) {

    let dynamicColors = [];
    dynamicColors = fnGetColor(running);

    //전체옵션변경
    const chartId = chart.containerEl.id;
    let chartOption;
    if(chartId == 'pie-chart1') chartOption = { title: '클러스터 메모리', width: 525, height: 375, background: {color: 'rgba(0,0,0,0)'}, animation: {duration: 500}, align: 'center' };
    if(chartId == 'pie-chart2') chartOption = { title: '클러스터 코어', width: 525, height: 375, background: {color: 'rgba(0,0,0,0)'}, animation: {duration: 500}, align: 'center' }
    
    chart.setOptions({
        chart: chartOption, 
        legend: { visible: true, showCheckbox: false }, 
        exportMenu: { visible: false }, 
        series:{
            dataLabels: {
                visible: true 
            }, 
            radiusRange: {
                inner: 70, 
                outer: 120
            }, 
            selectable: true 
        }, 
        theme: {
            title: {
                fontSize: 35, 
                fontWeight: 600
            },
            series: {
                //colors: ['#F15F5F', '#93CC8D'], 
                colors: dynamicColors, 
                lineWidth: 2, 
                strokeStyle: '#000000', 
                dataLabels: {
                    fontSize: 18, 
                    useSeriesColor: false, 
                    lineWidth: 2, 
                    color: '#404040', 
                    textStrokeColor: '#ffffff', 
                    shadowColor: '#ffffff', 
                    shadowBlur: 4 
                }
            }, 
            chart: {
                fontFamily: 'KBFGTextM'
                //backgroundColor: 'rgba(0,0,0,0)'
            }, 
            legend: {
                visible: true, 
                label: {
                    fontSize: 14, 
                    fontWeight: 500
                }
            }
        }
    });

}

//파이차트 동적 색상 처리
function fnGetColor(running) {
    let arrColors = [];
    const lastColors = '#93CC8D'; //green
    const defaultColors = ['#FF7777', '#4374D9', '#FFE400', '#5F00FF', '#FF5E00'
                            , '#D2F4CA', '#BDDDF2', '#FAF4C0', '#A6A6A6', '#7052F9'
                            , '#4C8CF8', '#E8FF3C', '#A3FD3C', '#FC3B86', '#ED9DFB'];

    if(running == null) {
        arrColors.push('#FF7777');
        arrColors.push(lastColors);
        return arrColors;
    }

    for(var i=0; i<running.length; i++) {
        arrColors.push(defaultColors[i]);
    }

    arrColors[arrColors.length] = lastColors;

    return arrColors;
}

//number to TwoDecimalPlaces DATA
function fnRound(number, decimalPlaces) {
    return parseFloat(number, toFixed(decimalPlaces));
}

//bytes To gigabaytes DATA
function bytesToGiga(param) {
    const bytes = param;
    const gigabytes = bytes / 1024;
    return gigabytes.toFixed(1);
}
</script>

JAVA

YarnResourceController.java

package com.blang.bck;
/* 중략 */
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;

@Controller
public class YarnResourceController {

    private static final Logger logger = LoggerFactory.getLogger(YarnResourceController.class);

    @Autowired
    private YarnResourceService yarnResourceService;

    /*
    @Value("#{globalProperties['yarn.cluster.domain1']}")
    private String YARN_RESOURCE_DOMAIN1;
    @Value("#{globalProperties['yarn.cluster.domain2']}")
    private String YARN_RESOURCE_DOMAIN2;
    @Value("#{globalProperties['yarn.cluster.info']}")
    private String YARN_RESOURCE_INFO;
    @Value("#{globalProperties['yarn.cluster.metrics']}")
    private String YARN_RESOURCE_METRICS;
    @Value("#{globalProperties['yarn.cluster.running']}")
    private String YARN_RESOURCE_RUNNING;
    @Value("#{globalProperties['yarn.cluster.accepted']}")
    private String YARN_RESOURCE_ACCEPTED;
    */
    
    private static final String YARN_RESOURCE_DOMAIN1 = "http://cdbgmso1.blang.com:8088";
    private static final String YARN_RESOURCE_DOMAIN2 = "http://cdbgmso2.blang.com:8088";
    private static final String YARN_RESOURCE_INFO = "/ws/v1/cluster/info";
    private static final String YARN_RESOURCE_METRICS = "/ws/v1/cluster/metrics";
    private static final String YARN_RESOURCE_RUNNING = "/ws/v1/cluster/apps?states=RUNNING";
    private static final String YARN_RESOURCE_ACCEPTED = "/ws/v1/cluster/apps?states=ACCEPTED";

    @RequestMapping(value = "/yarn", method = RequestMethod.GET)
    public String yarn() {
        Logger.info("YarnResourceController Yarn start >>");
        return "jsp/web/DBD99999M00";
    }


    @ResponseBody
    @RequestMapping(value = "/getYarnResource", method = RequestMethod.POST)
    public String getYarnResource(@RequestBody Map<String, Object> params, HttpServletRequest req) 
    {
        Logger.info("YarnResourceController getYarnResource start >>");

        List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
        Map<String, Object> resultMap = new HashMap<String, Object>();

        try {
            String YARN_RESOURCE_ACTIVE_DOMAIN = "";
            String activeServer = "";
            String responseData = "";
            String responseData1 = "";
            String responseData2 = "";

            // STEP1. Active 서버 체크
            String ipAddress = InetAddress.getLocalHost().getHostAddress();
            Logger.debug("ipAddress: " + ipAddress);

            if(!ipAddress.contains("10.213"))
            {
                responseData1 = connectCaptureGET(YARN_RESOURCE_DOMAIN1+YARN_RESOURCE_INFO); //서버1 
                responseData2 = connectCaptureGET(YARN_RESOURCE_DOMAIN2+YARN_RESOURCE_INFO); //서버2 
                if(responseData1 != null && responseData1.contains("ACTIVE")) activeServer = YARN_RESOURCE_DOMAIN1;
                if(responseData2 != null && responseData2.contains("ACTIVE")) activeServer = YARN_RESOURCE_DOMAIN2;
            }
            else //dev
            {
                responseData1 = connectGET(YARN_RESOURCE_DOMAIN1+YARN_RESOURCE_INFO); //서버1
                if(responseData1 != null && responseData1.contains("ACTIVE")) activeServer = YARN_RESOURCE_DOMAIN1;
            }

            YARN_RESOURCE_ACTIVE_DOMAIN = activeServer;
            Logger.debug("활성화된 서버: " + YARN_RESOURCE_ACTIVE_DOMAIN);

            // Yarn Resource Manager REST API (Response JSON ROOT key 이다.)
            final String[] yarnResource = {"clusterMetrics", "running", "accepted"};
            for(int i=0; i<yarnResource.length; i++) {

                switch (yarnResource[i]) {
                    case "clusterMetrics": 
                        responseData = connectGET(YARN_RESOURCE_ACTIVE_DOMAIN + YARN_RESOURCE_METRICS);
                        resultMap = yarnResourceService.getMetricsMap(responseData);
                        break;

                    case "running": 
                        responseData = connectGET(YARN_RESOURCE_ACTIVE_DOMAIN + YARN_RESOURCE_RUNNING);
                        resultMap = yarnResourceService.getRunningMap(responseData);
                        break;
                    
                    case "accepted": 
                        responseData = connectGET(YARN_RESOURCE_ACTIVE_DOMAIN + YARN_RESOURCE_ACCEPTED);
                        resultMap = yarnResourceService.getAcceptedMap(responseData);
                        break;
                }

                Logger.debug(yarnResource[i] + "=> GET " + i + "번째 리스폰스: " + responseData);
                Logger.debug(yarnResource[i] + "=> GET " + i + "번째 리스폰스 가공된 맵: " + resultMap);
                Logger.debug("__________________________________________________________________");

                resultList.add(resultMap);
            }


        } catch (Exception e) {
            e.printStackTrace();
        }

        return "jsp/web/DBD99999M00";
    }


    /*********************
	 * GET 요청 후 응답결과를 String 으로 받는다.
	 * 
	 * @param 요청 URL주소
	 * @return String
	 * @throws IOException 
	 * @throws ClientProtocolException
	 *********************/
	public static String connectGET(String containParamURL) throws ClientProtocolException, IOException 
	{
	  BufferedReader br = null;
	  StringBuilder builder = new StringBuilder();
	  HttpClient httpClient = HttpClients.createDefault();
	  HttpGet get = null;

	  try
	  {
	    get = new HttpGet(containParamURL);

	    HttpResponse response = httpClient.execute(get);
	    int status = response.getStatusLine().getStatusCode();

	    if(status >= 200 && status < 300) 
	    {
	      HttpEntity entity = response.getEntity();

	      br = new BufferedReader(new InputStreamReader(entity.getContent()));
	      String line = null;
	      
	      while((line = br.readLine()) != null) {
	        builder.append(line);
	      }
	    }
	  }
	  finally
	  {
	    if(br != null) {
	      br.close();
	    }
	    if(get != null) {
	      get.releaseConnection();
	    }
	  }

	  return builder.toString();
	}

    
    /*********************
	 * GET 요청 후 타서버 접속하여 응답결과를 String 으로 받는다.
	 * 타겟 서버측 내부에서 로드밸런싱 등의 리다이렉트 처리가 일어나면
     * 중간에서 응답을 캡처한다.
     * 
	 * @param 요청 URL주소
	 * @return String
	 * @throws IOException 
	 * @throws ClientProtocolException
	 *********************/
	public static String connectCaptureGET(String containParamURL) throws ClientProtocolException, IOException 
	{
        final boolean[] isRedirect = {false};
	    StringBuilder builder = new StringBuilder();
        HttpGet get = null;

        // Yarn Resource 서버측 로드밸런싱으로 인한 HTTP 응답 인터셉터 캡처 구현
        HttpResponseInterceptor responseInterceptor = new HttpResponseInterceptor() {
            @Override
            public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 307) isRedirect[0] = true; //리다이렉트 캡처
        }};

        //HttpClient 생성 및 응답 인터셉터 등록
        CloseableHttpClient httpClient = HttpClient.custom();
                .addInterceptorLast(responseInterceptor);
                .build();
	    try
	    {
	        get = new HttpGet(containParamURL);

	        HttpResponse response = httpClient.execute(get);
	        int status = response.getStatusLine().getStatusCode();

	        if(status >= 200 && status < 300) 
	        {
                if(isRedirect[0]) return null; //307 로드밸런싱시 return
	            HttpEntity entity = response.getEntity();

                // try-with-resources 자원해제
                try (BufferedReader br = new BufferedReader(new InputStreamReader(entitiy.getContent()));) {
                    String line = null;
                    while((line = br.readLine()) != null) {
	                    builder.append(line);
	                }
                }	      
	        }
	    }
	    finally
        {
            try {
                httpClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
	    return builder.toString();
	}


}

YarnResourceService.java

import java.lang.reflext.Type;
import java.lang.reflext.TypeToken;
import com.google.gson.Gson;
import com.blang.bck.been.yarnresource.YarnResourceAcceptedDTO;
import com.blang.bck.been.yarnresource.YarnResourceMetricsDTO;
import com.blang.bck.been.yarnresource.YarnResourceRunningDTO;

/**
 *  GSON 2.10.1 Version Using
 */

@Service
public class YarnResourceService
{
    /**
     * Metrics 리소스
     * gson 라이브러리 json to map 
     * @param params
     * @return
     */
    public Map<String, Object> getMetricsMap(String params) {
        
        System.out.println("YarnResourceService getMetricsMap start >>>");

        Map<String, Object> resultMap = new Map<String, Object>();
        List<Map<String, Object>> inputList = new ArrayList<Map<String, Object>>();

        try {
            Gson gson = new Gson();
            //java.lang.reflect.Type gsonType = (Type) new com.google.gson.reflect.TypeToken<Map<String, Object>>(){}.getType();
            Type gsonType = (Type) new TypeToken<HashMap<String, Object>>(){}.getType();
            
            YarnResourceMetricsDTO clusterMetricsDTO = gson.fromJson(params, YarnResourceMetricsDTO.class);
            YarnResourceMetricsDTO.ClusterMetricsData metricsData = clusterMetricsDTO.getClusterMetrics();

            if(metricsData != null) {
                inputList.add(new Gson().fromJson(gson.toJson(metricsData), gsonType));
                resultMap.put("metrics", inputList);
                System.out.println("resultMap: " + resultMap);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }

        return resultMap;
    }

    /**
     * Running 리소스
     * gson 라이브러리 json to map 
     * @param params
     * @return
     */
    public Map<String, Object> getRunningMap(String params) {
        
        System.out.println("YarnResourceService getRunningMap start >>>");

        Map<String, Object> resultMap = new Map<String, Object>();
        List<Map<String, Object>> inputList = new ArrayList<Map<String, Object>>();
        
        try {
            Gson gson = new Gson();
            //java.lang.reflect.Type gsonType = (Type) new com.google.gson.reflect.TypeToken<Map<String, Object>>(){}.getType();
            Type gsonType = (Type) new TypeToken<HashMap<String, Object>>(){}.getType();
            Date date;
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

            YarnResourceRunningDTO runningDTO = gson.fromJson(params, YarnResourceRunningDTO.class);
            YarnResourceRunningDTO.Apps apps = runningDTO.getApps();
            if(apps != null) {

                List<YarnResourceRunningDTO.App> appList = apps.getApp();

                if(appList != null && !appList.isEmpty()) {
                
                    int idx = 0;
                    for(YarnResourceRunningDTO.App app : appList) {
                        
                        inputList.add(new Gson().fromJson(gson.toJson(app), gsonType)); // 리스트맵 간단하게 완성!

                        // 완성된 리스트맵에서 특정 키를 수정하고 싶은 부분 진행
                        // user 이라는 키값을 수정
                        String strUser = inputList.get(idx).get("user").toString();
                        if (strUser.equals("hive")) {
                            strUser = app.getApplicationTags().subString(app.getApplicationTags().indexOf("=") + 1); 
                            inputList.get(idx).put("user", strUser);
                        }

                        // startedTime 이라는 키값을 수정
                        String strResult = "";
                        long lTime = 0, lMTime = 0, lSTime = 0;
                        
                        lTime = appList.get(idx).getStartedTime();
                        date = new Date(lTime);
                        strResult = formatter.format(date);
                        inputList.get(idx).put("startedTime", strResult);

                        // launchTime 이라는 키값을 수정
                        lTime = appList.get(idx).getLaunchTime();
                        date = new Date(lTime);
                        strResult = formatter.format(date);
                        inputList.get(idx).put("launchTime", strResult);

                        // elapsedTime 이라는 키값을 수정 (경과시관 관련한 유닉스 시간을 분초로 변환)
                        lTime = appList.get(idx).getElapsedTime();
                        lSTime = lTime / 1000;
                        lMTime = lSTime / 60;
                        lSTime %= 60;
                        strResult = lMTime + "분 " + lSTime + "초" + "경과";
                        inputList.get(idx).put("launchTime", strResult);

                        idx++;
                    }
                    resultMap.put("running", inputList);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }

        return resultMap;
    }

    /**
     * Accepted 리소스
     * gson 라이브러리 json to map 
     * @param params
     * @return
     */
    public Map<String, Object> getAceeptedMap(String params) {

        System.out.println("YarnResourceService getAceeptedMap start >>>");

        Map<String, Object> resultMap = new Map<String, Object>();
        List<Map<String, Object>> inputList = new ArrayList<Map<String, Object>>();
        
        try {
            Gson gson = new Gson();
            //java.lang.reflect.Type gsonType = (Type) new com.google.gson.reflect.TypeToken<Map<String, Object>>(){}.getType();
            Type gsonType = (Type) new TypeToken<HashMap<String, Object>>(){}.getType();
            Date date;
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

            YarnResourceAcceptedDTO acceptedDTO = gson.fromJson(params, YarnResourceAcceptedDTO.class);
            YarnResourceAcceptedDTO.Apps apps = acceptedDTO.getApps();
            if(apps != null) {

                List<YarnResourceAcceptedDTO.App> appList = apps.getApp();

                if(appList != null && !appList.isEmpty()) {
                
                    int idx = 0;
                    for(YarnResourceAcceptedDTO.App app : appList) {
                        
                        inputList.add(new Gson().fromJson(gson.toJson(app), gsonType)); // 리스트맵 간단하게 완성!

                        //user 구하기
                        String strUser = inputList.get(idx).get("user").toString();
                        if(strUser.equals("hive")) {
                            strUser = app.getApplicationTags().substring(app.getApplicationTags().indexOf("=") + 1);
                            inputList.get(idx).put("user", strUser);
                        }

                        //startedTime 구하기
                        String strResult = "";
                        long lTime = 0;
                        
                        lTime = appList.get(idx).getStartedTime();
                        date = new Date(lTime);
                        strResult = formatter.format(date);
                        inputList.get(idx).put("startedTime", strResult);

                        idx++;
                    }
                    resultMap.put("accepted", inputList);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }

        return resultMap;
    }
}


YarnResourceAcceptedDTO.java

package com.blang.bck.been.yarnresource;

/**
 * Yarn Resource Manager Accepted DTO
 * @author MT01301
 */
public class YarnResourceAcceptedDTO {
  private Apps apps;

  public Apps getApps() {
    return apps;
  }

  public static class Apps {
    private List<App> app;

    public List<App> getApp() {
      return app;
    }
  }
  
  public static class App {
    private String user;
    private String applicationTags;
    private long startedTime;
    
    public String getUser { return user; }
    public String getApplicationTags; { return applicationTags; }
    public long getStartedTime; { return startedTime; }
  }
}

YarnResourceMetricsDTO.java

package com.blang.bck.been.yarnresource;

/**
 * Yarn Resource Manager Metrics DTO
 * @author MT01301
 */
public class YarnResourceMetricsDTO {
  private ClusterMetricsData clusterMetrics;

  public ClusterMetricsData getClusterMetrics() {
    return clusterMetrics;
  }

  public static class ClusterMetricsData {
    private int appsRunning;
    private int appsPending;
    private long allocatedVirtualCores;
    private long availableVirtualCores;
    private long totalVirtualCores;
    private long allocatedMB;
    private long availableMB;
    private long totalMB;
    public int getAppsRunning { return appsRunning; }
    public int getAppsPending { return appsPending; }
    public long getAllocatedVirtualCores { return allocatedVirtualCores; }
    public long getAvailableVirtualCores { return availableVirtualCores; }
    public long getTotalVirtualCores { return totalVirtualCores; }
    public long getAllocatedMB { return allocatedMB; }
    public long getAvailableMB { return availableMB; }
    public long getTotalMB { return totalMB; }    
  }
}

YarnResourceRunningDTO.java

package com.blang.bck.been.yarnresource;

/**
 * Yarn Resource Manager Running DTO
 * @author MT01301
 */
public class YarnResourceRunningDTO {
  private Apps apps;

  public Apps getApps() {
    return apps;
  }

  public static class Apps {
    private List<App> app;

    public List<App> getApp() {
      return app;
    }
  }
  
  public static class App {
    private String user;
    private String applicationTags;
    private int allocatedVcores;
    private int allocatedMB;
    private long startedTime;
    private long launchTime;
    private long elapsedTime;
    private String queue;
    private double queueUsagePercentage;
    private double clusterUsagePercentage;

    public String getUser { return user; }
    public String getApplicationTags; { return applicationTags; }
    public int getAllocatedVcores; { return allocatedVcores; }
    public int getAllocatedMB; { return allocatedMB; }
    public long getStartedTime; { return startedTime; }
    public long getLaunchTime; { return launchTime; }
    public long getElapsedTime; { return elapsedTime; }
    public String getQueue; { return queue; }
    public double getQueueUsagePercentage; { return queueUsagePercentage; }
    public double getClusterUsagePercentage; { return clusterUsagePercentage; }
  }
}

Leave a comment