add:loaded all controllers within the range of ko-time.pointcut

This commit is contained in:
huoyo 2024-09-19 22:50:40 +08:00
parent 89dd35e993
commit 1b1cbef791
10 changed files with 181 additions and 93 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ target/
*.log
/.idea/
/.idea/
/.idea/

View File

@ -6,7 +6,7 @@
<groupId>cn.langpy</groupId>
<artifactId>ko-time</artifactId>
<version>2.4.7</version>
<version>2.4.8</version>
<name>KoTime</name>
<description>A springboot tool for tracking the paths of the methods,which can help you find method's performances easily.</description>
<licenses>

View File

@ -1,30 +1,44 @@
package cn.langpy.kotime.config;
import cn.langpy.kotime.model.MethodNode;
import cn.langpy.kotime.service.GraphService;
import cn.langpy.kotime.util.Context;
import cn.langpy.kotime.util.KoUtil;
import cn.langpy.kotime.util.MethodType;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;
import java.util.logging.Logger;
@Component
public class SaveResourceConfig implements CommandLineRunner {
private static Logger log = Logger.getLogger(SaveResourceConfig.class.toString());
@Resource
private ApplicationContext applicationContext;
@Resource
private AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor;
@Override
public void run(String... args) throws Exception {
DataSource dataSource = KoUtil.getDataSource();
if (null != dataSource) {
log.info("kotime=>Setting the finnal DataSource for kotime so that previous DataSources will be invalid.");
log.info("kotime=>Setting the final DataSource for kotime so that previous DataSources will be invalid.");
Context.setDataSource(dataSource);
}
StringRedisTemplate redisTemplate = KoUtil.getStringRedisTemplate();
if (null != redisTemplate) {
log.info("kotime=>Setting the finnal StringRedisTemplate for kotime so that previous StringRedisTemplate will be invalid.");
log.info("kotime=>Setting the final StringRedisTemplate for kotime so that previous StringRedisTemplate will be invalid.");
Context.setStringRedisTemplate(redisTemplate);
}
@ -35,5 +49,47 @@ public class SaveResourceConfig implements CommandLineRunner {
}
KoUtil.clearCaches();
acquireControllers();
}
private void acquireControllers() {
RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
GraphService graphService = GraphService.getInstance();
MethodMatcher methodMatcher = aspectJExpressionPointcutAdvisor.getPointcut().getMethodMatcher();
for (Map.Entry<RequestMappingInfo, HandlerMethod> methodEntry : handlerMethods.entrySet()) {
HandlerMethod handlerMethod = methodEntry.getValue();
boolean matches = methodMatcher.matches(handlerMethod.getMethod(), handlerMethod.getClass());
if (matches) {
MethodNode methodNode = toMethodNode(handlerMethod);
graphService.addMethodNode(methodNode);
}
}
}
private MethodNode toMethodNode(HandlerMethod method) {
Class<?> beanType = method.getBeanType();
RequestMapping requestMapping = beanType.getAnnotation(RequestMapping.class);
String[] cvalues = requestMapping.value();
String classRoute = "";
if (cvalues != null && cvalues.length > 0) {
classRoute = cvalues[0];
}
RequestMapping methodAnnotation = method.getMethodAnnotation(RequestMapping.class);
String[] mvalues = methodAnnotation.value();
String methodRoute = "";
if (mvalues != null && mvalues.length > 0) {
methodRoute = mvalues[0];
}
String route = classRoute+methodRoute;
MethodNode methodNode = new MethodNode();
methodNode.setId(beanType.getName() + "." + method.getMethod().getName());
methodNode.setClassName(beanType.getName());
methodNode.setMethodName(method.getMethod().getName());
methodNode.setName( beanType.getSimpleName()+ "." + method.getMethod().getName());
methodNode.setRouteName(route);
methodNode.setMethodType(MethodType.Controller);
return methodNode;
}
}

View File

@ -28,9 +28,9 @@ public class KoSqlConstant {
public final static String updateParamsAna = "UPDATE ko_param_ana SET avg_run_time=?, max_run_time=?, min_run_time=? WHERE source_id=? and params=?";
public final static String queryControllers = "select m.id,name,class_name,method_name,method_type,route_name,r.avg_run_time,r.max_run_time,r.min_run_time,r.call_num " +
public final static String queryControllers = "select m.id,name,class_name,method_name,method_type,route_name,ifnull(r.avg_run_time,0.0) avg_run_time,ifnull(r.max_run_time,0.0) max_run_time,ifnull(r.min_run_time,0.0) min_run_time,ifnull(r.call_num,0) call_num " +
"from ko_method_node m " +
"join ko_method_relation r on m.id = r.target_id " +
"left join ko_method_relation r on m.id = r.target_id " +
"where m.method_type='Controller'";
public final static String searchMethodsByName = "select m.id,name,class_name,method_name,method_type,route_name,r.avg_run_time,r.max_run_time,r.min_run_time,r.call_num " +

View File

@ -49,7 +49,7 @@ public class KoTimeController {
@GetMapping("/getApis")
@ResponseBody
@Auth
public List<MethodInfo> getApis(String question) {
public List<MethodInfo> getApis(String question,String orderBy,String sort) {
GraphService graphService = GraphService.getInstance();
List<MethodInfo> list = null;
if (StringUtils.hasText(question)) {
@ -57,7 +57,18 @@ public class KoTimeController {
} else {
list = graphService.getControllers();
}
Collections.sort(list);
Collections.sort(list, (o1, o2) -> {
int sortValue = -1;
if ("asc".equals(sort)) {
sortValue = 1;
}
if ("callNum".equals(orderBy)) {
return o1.getCallNum().compareTo(o2.getCallNum())* sortValue;
}else {
return o1.getAvgRunTime().compareTo(o2.getAvgRunTime())* sortValue;
}
});
return list;
}

View File

@ -11,6 +11,7 @@ import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
@ -238,7 +239,11 @@ public class MemoryBase implements GraphService {
if (relations.isPresent()) {
relation = relations.get();
} else {
continue;
relation = new MethodRelation();
relation.setCallNum(0);
relation.setAvgRunTime(0.0);
relation.setMaxRunTime(0.0);
relation.setMinRunTime(0.0);
}
MethodInfo methodInfo = new MethodInfo();
methodInfo.setId(methodNode.getId());
@ -277,7 +282,11 @@ public class MemoryBase implements GraphService {
if (relations.isPresent()) {
relation = relations.get();
} else {
continue;
relation = new MethodRelation();
relation.setCallNum(0);
relation.setAvgRunTime(0.0);
relation.setMaxRunTime(0.0);
relation.setMinRunTime(0.0);
}
MethodInfo methodInfo = new MethodInfo();
methodInfo.setId(methodNode.getId());
@ -381,7 +390,17 @@ public class MemoryBase implements GraphService {
rootInfo.setMethodName(methodNode.getMethodName());
rootInfo.setMethodType(methodNode.getMethodType());
rootInfo.setRouteName(methodNode.getRouteName());
MethodRelation methodRelation = methodRelations.values().stream().filter(relation -> relation.getTargetId().equals(methodId)).findFirst().get();
Optional<MethodRelation> methodRelationStream = methodRelations.values().stream().filter(relation -> relation.getTargetId().equals(methodId)).findFirst();
MethodRelation methodRelation;
if (!methodRelationStream.isPresent()) {
methodRelation = new MethodRelation();
methodRelation.setCallNum(0);
methodRelation.setAvgRunTime(0.0);
methodRelation.setMaxRunTime(0.0);
methodRelation.setMinRunTime(0.0);
}else {
methodRelation = methodRelationStream.get();
}
rootInfo.setValue(methodRelation.getAvgRunTime());
rootInfo.setAvgRunTime(methodRelation.getAvgRunTime());
rootInfo.setMaxRunTime(methodRelation.getMaxRunTime());

View File

@ -232,7 +232,11 @@ public class RedisBase implements GraphService {
if (relations.isPresent()) {
relation = relations.get();
} else {
continue;
relation = new MethodRelation();
relation.setCallNum(0);
relation.setAvgRunTime(0.0);
relation.setMaxRunTime(0.0);
relation.setMinRunTime(0.0);
}
MethodInfo methodInfo = new MethodInfo();
methodInfo.setId(methodNode.getId());
@ -279,7 +283,11 @@ public class RedisBase implements GraphService {
if (relations.isPresent()) {
relation = relations.get();
} else {
continue;
relation = new MethodRelation();
relation.setCallNum(0);
relation.setAvgRunTime(0.0);
relation.setMaxRunTime(0.0);
relation.setMinRunTime(0.0);
}
MethodInfo methodInfo = new MethodInfo();
methodInfo.setId(methodNode.getId());
@ -420,8 +428,18 @@ public class RedisBase implements GraphService {
rootInfo.setMethodType(methodNode.getMethodType());
rootInfo.setRouteName(methodNode.getRouteName());
List<MethodRelation> methodRelationList = searchList(methodRelationPre, MethodRelation.class);
Optional<MethodRelation> methodRelationStream = methodRelationList.stream().filter(relation -> relation.getTargetId().equals(methodId)).findFirst();
MethodRelation methodRelation;
if (!methodRelationStream.isPresent()) {
methodRelation = new MethodRelation();
methodRelation.setCallNum(0);
methodRelation.setAvgRunTime(0.0);
methodRelation.setMaxRunTime(0.0);
methodRelation.setMinRunTime(0.0);
}else {
methodRelation = methodRelationStream.get();
}
MethodRelation methodRelation = methodRelationList.stream().filter(relation -> relation.getTargetId().equals(methodId)).findFirst().get();
rootInfo.setValue(methodRelation.getAvgRunTime());
rootInfo.setAvgRunTime(methodRelation.getAvgRunTime());
rootInfo.setMaxRunTime(methodRelation.getMaxRunTime());

View File

@ -24,8 +24,8 @@ import java.util.logging.Logger;
*/
@Aspect
@Component
public class AuthHandler {
private static Logger log = Logger.getLogger(AuthHandler.class.toString());
public class KoAuthHandler {
private static Logger log = Logger.getLogger(KoAuthHandler.class.toString());
@Pointcut(KoConstant.authRange)
public void preProcess() {

View File

@ -2,6 +2,7 @@
color: #333;
font-weight: 400;
font-size: 14px;
cursor: pointer;
}
.common-li-bolder {

View File

@ -70,12 +70,15 @@
}
function loadApis() {
let searchText = $("#searchText").val();
get(concatToken('contextPath/koTime/getApis?question=' + searchText), function (data) {
let searchDom = getDom("searchText");
let searchText = searchDom.value;
let apiSortName = getDom('apiSortName')
let apiSortType = getDom('apiSortType')
get(concatToken(`contextPath/koTime/getApis?question=${searchText}&orderBy=${apiSortName.value}&sort=${apiSortType.value}`), function (data) {
searchDom.value='';
let element = getDom('apiList');
html = '';
for (let i = 0; i < data.length; i++) {
let simName = data[i]['name'].replace('.','#');
let className = data[i]['className'];
let methodName = data[i]['methodName'];
let avgRunTime = data[i]['avgRunTime'];
@ -85,25 +88,17 @@
let apiId = className + "." + methodName;
let color = avgRunTime > globalThreshold ? 'danger' : 'success';
if (methodType == 'Controller' && routeName != null && routeName != '') {
if (abbreviationEnable) {
html += "<li onclick=\"showMethods('" + apiId + "')\" class='common-li' id=\"" + apiId + "-list\"><span class='common-li-bolder'>" + simName + "</span>&nbsp(<span class='common-li-special'>" + routeName + "</span>)&nbsp &nbsp<span style='font-size: 10px;text-transform: lowercase' class=\"uk-label uk-label-" + color + "\">{{tab.interface.interface-list.avg-tip}} " + avgRunTime + " ms</span>" +
"&nbsp &nbsp<span style='font-size: 10px;text-transform: unset;background-color:#6d6d85' class=\"uk-label\">{{tab.interface.interface-list.call-num-tip}} " + callNum + "</span></li>";
}else {
html += "<li onclick=\"showMethods('" + apiId + "')\" class='common-li' id=\"" + apiId + "-list\">" + className + "#<span class='common-li-bolder'>" + methodName + "</span>&nbsp(<span class='common-li-special'>" + routeName + "</span>)&nbsp &nbsp<span style='font-size: 10px;text-transform: lowercase' class=\"uk-label uk-label-" + color + "\">{{tab.interface.interface-list.avg-tip}} " + avgRunTime + " ms</span>" +
"&nbsp &nbsp<span style='font-size: 10px;text-transform: unset;background-color:#6d6d85' class=\"uk-label\">{{tab.interface.interface-list.call-num-tip}} " + callNum + "</span></li>";
className = data[i]['name'].split('.')[0];
}
} else {
if (abbreviationEnable) {
html += "<li onclick=\"showMethods('" + apiId + "')\" class='common-li' id=\"" + apiId + "-list\"><span class='common-li-bolder'>" + simName + "</span>&nbsp &nbsp<span style='font-size: 10px;text-transform: lowercase' class=\"uk-label uk-label-" + color + "\">{{tab.interface.interface-list.avg-tip}} " + avgRunTime + " ms</span>" +
if (methodType == 'Controller' && routeName != null && routeName != '') {
html += "<li onclick=\"showMethods('" + apiId + "')\" class='common-li' id=\"" + apiId + "-list\">" + className + "#<span class='common-li-bolder'>" + methodName + "</span>&nbsp(<span class='common-li-special'>" + routeName + "</span>)&nbsp &nbsp<span style='font-size: 10px;text-transform: lowercase' class=\"uk-label uk-label-" + color + "\">{{tab.interface.interface-list.avg-tip}} " + avgRunTime + " ms</span>" +
"&nbsp &nbsp<span style='font-size: 10px;text-transform: unset;background-color:#6d6d85' class=\"uk-label\">{{tab.interface.interface-list.call-num-tip}} " + callNum + "</span></li>";
} else {
html += "<li onclick=\"showMethods('" + apiId + "')\" class='common-li' id=\"" + apiId + "-list\">" + className + "#<span class='common-li-bolder'>" + methodName + "</span>&nbsp &nbsp<span style='font-size: 10px;text-transform: unset' class=\"uk-label uk-label-" + color + "\">{{tab.interface.interface-list.avg-tip}} " + avgRunTime + " ms</span>" +
"&nbsp &nbsp<span style='font-size: 10px;text-transform: lowercase;background-color:#6d6d85' class=\"uk-label\">{{tab.interface.interface-list.call-num-tip}} " + callNum + "</span></li>";
}
}
}
;
};
element.innerHTML = html;
});
}
@ -423,43 +418,7 @@
function searchApis(e) {
if (e.keyCode == 13) {
let question = $('#searchText').val()
get(concatToken('contextPath/koTime/getApis?question=' + question), function (data) {
let element = getDom('apiList');
html = '';
for (let i = 0; i < data.length; i++) {
let simName = data[i]['name'].replace('.','#');
let className = data[i]['className'];
let methodName = data[i]['methodName'];
let avgRunTime = data[i]['avgRunTime'];
let methodType = data[i]['methodType'];
let routeName = data[i]['routeName'];
let callNum = data[i]['callNum'];
let apiId = className + "." + methodName;
let color = avgRunTime > globalThreshold ? 'danger' : 'success';
if (methodType == 'Controller' && routeName != null && routeName != '') {
if (abbreviationEnable) {
html += "<li onclick=\"showMethods('" + apiId + "')\" class='common-li' id=\"" + apiId + "-list\"><span class='common-li-bolder'>" + simName + "</span>&nbsp(<span class='common-li-special'>" + routeName + "</span>)&nbsp &nbsp<span style='font-size: 10px;text-transform: lowercase' class=\"uk-label uk-label-" + color + "\">{{tab.interface.interface-list.avg-tip}} " + avgRunTime + " ms</span>" +
"&nbsp &nbsp<span style='font-size: 10px;text-transform: unset;background-color:#6d6d85' class=\"uk-label\">{{tab.interface.interface-list.call-num-tip}} " + callNum + "</span></li>";
}else {
html += "<li onclick=\"showMethods('" + apiId + "')\" class='common-li' id=\"" + apiId + "-list\">" + className + "#<span class='common-li-bolder'>" + methodName + "</span>&nbsp(<span class='common-li-special'>" + routeName + "</span>)&nbsp &nbsp<span style='font-size: 10px;text-transform: lowercase' class=\"uk-label uk-label-" + color + "\">{{tab.interface.interface-list.avg-tip}} " + avgRunTime + " ms</span>" +
"&nbsp &nbsp<span style='font-size: 10px;text-transform: unset;background-color:#6d6d85' class=\"uk-label\">{{tab.interface.interface-list.call-num-tip}} " + callNum + "</span></li>";
}
} else {
if (abbreviationEnable) {
html += "<li onclick=\"showMethods('" + apiId + "')\" class='common-li' id=\"" + apiId + "-list\"><span class='common-li-bolder'>" + simName + "</span>&nbsp &nbsp<span style='font-size: 10px;text-transform: unset' class=\"uk-label uk-label-" + color + "\">{{tab.interface.interface-list.avg-tip}} " + avgRunTime + " ms</span>" +
"&nbsp &nbsp<span style='font-size: 10px;text-transform: unset;background-color:#6d6d85' class=\"uk-label\">{{tab.interface.interface-list.call-num-tip}} " + callNum + "</span></li>";
}else {
html += "<li onclick=\"showMethods('" + apiId + "')\" class='common-li' id=\"" + apiId + "-list\">" + className + "#<span class='common-li-bolder'>" + methodName + "</span>&nbsp &nbsp<span style='font-size: 10px;text-transform: unset' class=\"uk-label uk-label-" + color + "\">{{tab.interface.interface-list.avg-tip}} " + avgRunTime + " ms</span>" +
"&nbsp &nbsp<span style='font-size: 10px;text-transform: unset;background-color:#6d6d85' class=\"uk-label\">{{tab.interface.interface-list.call-num-tip}} " + callNum + "</span></li>";
}
}
}
;
element.innerHTML = html;
});
$('#searchText').val('');
loadApis()
}
}
@ -670,6 +629,9 @@
UIkit.notification.closeAll();
UIkit.modal(getDom("modal-thread")).show();
}
function changeApiSort() {
loadApis();
}
$(document).ready(function () {
refreshData();
@ -682,7 +644,7 @@
<body style="background-color: #f2f3f2">
<nav class="uk-navbar-container" style="background-color: #404b67;height: 85px" uk-navbar>
<nav class="uk-navbar-container" style="background-color: #404b67;height: 80px" uk-navbar>
<div class="uk-navbar-center">
<div class="uk-grid-small" uk-grid>
<div title="{{kotime-refresh}}" style="margin-top: 20px;font-size: 24px;color: white;cursor: pointer"
@ -707,7 +669,7 @@
</ul>
<ul class="uk-switcher uk-margin">
<li style="margin-left: 30%;margin-right: 30%;">
<li style="margin-left: 30%;margin-right: 30%;margin-top: -10px">
<ul class="uk-flex-left" uk-tab>
<li class="uk-active"><a style="text-transform: unset" href="#">{{tab.summary.interface-metric}}</a></li>
</ul>
@ -827,8 +789,10 @@
<p id="apiTip">{{tab.summary.bottom-normal-tip}}</p>
</div>
</li>
<li style="margin-left: 20%;margin-right: 20%;">
<div style="left: 35%;width: 350px;" class="uk-search uk-search-default">
<li style="margin-left: 20%;margin-right: 20%;margin-top: -10px">
<div style="margin-left: 10%;margin-right: 10%;" class="uk-grid-small uk-flex-center uk-text-center" uk-grid>
<div class="uk-width-3-5@s">
<div style="left: 10%;width: 400px;" class="uk-search uk-search-default">
<span uk-search-icon></span>
<input list="condidates" id="searchText"
style="border-top: none;border-right: none;border-left: none;border-bottom: 1px solid lightgreen;color:#213121"
@ -837,19 +801,37 @@
<datalist id="condidates" onkeydown="searchApis(event);">
</datalist>
</div>
<ul id="apiList" style="background-color: rgba(245,242,242,0.96);padding: 10px;overflow-y: auto;max-height: 70%"
class="uk-list uk-list-divider">
</div>
<div class="uk-width-1-5@s">
<div style="display: flex;">
<input style="width: 60px;border-top: none;border-right: none;border-left: none;background-color: inherit" class="uk-input" type="text" placeholder="根据" value="根据" disabled>
<select id="apiSortName" onchange="changeApiSort()" style="width: 100px;border-top: none;border-right: none;border-left: none;border-bottom: 1px solid lightgreen;color:#213121;background-color: inherit" class="uk-select">
<option value="avgRunTime" selected>平均响应</option>
<option value="callNum">调用次数</option>
</select>
</div>
</div>
<div class="uk-width-1-5@s">
<div style="width: 70px">
<select id="apiSortType" onchange="changeApiSort()" style="border-top: none;border-right: none;border-left: none;border-bottom: 1px solid lightgreen;color:#213121;background-color: inherit" class="uk-select">
<option value="desc" selected>降序</option>
<option value="asc">升序</option>
</select>
</div>
</div>
</div>
<ul id="apiList" style="background-color: rgba(245,242,242,0.96);padding: 10px;overflow-y: auto;max-height: 70%;margin-top: 10px" class="uk-list uk-list-divider">
<li>method 1 1&nbsp<span class="uk-label uk-label-success">0</span></li>
</ul>
</li>
<li style="margin-left: 20%;margin-right: 20%;">
<li style="margin-left: 20%;margin-right: 20%;margin-top: -10px">
<ul id="exceptionList"
style="background-color: rgba(245,242,242,0.96);padding: 10px;overflow-y: auto;max-height: 73%"
class="uk-list uk-list-divider">
<li>exception 1 1&nbsp<span class="uk-label uk-label-danger">未开启</span></li>
</ul>
</li>
<li style="margin-left: 30%;margin-right: 30%;">
<li style="margin-left: 30%;margin-right: 30%;margin-top: -10px">
<ul class="uk-flex-left" uk-tab>
<li class="uk-active"><a style="text-transform: unset" href="#">{{tab.thread.thread-metric}}</a></li>
</ul>
@ -905,7 +887,7 @@
<li>thread 1 1&nbsp<span class="uk-label uk-label-success">0</span></li>
</ul>
</li>
<li style="margin-left: 35%;margin-right: 35%;">
<li style="margin-left: 35%;margin-right: 35%;margin-top: -10px">
<div class="uk-card uk-card-default uk-card-body" style="border-radius: 5px">
<div id="classForm">
<div uk-form-custom="target: true" class="uk-form-controls">
@ -926,7 +908,7 @@
</div>
</div>
</li>
<li style="margin-left: 30%;margin-right: 30%;">
<li style="margin-left: 30%;margin-right: 30%;margin-top: -10px">
<ul class="uk-flex-left" uk-tab>
<li class="uk-active"><a href="#" style="text-transform: unset">{{tab.configuration.kotime-config}}</a></li>
<li class=""><a href="#" style="text-transform: unset">{{tab.configuration.dynamic-config}}</a></li>
@ -986,7 +968,7 @@
</ul>
</li>
<li style="margin-left: 30%;margin-right: 30%;">
<li style="margin-left: 30%;margin-right: 30%;margin-top: -10px">
<script src='https://gitee.com/huoyo/ko-time/widget_preview' async defer></script>
<div id="osc-gitee-widget-tag"></div>
<style>
@ -1055,7 +1037,7 @@
<div id="modal-method" class="uk-modal-full" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-full uk-close-large" type="button" uk-close></button>
<button class="uk-modal-close-full uk-close-large" style="color: red" type="button" uk-close></button>
<div class="uk-grid-collapse uk-child-width-1-1@s uk-flex-middle" uk-grid>
<div uk-height-viewport>
<div id="layerDemo" width="1200px" height="900px">