JS 토스트UI 차트와 그리드 구현
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