From 269abcf35218105abd7b298b7a3a28b27dca35fc Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Sat, 3 Mar 2018 23:48:39 +0900 Subject: [PATCH 01/24] [webapp][server] API getting multiple xlogs by txids. --- .../src/main/java/scouter/net/RequestCmd.java | 1 + .../netio/service/handle/XLogService.scala | 26 +++++++++ .../webapp/framework/filter/CorsFilter.java | 1 + .../scouterx/webapp/framework/util/ZZ.java | 6 ++ .../webapp/layer/consumer/XLogConsumer.java | 38 +++++++++++-- .../layer/controller/XLogController.java | 2 +- .../layer/controller/XLogDataController.java | 22 +++++++- .../webapp/layer/service/XLogService.java | 17 ++++-- .../webapp/request/MultiXLogRequest.java | 56 +++++++++++++++++++ 9 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 scouter.webapp/src/main/java/scouterx/webapp/request/MultiXLogRequest.java diff --git a/scouter.common/src/main/java/scouter/net/RequestCmd.java b/scouter.common/src/main/java/scouter/net/RequestCmd.java index 135b77a4e..907ee6738 100644 --- a/scouter.common/src/main/java/scouter/net/RequestCmd.java +++ b/scouter.common/src/main/java/scouter/net/RequestCmd.java @@ -104,6 +104,7 @@ public class RequestCmd { public static final String TRANX_LOAD_TIME = "TRANX_LOAD_TIME"; public static final String XLOG_READ_BY_TXID = "XLOG_READ_BY_TXID"; public static final String XLOG_READ_BY_GXID = "XLOG_READ_BY_GXID"; + public static final String XLOG_LOAD_BY_TXIDS = "XLOG_LOAD_BY_TXIDS"; public static final String XLOG_LOAD_BY_GXID = "XLOG_LOAD_BY_GXID"; public static final String TRANX_PROFILE = "TRANX_PROFILE"; public static final String TRANX_PROFILE_FULL = "TRANX_PROFILE_FULL"; diff --git a/scouter.server/src/main/scala/scouter/server/netio/service/handle/XLogService.scala b/scouter.server/src/main/scala/scouter/server/netio/service/handle/XLogService.scala index 3b36774b2..14d59095b 100644 --- a/scouter.server/src/main/scala/scouter/server/netio/service/handle/XLogService.scala +++ b/scouter.server/src/main/scala/scouter/server/netio/service/handle/XLogService.scala @@ -357,6 +357,32 @@ class XLogService { } } + @ServiceHandler(RequestCmd.XLOG_LOAD_BY_TXIDS) + def loadByTxIds(din: DataInputX, dout: DataOutputX, login: Boolean) { + val param = din.readMapPack() + val date = param.getText("date") + val txidLv = param.getList("txid") + + var loadCount = 0 + try { + EnumerScala.foreach(txidLv, (txidValue: DecimalValue) => { + loadCount += 1; + + if (loadCount >= Configure.getInstance().req_search_xlog_max_count) { + return; + } + val xbytes = XLogRD.getByTxid(date, txidValue.longValue()) + if(xbytes != null) { + dout.writeByte(TcpFlag.HasNEXT) + dout.write(xbytes) + dout.flush() + } + }) + } catch { + case e: Exception => {} + } + } + @ServiceHandler(RequestCmd.XLOG_LOAD_BY_GXID) def loadByGxId(din: DataInputX, dout: DataOutputX, login: Boolean) { val param = din.readMapPack(); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/filter/CorsFilter.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/filter/CorsFilter.java index 5a03a8ad6..d22acbae5 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/filter/CorsFilter.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/filter/CorsFilter.java @@ -52,6 +52,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } httpServletResponse.addHeader("Access-Control-Allow-Origin", allowOrigin); httpServletResponse.addHeader("Access-Control-Allow-Credentials", allowCredentials); + httpServletResponse.addHeader("Access-Control-Max-Age", "600"); httpServletResponse.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); httpServletResponse.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PATCH"); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/util/ZZ.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/util/ZZ.java index 52a8fc7c9..d31ff443b 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/util/ZZ.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/util/ZZ.java @@ -94,6 +94,12 @@ public static List splitParamAsInteger(String org) { return Arrays.stream(items).map(Integer::parseInt).collect(Collectors.toList()); } + public static List splitParamAsLong(String org) { + org = stripFirstLastBracket(org); + String[] items = StringUtils.split(org, COMMA); + return Arrays.stream(items).map(Long::parseLong).collect(Collectors.toList()); + } + public static Set splitParamAsIntegerSet(String org) { org = stripFirstLastBracket(org); String[] items = StringUtils.split(org, COMMA); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java index 2af6a2e5f..f82c6a96a 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java @@ -29,11 +29,12 @@ import scouterx.webapp.framework.client.net.TcpProxy; import scouterx.webapp.model.XLogData; import scouterx.webapp.model.scouter.SXLog; +import scouterx.webapp.request.GxidXLogRequest; +import scouterx.webapp.request.MultiXLogRequest; import scouterx.webapp.request.PageableXLogRequest; import scouterx.webapp.request.RealTimeXLogRequest; import scouterx.webapp.request.SearchXLogRequest; import scouterx.webapp.request.SingleXLogRequest; -import scouterx.webapp.request.GxidXLogRequest; import scouterx.webapp.view.PageableXLogView; import scouterx.webapp.view.RealTimeXLogView; @@ -41,6 +42,8 @@ import java.util.List; import java.util.stream.Collectors; +import static scouter.lang.constants.ParamConstant.XLOG_TXID; + /** * @author Gun Lee (gunlee01@gmail.com) on 2017. 8. 27. */ @@ -87,7 +90,7 @@ public void handlePageableXLog(final PageableXLogRequest pageableXLogRequest, fi MapPack paramPack = new MapPack(); paramPack.put(ParamConstant.DATE, pageableXLogRequest.getYyyymmdd()); paramPack.put(ParamConstant.XLOG_START_TIME, pageableXLogRequest.getStartTimeMillis()); - paramPack.put(ParamConstant.XLOG_TXID, pageableXLogRequest.getLastTxid()); + paramPack.put(XLOG_TXID, pageableXLogRequest.getLastTxid()); paramPack.put(ParamConstant.XLOG_END_TIME, pageableXLogRequest.getEndTimeMillis()); paramPack.put(ParamConstant.XLOG_LAST_BUCKET_TIME, pageableXLogRequest.getLastXLogTime()); paramPack.put(ParamConstant.XLOG_PAGE_COUNT, pageableXLogRequest.getPageCount()); @@ -162,7 +165,7 @@ public SXLog retrieveByTxidAsXLog(final SingleXLogRequest singleXLogRequest) { * * @param xlogRequest */ - public List retrieveXLogsByGxid(final GxidXLogRequest xlogRequest) { + public List retrieveXLogListByGxid(final GxidXLogRequest xlogRequest) { return retrieveXLogPacksByGxid(xlogRequest).stream() .map(pack -> (XLogPack) pack) .map(SXLog::of) @@ -175,18 +178,30 @@ public List retrieveXLogsByGxid(final GxidXLogRequest xlogRequest) { * * @param xLogRequest */ - public List retrieveXLogDatasByGxid(final GxidXLogRequest xLogRequest) { + public List retrieveXLogDataListByGxid(final GxidXLogRequest xLogRequest) { return retrieveXLogPacksByGxid(xLogRequest).stream() .map(pack -> (XLogPack) pack) .map(pack -> XLogData.of(pack, xLogRequest.getServerId())) .collect(Collectors.toList()); } + /** + * retrieve XLog Data List by txids + * + * @param multiXLogRequest + */ + public List retrieveXLogDataListByTxids(final MultiXLogRequest multiXLogRequest) { + return retrieveXLogPacksByTxids(multiXLogRequest).stream() + .map(pack -> (XLogPack) pack) + .map(pack -> XLogData.of(pack, multiXLogRequest.getServerId())) + .collect(Collectors.toList()); + } + private XLogPack retrieveByTxid(final SingleXLogRequest singleXLogRequest) { MapPack param = new MapPack(); param.put(ParamConstant.DATE, singleXLogRequest.getYyyymmdd()); - param.put(ParamConstant.XLOG_TXID, singleXLogRequest.getTxid()); + param.put(XLOG_TXID, singleXLogRequest.getTxid()); XLogPack pack; try (TcpProxy tcpProxy = TcpProxy.getTcpProxy(singleXLogRequest.getServerId())) { @@ -208,6 +223,19 @@ private List retrieveXLogPacksByGxid(GxidXLogRequest xlogRequest) { return results; } + private List retrieveXLogPacksByTxids(MultiXLogRequest xlogRequest) { + MapPack param = new MapPack(); + param.put(ParamConstant.DATE, xlogRequest.getYyyymmdd()); + ListValue xlogLv = param.newList(XLOG_TXID); + xlogRequest.getTxidList().forEach(xlogLv::add); + + List results; + try (TcpProxy tcpProxy = TcpProxy.getTcpProxy(xlogRequest.getServerId())) { + results = tcpProxy.process(RequestCmd.XLOG_LOAD_BY_TXIDS, param); + } + return results; + } + private List searchXLogPackList(final SearchXLogRequest searchXLogRequest) { MapPack paramPack = new MapPack(); paramPack.put(ParamConstant.DATE,searchXLogRequest.getYyyymmdd()); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogController.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogController.java index 4d4114407..d9a5c0e50 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogController.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogController.java @@ -153,7 +153,7 @@ public CommonResultView retrieveSingleXLog(@Valid @BeanParam SingleXLogRe @Consumes(MediaType.APPLICATION_JSON) public CommonResultView> retrieveXLogsByGxid(@Valid @BeanParam GxidXLogRequest gxidRequest) { gxidRequest.validate(); - List xLogs = xLogService.retrieveXLogsByGxid(gxidRequest); + List xLogs = xLogService.retrieveXLogListByGxid(gxidRequest); return CommonResultView.success(xLogs); } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogDataController.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogDataController.java index 83622ed2e..e6a1f34a5 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogDataController.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogDataController.java @@ -37,6 +37,7 @@ import scouterx.webapp.model.XLogData; import scouterx.webapp.model.XLogPackWrapper; import scouterx.webapp.request.GxidXLogRequest; +import scouterx.webapp.request.MultiXLogRequest; import scouterx.webapp.request.PageableXLogRequest; import scouterx.webapp.request.RealTimeXLogDataRequest; import scouterx.webapp.request.SearchXLogRequest; @@ -177,7 +178,7 @@ public Response streamPageableXLog(@Valid @BeanParam PageableXLogRequest xLogReq /** * request xlog by txid - * uri : /xlog-data/{yyyymmdd}/{txid} @see {@link SingleXLogRequest} + * uri : /{yyyymmdd}/{txid} @see {@link SingleXLogRequest} * * @param singleXlogRequest */ @@ -191,6 +192,21 @@ public CommonResultView retrieveSingleXLog(@Valid @BeanParam SingleXLo return CommonResultView.success(xLogData); } + /** + * request xlog by txid + * uri : /{yyyymmdd}/multi @see {@link MultiXLogRequest} + * + * @param multiXLogRequest + */ + @GET + @Path("/{yyyymmdd}/multi/{txidList}") + @Consumes(MediaType.APPLICATION_JSON) + public CommonResultView> retrieveXLogDataListByTxids(@Valid @BeanParam MultiXLogRequest multiXLogRequest) { + List xLogs = xLogService.retrieveXLogDataListByTxids(multiXLogRequest); + + return CommonResultView.success(xLogs); + } + /** * request xlogs by gxid * uri : /{yyyymmdd}/gxid/{gxid} @see {@link GxidXLogRequest} @@ -200,9 +216,9 @@ public CommonResultView retrieveSingleXLog(@Valid @BeanParam SingleXLo @GET @Path("/{yyyymmdd}/gxid/{gxid}") @Consumes(MediaType.APPLICATION_JSON) - public CommonResultView> retrieveXLogDatasByGxid(@Valid @BeanParam GxidXLogRequest gxidRequest) { + public CommonResultView> retrieveXLogDataListByGxid(@Valid @BeanParam GxidXLogRequest gxidRequest) { gxidRequest.validate(); - List xLogs = xLogService.retrieveXLogDatasByGxid(gxidRequest); + List xLogs = xLogService.retrieveXLogDataListByGxid(gxidRequest); return CommonResultView.success(xLogs); } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/service/XLogService.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/service/XLogService.java index 189deff28..7828a5bd4 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/service/XLogService.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/service/XLogService.java @@ -22,6 +22,7 @@ import scouterx.webapp.layer.consumer.XLogConsumer; import scouterx.webapp.model.XLogData; import scouterx.webapp.model.scouter.SXLog; +import scouterx.webapp.request.MultiXLogRequest; import scouterx.webapp.request.SearchXLogRequest; import scouterx.webapp.request.PageableXLogRequest; import scouterx.webapp.request.RealTimeXLogRequest; @@ -88,16 +89,24 @@ public SXLog retrieveSingleXLog(final SingleXLogRequest singleXlogRequest) { /** * retrieve Xlog List by gxid */ - public List retrieveXLogsByGxid(final GxidXLogRequest xlogRequest) { - return xLogConsumer.retrieveXLogsByGxid(xlogRequest); + public List retrieveXLogListByGxid(final GxidXLogRequest xlogRequest) { + return xLogConsumer.retrieveXLogListByGxid(xlogRequest); } /** * retrieve Xlog data List by gxid */ - public List retrieveXLogDatasByGxid(final GxidXLogRequest xlogRequest) { - return xLogConsumer.retrieveXLogDatasByGxid(xlogRequest); + public List retrieveXLogDataListByGxid(final GxidXLogRequest xlogRequest) { + return xLogConsumer.retrieveXLogDataListByGxid(xlogRequest); } + /** + * retrieve Xlog data List by txids + */ + public List retrieveXLogDataListByTxids(final MultiXLogRequest multiXLogRequest) { + return xLogConsumer.retrieveXLogDataListByTxids(multiXLogRequest); + } + + } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/request/MultiXLogRequest.java b/scouter.webapp/src/main/java/scouterx/webapp/request/MultiXLogRequest.java new file mode 100644 index 000000000..2b0c9bd30 --- /dev/null +++ b/scouter.webapp/src/main/java/scouterx/webapp/request/MultiXLogRequest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015 the original author or authors. + * @https://github.com/scouter-project/scouter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package scouterx.webapp.request; + +import lombok.Getter; +import lombok.Setter; +import scouterx.webapp.framework.client.server.ServerManager; +import scouterx.webapp.framework.util.ZZ; + +import javax.validation.constraints.NotNull; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import java.util.List; + +/** + * Created by jaco.ryu on 2017. 10. 13.. + */ +@Getter +@Setter +public class MultiXLogRequest { + int serverId; + + List txidList; + + @NotNull + @PathParam("yyyymmdd") + String yyyymmdd; + + @PathParam("txidList") + public void setTxidList(String txids) { + this.txidList = ZZ.splitParamAsLong(txids); + } + + @QueryParam("serverId") + public void setServerId(int serverId) { + this.serverId = ServerManager.getInstance().getServerIfNullDefault(serverId).getId(); + } + + +} From f3056742247e407b92b1a832c4c3cfc3ea7d6671 Mon Sep 17 00:00:00 2001 From: knamkim Date: Fri, 9 Mar 2018 15:15:34 +0900 Subject: [PATCH 02/24] change local reposity path in parent pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6838436b5..a0b93f8f6 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ local-maven-repo - file:///${project.basedir}/local-maven-repo + file:///${project.basedir}/../local-maven-repo redhat.com From aa8898c301a104b6b034c1919b761b89f65e0645 Mon Sep 17 00:00:00 2001 From: Gun Lee Date: Fri, 9 Mar 2018 19:12:43 +0900 Subject: [PATCH 03/24] versioning 1.8.4.SNAPSHOT --- pom.xml | 2 +- scouter.agent.batch/pom.xml | 2 +- scouter.agent.host/pom.xml | 2 +- scouter.agent.java/pom.xml | 2 +- scouter.common/pom.xml | 2 +- scouter.deploy/pom.xml | 2 +- scouter.server.boot/pom.xml | 2 +- scouter.server/pom.xml | 2 +- scouter.webapp/pom.xml | 19 ++++++++++++++++++- 9 files changed, 26 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index a0b93f8f6..0e5f3103c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4.SNAPSHOT pom SCOUTER APM diff --git a/scouter.agent.batch/pom.xml b/scouter.agent.batch/pom.xml index 22e8a5a1e..78abcc657 100644 --- a/scouter.agent.batch/pom.xml +++ b/scouter.agent.batch/pom.xml @@ -5,7 +5,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4.SNAPSHOT scouter-agent-batch diff --git a/scouter.agent.host/pom.xml b/scouter.agent.host/pom.xml index e5fff83e5..b8a5dba03 100644 --- a/scouter.agent.host/pom.xml +++ b/scouter.agent.host/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4.SNAPSHOT scouter-agent-host diff --git a/scouter.agent.java/pom.xml b/scouter.agent.java/pom.xml index 531be5c50..0f86e7748 100644 --- a/scouter.agent.java/pom.xml +++ b/scouter.agent.java/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4.SNAPSHOT scouter-agent-java diff --git a/scouter.common/pom.xml b/scouter.common/pom.xml index 6e668def1..fd49860b3 100644 --- a/scouter.common/pom.xml +++ b/scouter.common/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4.SNAPSHOT scouter-common diff --git a/scouter.deploy/pom.xml b/scouter.deploy/pom.xml index d24848120..fbc90c446 100644 --- a/scouter.deploy/pom.xml +++ b/scouter.deploy/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4.SNAPSHOT scouter-deploy diff --git a/scouter.server.boot/pom.xml b/scouter.server.boot/pom.xml index 227e55ae1..a5c2887e5 100644 --- a/scouter.server.boot/pom.xml +++ b/scouter.server.boot/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4.SNAPSHOT scouter-server-boot diff --git a/scouter.server/pom.xml b/scouter.server/pom.xml index df787c0cf..eb64063ef 100644 --- a/scouter.server/pom.xml +++ b/scouter.server/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4.SNAPSHOT scouter-server diff --git a/scouter.webapp/pom.xml b/scouter.webapp/pom.xml index f9cbeba13..9871e07f6 100644 --- a/scouter.webapp/pom.xml +++ b/scouter.webapp/pom.xml @@ -5,7 +5,7 @@ scouter-parent io.github.scouter-project - 1.8.3 + 1.8.4.SNAPSHOT 4.0.0 @@ -45,6 +45,23 @@ jetty-servlet ${jetty.version} + + + + + + + + + + + + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty.version} + org.glassfish.jersey.containers jersey-container-servlet-core From 13fbed88ca2a7229c8c623edfa87c877a23bd056 Mon Sep 17 00:00:00 2001 From: Gun Lee Date: Fri, 9 Mar 2018 19:13:32 +0900 Subject: [PATCH 04/24] [Server] fix log purging bug. --- scouter.server/src/main/scala/scouter/server/Logger.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scouter.server/src/main/scala/scouter/server/Logger.scala b/scouter.server/src/main/scala/scouter/server/Logger.scala index 20791c495..a3637c26a 100644 --- a/scouter.server/src/main/scala/scouter/server/Logger.scala +++ b/scouter.server/src/main/scala/scouter/server/Logger.scala @@ -219,8 +219,9 @@ object Logger { try { val d = DateUtil.yyyymmdd(date) val fileUnit = DateUtil.getDateUnit(d) - if (nowUnit - fileUnit > DateUtil.MILLIS_PER_DAY * conf.log_keep_days) { + if (nowUnit - fileUnit > conf.log_keep_days) { files(i).delete() + Logger.println("[scouter] delete log file : " + files(i).getAbsolutePath) } } catch { case e: Exception => From 54b7fcf53fe5820e3037744be4eb0b9357c7bea2 Mon Sep 17 00:00:00 2001 From: Gun Lee Date: Fri, 9 Mar 2018 19:14:22 +0900 Subject: [PATCH 05/24] [webapp] websocket sample service. --- .../java/scouter/server/http/HttpServer.java | 12 ++ .../webapp/layer/websock/BasicSocket.java | 52 ++++++++ .../java/scouterx/webapp/main/WebAppMain.java | 21 ++- .../main/resources/webroot/test/ws/index.html | 21 +++ .../main/resources/webroot/test/ws/main.css | 29 ++++ .../resources/webroot/test/ws/websocket.js | 125 ++++++++++++++++++ 6 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 scouter.webapp/src/main/java/scouterx/webapp/layer/websock/BasicSocket.java create mode 100644 scouter.webapp/src/main/resources/webroot/test/ws/index.html create mode 100644 scouter.webapp/src/main/resources/webroot/test/ws/main.css create mode 100644 scouter.webapp/src/main/resources/webroot/test/ws/websocket.js diff --git a/scouter.server/src/main/java/scouter/server/http/HttpServer.java b/scouter.server/src/main/java/scouter/server/http/HttpServer.java index a1ed702cf..48bfe2c2b 100644 --- a/scouter.server/src/main/java/scouter/server/http/HttpServer.java +++ b/scouter.server/src/main/java/scouter/server/http/HttpServer.java @@ -96,6 +96,18 @@ public void run() { handlers.addHandler(context); server.setHandler(handlers); + if (conf.net_http_api_enabled) { + try { + Class c = Class.forName("scouterx.webapp.main.WebAppMain"); + c.getMethod("setWebSocketServer", ServletContextHandler.class).invoke(null, context); + } catch (Throwable e) { + Logger.println("Error while setWebSocketServer!"); + System.out.println("Error while setWebSocketServer!"); + Logger.printStackTrace(e); + e.printStackTrace(); + } + } + try { server.start(); server.join(); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/websock/BasicSocket.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/websock/BasicSocket.java new file mode 100644 index 000000000..19e12e85d --- /dev/null +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/websock/BasicSocket.java @@ -0,0 +1,52 @@ +package scouterx.webapp.layer.websock; + +import lombok.extern.slf4j.Slf4j; + +import javax.websocket.CloseReason; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.RemoteEndpoint; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint("/basic") +@Slf4j +public class BasicSocket +{ + private Session session; + private RemoteEndpoint.Async remote; + + @OnClose + public void onWebSocketClose(CloseReason close) + { + this.session = null; + this.remote = null; + log.info("WebSocket Close: {} - {}",close.getCloseCode(),close.getReasonPhrase()); + } + + @OnOpen + public void onWebSocketOpen(Session session) + { + this.session = session; + this.remote = this.session.getAsyncRemote(); + log.info("WebSocket Connect: {}",session); + this.remote.sendText("You are now connected to " + this.getClass().getName()); + } + + @OnError + public void onWebSocketError(Throwable cause) + { + log.warn("WebSocket Error",cause); + } + + @OnMessage + public String onWebSocketText(String message) + { + log.info("Echoing back text message [{}]",message); + // Using shortcut approach to sending messages. + // You could use a void method and use remote.sendText() + return message; + } +} diff --git a/scouter.webapp/src/main/java/scouterx/webapp/main/WebAppMain.java b/scouter.webapp/src/main/java/scouterx/webapp/main/WebAppMain.java index c3c531cf4..465bb4ec5 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/main/WebAppMain.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/main/WebAppMain.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import org.glassfish.jersey.servlet.ServletContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,9 +45,13 @@ import scouterx.webapp.framework.filter.LoggingInitServletFilter; import scouterx.webapp.framework.filter.NoCacheFilter; import scouterx.webapp.framework.filter.ReleaseResourceFilter; +import scouterx.webapp.layer.websock.BasicSocket; import scouterx.webapp.swagger.Bootstrap; import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; import javax.ws.rs.core.Application; import java.io.File; import java.util.EnumSet; @@ -90,6 +95,8 @@ public static void main(String[] args) throws Exception { handlers.addHandler(servletContextHandler); server.setHandler(handlers); + setWebSocketServer(servletContextHandler); + try { server.start(); @@ -99,6 +106,14 @@ public static void main(String[] args) throws Exception { } } + public static void setWebSocketServer(ServletContextHandler servletContextHandler) throws ServletException, DeploymentException { + // Add javax.websocket support + ServerContainer container = WebSocketServerContainerInitializer.configureContext(servletContextHandler); + container.setDefaultMaxSessionIdleTimeout(7*24*3600*1000); + // Add echo endpoint to server container + container.addEndpoint(BasicSocket.class); + } + private static ServletContextHandler setWebHttpApiHandler () { ConfigureAdaptor conf = ConfigureManager.getConfigure(); @@ -241,12 +256,14 @@ private static void initializeLogDir() { * (This method also can be invoked from scouter.server's Http Server when this webapp runs as an embedded mode.) * */ - public static ServletContextHandler setWebAppContext() { + public static ServletContextHandler setWebAppContext() throws ServletException, DeploymentException { //The case - embedded mode (run in-process of scouter server) if (!standAloneMode) { initializeLogDir(); connectScouterCollector(); } - return setWebHttpApiHandler(); + ServletContextHandler handler = setWebHttpApiHandler(); + + return handler; } } diff --git a/scouter.webapp/src/main/resources/webroot/test/ws/index.html b/scouter.webapp/src/main/resources/webroot/test/ws/index.html new file mode 100644 index 000000000..2a8eb355a --- /dev/null +++ b/scouter.webapp/src/main/resources/webroot/test/ws/index.html @@ -0,0 +1,21 @@ + + + Jetty WebSocket Echo Examples + + + + + Jetty WebSocket Echo Examples #console +
+
+ + + +
+ + + \ No newline at end of file diff --git a/scouter.webapp/src/main/resources/webroot/test/ws/main.css b/scouter.webapp/src/main/resources/webroot/test/ws/main.css new file mode 100644 index 000000000..9eebead46 --- /dev/null +++ b/scouter.webapp/src/main/resources/webroot/test/ws/main.css @@ -0,0 +1,29 @@ +body { + font-family: sans-serif; +} + +div { + border: 0px solid black; +} + +div#console { + clear: both; + width: 40em; + height: 20em; + overflow: auto; + background-color: #f0f0f0; + padding: 4px; + border: 1px solid black; +} + +div#console .info { + color: black; +} + +div#console .client { + color: blue; +} + +div#console .server { + color: magenta; +} diff --git a/scouter.webapp/src/main/resources/webroot/test/ws/websocket.js b/scouter.webapp/src/main/resources/webroot/test/ws/websocket.js new file mode 100644 index 000000000..2619d9228 --- /dev/null +++ b/scouter.webapp/src/main/resources/webroot/test/ws/websocket.js @@ -0,0 +1,125 @@ +if (!window.WebSocket && window.MozWebSocket) { + window.WebSocket = window.MozWebSocket; +} + +if (!window.WebSocket) { + alert("WebSocket not supported by this browser"); +} + +function $() { + return document.getElementById(arguments[0]); +} +function $F() { + return document.getElementById(arguments[0]).value; +} + +function getKeyCode(ev) { + if (window.event) + return window.event.keyCode; + return ev.keyCode; +} + +var wstool = { + connect : function() { + var location = 'ws://' + document.location.host + "/basic"; + + wstool.info("Document URI: " + document.location); + wstool.info("WS URI: " + location); + + this._scount = 0; + + try { + this._ws = new WebSocket(location); + this._ws.onopen = this._onopen; + this._ws.onmessage = this._onmessage; + this._ws.onclose = this._onclose; + } catch (exception) { + wstool.info("Connect Error: " + exception); + } + }, + + close : function() { + this._ws.close(1000); + }, + + _out : function(css, message) { + var console = $('console'); + var spanText = document.createElement('span'); + spanText.className = 'text ' + css; + spanText.innerHTML = message; + var lineBreak = document.createElement('br'); + console.appendChild(spanText); + console.appendChild(lineBreak); + console.scrollTop = console.scrollHeight - console.clientHeight; + }, + + setState : function(enabled) { + $('connect').disabled = enabled; + $('close').disabled = !enabled; + $('hello').disabled = !enabled; + }, + + info : function(message) { + wstool._out("info", message); + }, + + error : function(message) { + wstool._out("error", message); + }, + + infoc : function(message) { + wstool._out("client", "[c] " + message); + }, + + infos : function(message) { + this._scount++; + wstool._out("server", "[s" + this._scount + "] " + message); + }, + + _onopen : function() { + wstool.setState(true); + wstool.info("Websocket Connected"); + }, + + _send : function(message) { + if (this._ws) { + this._ws.send(message); + wstool.infoc(message); + } + }, + + write : function(text) { + wstool._send(text); + }, + + _onmessage : function(m) { + if (m.data) { + wstool.infos(m.data); + } + }, + + _onclose : function(closeEvent) { + this._ws = null; + wstool.setState(false); + wstool.info("Websocket Closed"); + wstool.info(" .wasClean = " + closeEvent.wasClean); + + var codeMap = {}; + codeMap[1000] = "(NORMAL)"; + codeMap[1001] = "(ENDPOINT_GOING_AWAY)"; + codeMap[1002] = "(PROTOCOL_ERROR)"; + codeMap[1003] = "(UNSUPPORTED_DATA)"; + codeMap[1004] = "(UNUSED/RESERVED)"; + codeMap[1005] = "(INTERNAL/NO_CODE_PRESENT)"; + codeMap[1006] = "(INTERNAL/ABNORMAL_CLOSE)"; + codeMap[1007] = "(BAD_DATA)"; + codeMap[1008] = "(POLICY_VIOLATION)"; + codeMap[1009] = "(MESSAGE_TOO_BIG)"; + codeMap[1010] = "(HANDSHAKE/EXT_FAILURE)"; + codeMap[1011] = "(SERVER/UNEXPECTED_CONDITION)"; + codeMap[1015] = "(INTERNAL/TLS_ERROR)"; + var codeStr = codeMap[closeEvent.code]; + wstool.info(" .code = " + closeEvent.code + " " + codeStr); + wstool.info(" .reason = " + closeEvent.reason); + } +}; From 07fbe45158a34bf4fdf694a568482942584676ae Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Fri, 9 Mar 2018 22:38:06 +0900 Subject: [PATCH 06/24] [Server] KV store api minor bug fix. --- .../src/main/scala/scouter/server/db/io/IndexKeyFile2.scala | 5 ++++- .../java/scouterx/webapp/layer/consumer/XLogConsumer.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala b/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala index 72602e5bf..404984018 100644 --- a/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala +++ b/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala @@ -73,7 +73,10 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { while (realKeyPos > 0) { val oKey = this.keyFile.getKey(realKeyPos); if (CompareUtil.equals(oKey, key)) { - return this.keyFile.update(realKeyPos, ttl, key, value); + val result = this.keyFile.update(realKeyPos, ttl, key, value); + if(!result) { + return put(key, value, ttl); + } } realKeyPos = this.keyFile.getPrevPos(realKeyPos); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java index f82c6a96a..157ba9857 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java @@ -55,7 +55,7 @@ public class XLogConsumer { */ public void handleRealTimeXLog(final RealTimeXLogRequest xLogRequest, final INetReader reader) { boolean isFirst = false; - int firstRetrieveLimit = 5000; + int firstRetrieveLimit = 10000; if (xLogRequest.getXLogLoop() == 0 && xLogRequest.getXLogIndex() == 0) { isFirst = true; From 4845e6e9c22afa45ceadfbdefa33390fe680af43 Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Sun, 11 Mar 2018 23:13:48 +0900 Subject: [PATCH 07/24] [webapp] allows multi-logon with the same id. --- scouter.webapp/pom.xml | 12 ++ .../framework/client/server/Server.java | 2 +- .../webapp/framework/session/UserToken.java | 16 ++- .../framework/session/UserTokenCache.java | 20 +++- .../controller/PrivateKvStoreController.java | 2 +- .../layer/service/UserTokenService.java | 70 ++++++++--- .../framework/session/UserTokenTest.java | 4 +- .../layer/service/UserTokenServiceTest.java | 113 ++++++++++++++++++ 8 files changed, 208 insertions(+), 31 deletions(-) create mode 100644 scouter.webapp/src/test/java/scouterx/webapp/layer/service/UserTokenServiceTest.java diff --git a/scouter.webapp/pom.xml b/scouter.webapp/pom.xml index 9871e07f6..0fcb21a21 100644 --- a/scouter.webapp/pom.xml +++ b/scouter.webapp/pom.xml @@ -139,6 +139,18 @@ 1.10.19 test
+ + org.powermock + powermock-module-junit4 + 1.7.3 + test + + + org.powermock + powermock-api-mockito + 1.7.3 + test + diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/client/server/Server.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/client/server/Server.java index 8107fbfa8..5342ddf87 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/client/server/Server.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/client/server/Server.java @@ -27,7 +27,7 @@ import scouterx.webapp.framework.configure.ConfigureManager; @Slf4j -public final class Server { +public class Server { ConfigureAdaptor conf = ConfigureManager.getConfigure(); final private int id; diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserToken.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserToken.java index a19f9abf9..1213d7f50 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserToken.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserToken.java @@ -29,41 +29,45 @@ @Getter @Setter public class UserToken { - String id; + String userId; String token; long footprintSec; //unix timestamp int serverId; boolean fromSession; private UserToken(String id, String token, int serverId) { - this.id = id; + this.userId = id; this.token = token; this.footprintSec = 0; this.serverId = serverId; } public String getStoreKey() { - return id; + return userId; } public String toStoreValue() { - return "V1." + footprintSec + "." + token + "." + id; + return "V1." + footprintSec + "." + token + "." + userId; } public String toBearerToken() { - return "V1." + token + "." + Hexa32.toString32(serverId) + "." + id; + return "V1." + token + "." + Hexa32.toString32(serverId) + "." + userId; } public boolean isExpired(int timeoutSec) { return footprintSec + timeoutSec < System.currentTimeMillis() / 1000; } + public boolean isNotExpired(int timeoutSec) { + return !isExpired(timeoutSec); + } + public boolean needToBeRenewed(int touchThresholdSec) { return footprintSec + touchThresholdSec < System.currentTimeMillis() / 1000; } public UserToken renew() { - return UserToken.newToken(id, token, serverId); + return UserToken.newToken(userId, token, serverId); } public static UserToken fromBearerToken(String token) { diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserTokenCache.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserTokenCache.java index ece400a5e..243a40362 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserTokenCache.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserTokenCache.java @@ -33,11 +33,23 @@ public static UserTokenCache getInstance() { return instance; } - public void put(String userId, UserToken token) { - cache.put(userId, token); + public UserToken get(UserToken condition) { + return get(condition.getToken()); } - public UserToken get(String userId) { - return cache.get(userId); + public void put(UserToken token) { + put(token.getToken(), token); + } + + public void putAsRecent(UserToken token) { + cache.putLast(token.getToken(), token); + } + + private void put(String key, UserToken token) { + cache.put(key, token); + } + + private UserToken get(String key) { + return cache.get(key); } } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/PrivateKvStoreController.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/PrivateKvStoreController.java index 441defc43..e4424fac7 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/PrivateKvStoreController.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/PrivateKvStoreController.java @@ -133,7 +133,7 @@ private String getPrivateKeyPrefix() { if (userToken == null) { ErrorState.LOGIN_REQUIRED.newBizException(); } - return "]" + userToken.getId() + ":"; + return "]" + userToken.getUserId() + ":"; } private String toPrivateKey(String key) { diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/service/UserTokenService.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/service/UserTokenService.java index e1347bdc1..5f7abc3c0 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/service/UserTokenService.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/service/UserTokenService.java @@ -29,9 +29,14 @@ import scouterx.webapp.framework.session.UserTokenCache; import scouterx.webapp.model.scouter.SUser; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + /** * @author Gun Lee (gunlee01@gmail.com) on 2017. 8. 27. - * + *

* It use scouter kv store in which data only can be added not delete or modify. * (We will make it better in a latter version.) */ @@ -40,8 +45,8 @@ public class UserTokenService { private ConfigureAdaptor conf = ConfigureManager.getConfigure(); private final static float TOUCH_RATE = 0.1f; - private int sessionExpireSec = (int) (conf.getNetHttpApiSessionTimeout() * (1.0f + TOUCH_RATE)); - private int SessionTouchThresholdSec = (int) (conf.getNetHttpApiSessionTimeout() * TOUCH_RATE); + public int sessionExpireSec = (int) (conf.getNetHttpApiSessionTimeout() * (1.0f + TOUCH_RATE)); + public int SessionTouchThresholdSec = (int) (conf.getNetHttpApiSessionTimeout() * TOUCH_RATE); private SessionIdGenerator sessionIdGenerator = new SessionIdGenerator(); private CustomKvStoreService customKvStoreService = new CustomKvStoreService(); @@ -51,8 +56,11 @@ public class UserTokenService { */ public String publishToken(final Server server, final SUser user) { UserToken userToken = UserToken.newToken(user.getId(), sessionIdGenerator.generateSessionId(), server.getId()); - UserTokenCache.getInstance().put(user.getId(), userToken); - customKvStoreService.set(SESSION_STORE, userToken.getStoreKey(), userToken.toStoreValue(), sessionExpireSec, server); + UserTokenCache.getInstance().put(userToken); + + String mergedStoreValue = getAndMergeToStoredValue(userToken); + customKvStoreService.set(SESSION_STORE, userToken.getUserId(), mergedStoreValue, sessionExpireSec, server); + return userToken.toBearerToken(); } @@ -60,17 +68,14 @@ public String publishToken(final Server server, final SUser user) { * check user session & renew token's footprint if valid */ public void validateToken(UserToken token) { - UserToken tokenTrusted = UserTokenCache.getInstance().get(token.getId()); + UserToken tokenTrusted = UserTokenCache.getInstance().get(token); if (tokenTrusted == null) { - String stored = customKvStoreService.get(SESSION_STORE, token.getStoreKey(), ServerManager.getInstance().getServerIfNullDefault(token.getServerId())); - if (StringUtils.isNotBlank(stored)) { - tokenTrusted = UserToken.fromStoreValue(stored, token.getServerId()); - if (tokenTrusted != null) { - UserTokenCache.getInstance().put(tokenTrusted.getId(), tokenTrusted); - } + tokenTrusted = getStoredMatchedToken(token); + if (tokenTrusted != null) { + UserTokenCache.getInstance().put(tokenTrusted); } } - if (tokenTrusted == null || !tokenTrusted.getToken().equals(token.getToken()) || tokenTrusted.isExpired(sessionExpireSec)) { + if (tokenTrusted == null || tokenTrusted.isExpired(sessionExpireSec)) { throw ErrorState.SESSION_EXPIRED.newBizException(); } if (tokenTrusted.needToBeRenewed(SessionTouchThresholdSec)) { @@ -82,9 +87,40 @@ public void validateToken(UserToken token) { * renew token's footprint */ private void touchToken(UserToken token) { - UserToken userToken = token.renew(); - UserTokenCache.getInstance().put(userToken.getId(), userToken); - customKvStoreService.set(SESSION_STORE, userToken.getStoreKey(), userToken.toStoreValue(), sessionExpireSec, - ServerManager.getInstance().getServer(token.getServerId())); + UserToken renewedToken = token.renew(); + + UserTokenCache.getInstance().putAsRecent(renewedToken); + String mergedStoreValue = getAndMergeToStoredValue(renewedToken); + + customKvStoreService.set(SESSION_STORE, renewedToken.getUserId(), mergedStoreValue, sessionExpireSec, ServerManager.getInstance().getServer(renewedToken.getServerId())); + } + + private UserToken getStoredMatchedToken(UserToken userToken) { + String tokens = customKvStoreService.get(SESSION_STORE, userToken.getUserId(), ServerManager.getInstance().getServerIfNullDefault(userToken.getServerId())); + Map userTokenMap = Arrays.stream(tokens.split(":")) + .map(v -> UserToken.fromStoreValue(v, 0)) + .collect(Collectors.toMap(UserToken::getToken, Function.identity())); + + return userTokenMap.get(userToken.getToken()); + } + + String getAndMergeToStoredValue(UserToken userToken) { + String tokens = customKvStoreService.get(SESSION_STORE, userToken.getUserId(), ServerManager.getInstance().getServerIfNullDefault(userToken.getServerId())); + return mergeStoredTokensWith(tokens, userToken); + } + + String mergeStoredTokensWith(String tokens, UserToken userToken) { + if (StringUtils.isBlank(tokens)) { + return userToken.toStoreValue(); + } + Map userTokenMap = Arrays.stream(tokens.split(":")) + .map(v -> UserToken.fromStoreValue(v, 0)) + .filter(v -> v.isNotExpired(sessionExpireSec)) + .collect(Collectors.toMap(UserToken::getToken, Function.identity())); + + userTokenMap.put(userToken.getToken(), userToken); + return userTokenMap.values().stream() + .map(UserToken::toStoreValue) + .collect(Collectors.joining(":")); } } diff --git a/scouter.webapp/src/test/java/scouterx/webapp/framework/session/UserTokenTest.java b/scouter.webapp/src/test/java/scouterx/webapp/framework/session/UserTokenTest.java index 97888c7c5..2c5837f06 100644 --- a/scouter.webapp/src/test/java/scouterx/webapp/framework/session/UserTokenTest.java +++ b/scouter.webapp/src/test/java/scouterx/webapp/framework/session/UserTokenTest.java @@ -33,7 +33,7 @@ public void toStoreValue_test() { UserToken unmarshalled = UserToken.fromStoreValue(toStoreValue, token.getServerId()); assertEquals(unmarshalled.getFootprintSec(), token.getFootprintSec()); - assertEquals(unmarshalled.getId(), token.getId()); + assertEquals(unmarshalled.getUserId(), token.getUserId()); assertEquals(unmarshalled.getToken(), token.getToken()); } @@ -45,7 +45,7 @@ public void toBearerToken_test() { assertTrue(token.getFootprintSec() > 0); assertTrue(unmarshalled.getFootprintSec() == 0); - assertEquals(unmarshalled.getId(), token.getId()); + assertEquals(unmarshalled.getUserId(), token.getUserId()); assertEquals(unmarshalled.getToken(), token.getToken()); } diff --git a/scouter.webapp/src/test/java/scouterx/webapp/layer/service/UserTokenServiceTest.java b/scouter.webapp/src/test/java/scouterx/webapp/layer/service/UserTokenServiceTest.java new file mode 100644 index 000000000..c27e2f628 --- /dev/null +++ b/scouter.webapp/src/test/java/scouterx/webapp/layer/service/UserTokenServiceTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015 the original author or authors. + * @https://github.com/scouter-project/scouter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package scouterx.webapp.layer.service; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import scouterx.lib3.tomcat.SessionIdGenerator; +import scouterx.webapp.framework.client.server.Server; +import scouterx.webapp.framework.client.server.ServerManager; +import scouterx.webapp.framework.session.UserToken; +import scouterx.webapp.model.scouter.SUser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +/** + * @author Gun Lee (gunlee01@gmail.com) on 2018. 3. 11. + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ServerManager.class}) +public class UserTokenServiceTest { + String vutUserId = "junit-user"; + + @Mock + CustomKvStoreService customKvStoreService; + @Mock + Server server; + @Mock + ServerManager serverManager; + + SessionIdGenerator sessionIdGenerator = new SessionIdGenerator(); + + @InjectMocks + UserTokenService sut = new UserTokenService(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mockStatic(ServerManager.class); + when(ServerManager.getInstance()).thenReturn(serverManager); + } + + @Test + public void publishToken() { + String bearer = sut.publishToken(server, new SUser(vutUserId)); + UserToken fromBearer = UserToken.fromBearerToken(bearer); + assertEquals(vutUserId, fromBearer.getUserId()); + } + + @Test + public void validateToken() { + String bearer = sut.publishToken(server, new SUser(vutUserId)); + UserToken fromBearer = UserToken.fromBearerToken(bearer); + + sut.validateToken(fromBearer); + } + + @Test + public void getAndMergeToStoredValue() { + UserToken userToken = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + + UserToken token0 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + UserToken token1 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + UserToken token2 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + + String tokens = token0.toStoreValue() + ":" + token1.toStoreValue() + ":" + token2.toStoreValue(); + String merged = sut.mergeStoredTokensWith(tokens, userToken); + + assertTrue(merged.contains(userToken.getToken())); + } + + @Test + public void getAndMergeToStoredValue_with_expired_tokens() { + UserToken userToken = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + + UserToken token0 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + UserToken token1 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + UserToken token2 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + + token0.setFootprintSec(System.currentTimeMillis()/1000L - sut.sessionExpireSec - 1); + token1.setFootprintSec(System.currentTimeMillis()/1000L - sut.sessionExpireSec - 1); + token2.setFootprintSec(System.currentTimeMillis()/1000L - sut.sessionExpireSec - 1); + + String tokens = token0.toStoreValue() + ":" + token1.toStoreValue() + ":" + token2.toStoreValue(); + String merged = sut.mergeStoredTokensWith(tokens, userToken); + + assertEquals(merged, userToken.toStoreValue()); + } +} \ No newline at end of file From 6bb7ded52b6bce912040dc93b2521a55a0aaf36e Mon Sep 17 00:00:00 2001 From: Gun Lee Date: Tue, 20 Mar 2018 18:38:16 +0900 Subject: [PATCH 08/24] [agent.java] add HeapTotal counter. --- .../scouter/agent/counter/task/HeapUsage.java | 18 +++++++++++------- .../lang/counters/CounterConstants.java | 1 + .../scouter/lang/counters/counters.xml | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/scouter.agent.java/src/main/java/scouter/agent/counter/task/HeapUsage.java b/scouter.agent.java/src/main/java/scouter/agent/counter/task/HeapUsage.java index 970ebf5a5..2e08a0daf 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/counter/task/HeapUsage.java +++ b/scouter.agent.java/src/main/java/scouter/agent/counter/task/HeapUsage.java @@ -34,21 +34,25 @@ public class HeapUsage { public void getHeapUsage(CounterBasket pw) { long total = Runtime.getRuntime().totalMemory(); long free = Runtime.getRuntime().freeMemory(); - float used = (float) ((total - free) / 1024. / 1024.); + + float tatalMb = (float) (total / 1024. / 1024.); + float usedMb = (float) ((total - free) / 1024. / 1024.); heapmin.add(total - free); - float usedmin = (float) (heapmin.getAvg(300) / 1024. / 1024.); + float used5MinAvgMb = (float) (heapmin.getAvg(300) / 1024. / 1024.); ListValue heapValues = new ListValue(); - heapValues.add((float) (total / 1024. / 1024.)); - heapValues.add(used); + heapValues.add(tatalMb); + heapValues.add(usedMb); PerfCounterPack p = pw.getPack(TimeTypeEnum.REALTIME); p.put(CounterConstants.JAVA_HEAP_TOT_USAGE, heapValues); - p.put(CounterConstants.JAVA_HEAP_USED, new FloatValue(used)); + p.put(CounterConstants.JAVA_HEAP_USED, new FloatValue(usedMb)); + p.put(CounterConstants.JAVA_HEAP_TOTAL, new FloatValue(tatalMb)); p = pw.getPack(TimeTypeEnum.FIVE_MIN); - p.put(CounterConstants.JAVA_HEAP_USED, new FloatValue(usedmin)); + p.put(CounterConstants.JAVA_HEAP_USED, new FloatValue(used5MinAvgMb)); + p.put(CounterConstants.JAVA_HEAP_TOTAL, new FloatValue(tatalMb)); } -} \ No newline at end of file +} diff --git a/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java b/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java index fc47750e1..fd2da7e59 100644 --- a/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java +++ b/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java @@ -71,6 +71,7 @@ public class CounterConstants { public final static String JAVA_GC_TIME = "GcTime"; public final static String JAVA_HEAP_TOT_USAGE = "HeapTotUsage"; public final static String JAVA_HEAP_USED = "HeapUsed"; + public final static String JAVA_HEAP_TOTAL = "HeapTotal"; public final static String JAVA_CPU_TIME = "CpuTime"; public final static String JAVA_PERM_USED = "PermUsed"; public final static String JAVA_PERM_PERCENT = "PermPercent"; diff --git a/scouter.common/src/main/resources/scouter/lang/counters/counters.xml b/scouter.common/src/main/resources/scouter/lang/counters/counters.xml index c713f619c..b10e828d4 100644 --- a/scouter.common/src/main/resources/scouter/lang/counters/counters.xml +++ b/scouter.common/src/main/resources/scouter/lang/counters/counters.xml @@ -36,6 +36,7 @@ + From 94febfbc8f207df562bf07845e5e25776b6e74f6 Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Tue, 20 Mar 2018 23:47:17 +0900 Subject: [PATCH 09/24] [agent.java][WIP] spring data redis - jedis support. --- .../java/scouter/agent/AgentTransformer.java | 2 + .../main/java/scouter/agent/Configure.java | 9 + .../scouter/agent/asm/redis/RedisKeyASM.java | 119 ++++ .../java/scouter/agent/trace/TraceMain.java | 206 +++---- .../java/scouter/util/ByteArrayEnumer.java | 22 + .../scouter/util/ByteArrayKeyLinkedMap.java | 521 ++++++++++++++++++ .../java/scouter/util/ByteArrayKeyMap.java | 315 +++++++++++ 7 files changed, 1094 insertions(+), 100 deletions(-) create mode 100644 scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java create mode 100644 scouter.common/src/main/java/scouter/util/ByteArrayEnumer.java create mode 100644 scouter.common/src/main/java/scouter/util/ByteArrayKeyLinkedMap.java create mode 100644 scouter.common/src/main/java/scouter/util/ByteArrayKeyMap.java diff --git a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java index e4696d746..72aa1356c 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java +++ b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java @@ -48,6 +48,7 @@ import scouter.agent.asm.asyncsupport.RequestStartAsyncASM; import scouter.agent.asm.asyncsupport.executor.ExecutorServiceASM; import scouter.agent.asm.asyncsupport.spring.SpringAsyncExecutionASM; +import scouter.agent.asm.redis.RedisKeyASM; import scouter.agent.asm.util.AsmUtil; import scouter.agent.util.AsyncRunner; import scouter.lang.conf.ConfObserver; @@ -118,6 +119,7 @@ public static void reload() { temp.add(new SpringAsyncExecutionASM()); temp.add(new CallRunnableASM()); temp.add(new ExecutorServiceASM()); + temp.add(new RedisKeyASM()); temp.add(new SpringReqMapASM()); temp.add(new HystrixCommandASM()); diff --git a/scouter.agent.java/src/main/java/scouter/agent/Configure.java b/scouter.agent.java/src/main/java/scouter/agent/Configure.java index cec9bbfc3..99329b47a 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/Configure.java +++ b/scouter.agent.java/src/main/java/scouter/agent/Configure.java @@ -526,6 +526,10 @@ public final static synchronized Configure getInstance() { @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) public String hook_async_callrunnable_scan_package_prefixes = ""; + @ConfigDesc("redis key setting patterns.\n refer to org.springframework.data.redis.core.AbstractOperations#rawKey") + @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) + public String _hook_redis_set_key_patterns = ""; + @ConfigDesc("PRE-released option before stable release!\nhook threadpool executor for tracing async processing.") public boolean hook_async_thread_pool_executor_enabled = false; @@ -557,6 +561,8 @@ public final static synchronized Configure getInstance() { @ConfigDesc("") public boolean _hook_spring_rest_enabled = true; @ConfigDesc("") + public boolean _hook_redis_enabled = true; + @ConfigDesc("") public String _hook_boot_prefix = null; @ConfigDesc("for warning a big Map type object that have a lot of entities.\n It may increase system load. be careful to enable this option.") public boolean _hook_map_impl_enabled = false; @@ -853,6 +859,8 @@ private void apply() { this.hook_async_callrunnable_scan_package_prefixes = getValue("hook_async_callrunnable_scan_package_prefixes", ""); + this._hook_redis_set_key_patterns = getValue("_hook_redis_set_key_patterns", ""); + this.hook_async_thread_pool_executor_enabled = getBoolean("hook_async_thread_pool_executor_enabled", false); this.hook_lambda_instrumentation_strategy_enabled = getBoolean("hook_lambda_instrumentation_strategy_enabled", false); @@ -970,6 +978,7 @@ private void apply() { this.alert_perm_warning_pct = getInt("alert_perm_warning_pct", 90); this._hook_spring_rest_enabled = getBoolean("_hook_spring_rest_enabled", true); + this._hook_redis_enabled = getBoolean("_hook_redis_enabled", true); this.alert_message_length = getInt("alert_message_length", 3000); this.alert_send_interval_ms = getInt("alert_send_interval_ms", 10000); diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java new file mode 100644 index 000000000..febc90bd4 --- /dev/null +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java @@ -0,0 +1,119 @@ +/* + * Copyright 2015 the original author or authors. + * @https://github.com/scouter-project/scouter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package scouter.agent.asm.redis; + +import scouter.agent.ClassDesc; +import scouter.agent.Configure; +import scouter.agent.asm.IASM; +import scouter.agent.asm.util.AsmUtil; +import scouter.agent.asm.util.HookingSet; +import scouter.agent.trace.TraceMain; +import scouter.org.objectweb.asm.ClassVisitor; +import scouter.org.objectweb.asm.MethodVisitor; +import scouter.org.objectweb.asm.Opcodes; +import scouter.org.objectweb.asm.Type; +import scouter.org.objectweb.asm.commons.LocalVariablesSorter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Gun Lee (gunlee01@gmail.com) on 2018. 3. 20. + */ +public class RedisKeyASM implements IASM, Opcodes { + private Configure conf = Configure.getInstance(); + + private static List setKeyPattern = new ArrayList(); + static { + setKeyPattern.add("org.springframework.data.redis.core.AbstractOperations.rawKey(Ljava/lang/Object;)[B"); + } + private List targetList; + + public RedisKeyASM() { + targetList = HookingSet.getHookingMethodSet(HookingSet.buildPatterns(conf._hook_redis_set_key_patterns, setKeyPattern)); + } + + public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) { + if (conf._hook_spring_rest_enabled == false) + return cv; + + for (int i = 0; i < targetList.size(); i++) { + HookingSet mset = targetList.get(i); + if (mset.classMatch.include(className)) { + return new RedisKeySetCV(cv, mset, className); + } + } + + return cv; + } +} + +class RedisKeySetCV extends ClassVisitor implements Opcodes { + String className; + HookingSet mset; + + public RedisKeySetCV(ClassVisitor cv, HookingSet mset, String className) { + super(ASM5, cv); + this.mset = mset; + this.className = className; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + if (mv == null || mset.isA(name, desc) == false) { + return mv; + } + if (AsmUtil.isSpecial(name)) { + return mv; + } + + return new KeySetMV(access, name, desc, mv); + } +} + +class KeySetMV extends LocalVariablesSorter implements Opcodes { + private static final String TRACEMAIN = TraceMain.class.getName().replace('.', '/'); + private static final String START_METHOD = "setRedisKey"; + private static final String START_SIGNATURE = "([BLjava/lang/Object;)V"; + + private String name; + private String desc; + private Type returnType; + + public KeySetMV(int access, String name, String desc, MethodVisitor mv) { + super(ASM5, access, desc, mv); + this.name = name; + this.desc = desc; + this.returnType = Type.getReturnType(desc); + } + + @Override + public void visitInsn(int opcode) { + if ((opcode >= IRETURN && opcode <= RETURN)) { + Type tp = returnType; + if (tp.getSort() == Type.ARRAY && tp.getElementType().getSort() == Type.BYTE) { + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(Opcodes.ALOAD, 1);// stat + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START_METHOD, START_SIGNATURE, false); + } + } + mv.visitInsn(opcode); + } +} \ No newline at end of file diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java index d0a05dd35..35ed6f67e 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java +++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java @@ -50,6 +50,7 @@ import scouter.lang.step.ThreadCallPossibleStep; import scouter.lang.value.MapValue; import scouter.util.ArrayUtil; +import scouter.util.ByteArrayKeyLinkedMap; import scouter.util.HashUtil; import scouter.util.Hexa32; import scouter.util.IPUtil; @@ -99,7 +100,7 @@ public static Object startHttpService(Object req, Object res) { if (ctx != null) { return null; } - if(TraceContextManager.startForceDiscard()) { + if (TraceContextManager.startForceDiscard()) { return null; } return startHttp(req, res); @@ -115,7 +116,7 @@ public static Object startHttpFilter(Object req, Object res) { if (ctx != null) { return null; } - if(TraceContextManager.startForceDiscard()) { + if (TraceContextManager.startForceDiscard()) { return null; } return startHttp(req, res); @@ -127,20 +128,20 @@ public static Object startHttpFilter(Object req, Object res) { public static Object reject(Object stat, Object req, Object res) { Configure conf = Configure.getInstance(); - if(plController != null) { - if (stat == null || req == null || res == null) + if (plController != null) { + if (stat == null || req == null || res == null) return null; - if (http == null) { + if (http == null) { initHttp(req); } - Stat stat0 = (Stat) stat; + Stat stat0 = (Stat) stat; if (stat0.isStaticContents) { return null; } - if(plController.reject(stat0.ctx, req, res,http)) { - endHttpService(stat0, REJECT); - return REJECT; - } + if (plController.reject(stat0.ctx, req, res, http)) { + endHttpService(stat0, REJECT); + return REJECT; + } } if (conf.control_reject_service_enabled) { if (stat == null || req == null || res == null) @@ -151,8 +152,8 @@ public static Object reject(Object stat, Object req, Object res) { Stat stat0 = (Stat) stat; if (stat0.isStaticContents) return null; - - + + // reject by customized plugin if (PluginHttpServiceTrace.reject(stat0.ctx, req, res) // reject by control_reject_service_max_count @@ -252,8 +253,8 @@ private static Object startHttp(Object req, Object res) { PluginHttpServiceTrace.start(ctx, req, res); } - if(plController != null) { - plController.start(ctx, req, res); + if (plController != null) { + plController.start(ctx, req, res); } } return stat; @@ -268,7 +269,7 @@ private static void initHttp(Object req) { } public static void endHttpService(Object stat, Throwable thr) { - if(TraceContextManager.isForceDiscarded()) { + if (TraceContextManager.isForceDiscarded()) { TraceContextManager.clearForceDiscard(); return; } @@ -281,7 +282,7 @@ public static void endHttpService(Object stat, Throwable thr) { TraceContext ctx = stat0.ctx; //wait on async servlet completion - if(!ctx.asyncServletStarted) { + if (!ctx.asyncServletStarted) { endHttpServiceFinal(ctx, stat0.req, stat0.res, thr); } else { HashedMessageStep step = new HashedMessageStep(); @@ -301,7 +302,7 @@ public static void endHttpService(Object stat, Throwable thr) { } public static void endHttpServiceFinal(TraceContext ctx, Object request, Object response, Throwable thr) { - if(TraceContextManager.isForceDiscarded()) { + if (TraceContextManager.isForceDiscarded()) { TraceContextManager.clearForceDiscard(); return; } @@ -335,17 +336,17 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object if (ctx.xType != XLogTypes.ASYNCSERVLET_DISPATCHED_SERVICE) { PluginHttpServiceTrace.end(ctx, request, response); } - if(plController != null) { - plController.end(ctx, request, response); + if (plController != null) { + plController.end(ctx, request, response); } //profile rs - if(conf.trace_rs_leak_enabled && ctx.unclosedRsMap.size() > 0) { + if (conf.trace_rs_leak_enabled && ctx.unclosedRsMap.size() > 0) { MapValue mv = new MapValue(); mv.put(AlertPack.HASH_FLAG + TextTypes.SERVICE + "_service-name", ctx.serviceHash); - if(conf.profile_fullstack_rs_leak_enabled) { + if (conf.profile_fullstack_rs_leak_enabled) { String message = ctx.unclosedRsMap.values().nextElement(); - if(message != null) { + if (message != null) { message = "ResultSet Leak suspected!\n" + message; HashedMessageStep step = new HashedMessageStep(); step.hash = DataProxy.sendHashedMessage(message); @@ -358,13 +359,13 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object } //profile stmt - if(conf.trace_stmt_leak_enabled && ctx.unclosedStmtMap.size() > 0) { + if (conf.trace_stmt_leak_enabled && ctx.unclosedStmtMap.size() > 0) { MapValue mv = new MapValue(); mv.put(AlertPack.HASH_FLAG + TextTypes.SERVICE + "_service-name", ctx.serviceHash); - if(conf.profile_fullstack_stmt_leak_enabled) { + if (conf.profile_fullstack_stmt_leak_enabled) { String message = ctx.unclosedStmtMap.values().nextElement(); - if(message != null) { + if (message != null) { message = "Statement Leak suspected!\n" + message; HashedMessageStep step = new HashedMessageStep(); step.hash = DataProxy.sendHashedMessage(message); @@ -390,12 +391,12 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object pack.xType = ctx.xType; //default 0 : XLogType.WEB_SERVICE pack.txid = ctx.txid; pack.gxid = ctx.gxid; - if(ctx.latestCpu > 0) { + if (ctx.latestCpu > 0) { pack.cpu = (int) (ctx.latestCpu - ctx.startCpu); } else { pack.cpu = (int) (SysJMX.getCurrentThreadCPU() - ctx.startCpu); } - if(ctx.latestBytes > 0) { + if (ctx.latestBytes > 0) { pack.kbytes = (int) ((ctx.latestBytes - ctx.bytes) / 1024.0d); } else { pack.kbytes = (int) ((SysJMX.getCurrentThreadAllocBytes() - ctx.bytes) / 1024.0d); @@ -406,7 +407,7 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object pack.ipaddr = IPUtil.toBytes(ctx.remoteIp); pack.userid = ctx.userid; - if(ctx.hasDumpStack) { + if (ctx.hasDumpStack) { pack.hasDump = 1; } else { pack.hasDump = 0; @@ -427,7 +428,7 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object sb.append(emsg).append("\n"); ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines); Throwable thrCause = thr.getCause(); - if(thrCause != null) { + if (thrCause != null) { thr = thrCause; while (thr != null) { sb.append("\nCause...\n"); @@ -440,13 +441,13 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object pack.error = DataProxy.sendError(emsg); ServiceSummary.getInstance().process(thr, pack.error, ctx.serviceHash, ctx.txid, 0, 0); } - } else if (ctx.userTransaction > 0 && conf.xlog_error_check_user_transaction_enabled) { + } else if (ctx.userTransaction > 0 && conf.xlog_error_check_user_transaction_enabled) { pack.error = DataProxy.sendError("UserTransaction missing commit/rollback Error"); ServiceSummary.getInstance().process(userTxNotClose, pack.error, ctx.serviceHash, ctx.txid, 0, 0); - } else if(conf.trace_rs_leak_enabled && ctx.unclosedRsMap.size() > 0) { + } else if (conf.trace_rs_leak_enabled && ctx.unclosedRsMap.size() > 0) { pack.error = DataProxy.sendError("ResultSet Leak suspected!"); ServiceSummary.getInstance().process(resultSetLeakSuspect, pack.error, ctx.serviceHash, ctx.txid, 0, 0); - } else if(conf.trace_stmt_leak_enabled && ctx.unclosedStmtMap.size() > 0) { + } else if (conf.trace_stmt_leak_enabled && ctx.unclosedStmtMap.size() > 0) { pack.error = DataProxy.sendError("Statement Leak suspected!"); ServiceSummary.getInstance().process(statementLeakSuspect, pack.error, ctx.serviceHash, ctx.txid, 0, 0); } @@ -454,14 +455,14 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object //check xlog sampling XLogDiscard discardMode = pack.error != 0 ? XLogDiscard.NONE : XLogSampler.getInstance().evaluateXLogDiscard(pack.elapsed, ctx.serviceName); //check xlog discard pattern - if(XLogSampler.getInstance().isDiscardServicePattern(ctx.serviceName)) { + if (XLogSampler.getInstance().isDiscardServicePattern(ctx.serviceName)) { discardMode = XLogDiscard.DISCARD_ALL; if (pack.error != 0 && conf.xlog_discard_service_show_error) { discardMode = XLogDiscard.NONE; } } - ctx.profile.close(discardMode==XLogDiscard.NONE ? true : false); + ctx.profile.close(discardMode == XLogDiscard.NONE ? true : false); if (ctx.group != null) { pack.group = DataProxy.sendGroup(ctx.group); } @@ -581,7 +582,7 @@ public static Object startService(String name, String className, String methodNa if (ctx != null) { return null; } - if(TraceContextManager.startForceDiscard()) { + if (TraceContextManager.startForceDiscard()) { return null; } @@ -674,7 +675,7 @@ public static void endService(Object stat, Object returnValue, Throwable thr) { //check xlog sampling XLogDiscard discardMode = pack.error != 0 ? XLogDiscard.NONE : XLogSampler.getInstance().evaluateXLogDiscard(pack.elapsed, ctx.serviceName); - ctx.profile.close(discardMode==XLogDiscard.NONE ? true : false); + ctx.profile.close(discardMode == XLogDiscard.NONE ? true : false); DataProxy.sendServiceName(ctx.serviceHash, ctx.serviceName); pack.service = ctx.serviceHash; pack.threadNameHash = DataProxy.sendHashedMessage(ctx.threadName); @@ -731,7 +732,7 @@ private static int errorCheck(TraceContext ctx, Throwable thr) { sb.append(emsg).append("\n"); ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines); Throwable thrCause = thr.getCause(); - if(thrCause != null) { + if (thrCause != null) { thr = thrCause; while (thr != null) { sb.append("\nCause...\n"); @@ -743,7 +744,7 @@ private static int errorCheck(TraceContext ctx, Throwable thr) { } error = DataProxy.sendError(emsg); ServiceSummary.getInstance().process(thr, error, ctx.serviceHash, ctx.txid, 0, 0); - } else if (ctx.userTransaction > 0 && conf.xlog_error_check_user_transaction_enabled) { + } else if (ctx.userTransaction > 0 && conf.xlog_error_check_user_transaction_enabled) { error = DataProxy.sendError("Missing Commit/Rollback Error"); ServiceSummary.getInstance().process(userTxNotClose, error, ctx.serviceHash, ctx.txid, 0, 0); } @@ -849,7 +850,7 @@ public static Object startMethod(int hash, String classMethod) { return null; } - if(TraceContextManager.isForceDiscarded()) { + if (TraceContextManager.isForceDiscarded()) { return null; } @@ -858,16 +859,16 @@ public static Object startMethod(int hash, String classMethod) { //System.out.println("[Scouter][HookMethodCtxNull]" + classMethod); if (conf._trace_auto_service_enabled) { Object localContext = startService(classMethod, null, null, null, null, null, XLogTypes.APP_SERVICE); - if (localContext != null) { - //service start - ((LocalContext) localContext).service = true; - if (conf._trace_auto_service_backstack_enabled) { - String stack = ThreadUtil.getStackTrace(Thread.currentThread().getStackTrace(), 2); - AutoServiceStartAnalyzer.put(classMethod, stack); - MessageStep m = new MessageStep(); - m.message = "SERVICE BACKSTACK:\n" + stack; - ((LocalContext) localContext).context.profile.add(m); - } + if (localContext != null) { + //service start + ((LocalContext) localContext).service = true; + if (conf._trace_auto_service_backstack_enabled) { + String stack = ThreadUtil.getStackTrace(Thread.currentThread().getStackTrace(), 2); + AutoServiceStartAnalyzer.put(classMethod, stack); + MessageStep m = new MessageStep(); + m.message = "SERVICE BACKSTACK:\n" + stack; + ((LocalContext) localContext).context.profile.add(m); + } } return localContext; } @@ -908,7 +909,7 @@ public static void setSpringControllerName(String name) { TraceContext ctx = TraceContextManager.getContext(); if (ctx == null || name == null) return; - if(!ctx.alreadySetControllerName) { + if (!ctx.alreadySetControllerName) { ctx.alreadySetControllerName = true; ctx.serviceName = name; ctx.serviceHash = HashUtil.hash(name); @@ -919,13 +920,13 @@ public static void startSpringControllerMethod(String className, String methodNa TraceContext ctx = TraceContextManager.getContext(); if (ctx == null) return; - if(conf.profile_spring_controller_method_parameter_enabled) { + if (conf.profile_spring_controller_method_parameter_enabled) { if (arg == null) { return; } int start_time = (int) (System.currentTimeMillis() - ctx.startTime); - for(int i=0; i caller txid : " + id.caller + "=" + Hexa32.toString32(id.caller) - + " ctx.txid : " + ctx.txid + "=" + Hexa32.toString32(ctx.txid) - + " id.callee : " + id.callee + "=" + Hexa32.toString32(id.callee) + + " ctx.txid : " + ctx.txid + "=" + Hexa32.toString32(ctx.txid) + + " id.callee : " + id.callee + "=" + Hexa32.toString32(id.callee) + " id.thread : " + id.callerThreadId + " current.thread : " + Thread.currentThread().getName() + "=" + Thread.currentThread().getId()); return null; } } - LocalContext localContext = (LocalContext)startService(fullName, className, methodName, methodDesc, _this, arg, XLogTypes.BACK_THREAD2); + LocalContext localContext = (LocalContext) startService(fullName, className, methodName, methodDesc, _this, arg, XLogTypes.BACK_THREAD2); if (localContext == null) { return null; } localContext.service = true; - if(id.gxid != 0) localContext.context.gxid = id.gxid; - if(id.callee != 0) localContext.context.txid = id.callee; - if(id.caller != 0) localContext.context.caller = id.caller; + if (id.gxid != 0) localContext.context.gxid = id.gxid; + if (id.callee != 0) localContext.context.txid = id.callee; + if (id.caller != 0) localContext.context.caller = id.caller; String serviceName = StringUtil.removeLastString(className, '/') + "#" + methodName + "() -- " + fullName; serviceName = serviceName.replace("$ByteBuddy", ""); @@ -1110,7 +1111,7 @@ public static Object startAsyncPossibleService(Object keyObject, String fullName localContext.context.serviceHash = HashUtil.hash(serviceName); localContext.context.serviceName = serviceName; - if(id.tcStep != null) { + if (id.tcStep != null) { id.tcStep.threaded = 1; id.tcStep.hash = DataProxy.sendApicall(serviceName); } @@ -1139,9 +1140,9 @@ public static void endAsyncPossibleService(Object oRtn, Object oLocalContext, Th public static void springAsyncExecutionSubmit(Object _this, Callable callable) { try { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; + if (ctx == null) return; - if(TransferMap.get(System.identityHashCode(callable)) != null) { + if (TransferMap.get(System.identityHashCode(callable)) != null) { return; } @@ -1170,16 +1171,16 @@ public static void springAsyncExecutionSubmit(Object _this, Callable callable) { public static void springAsyncDetermineExecutor(Method m) { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; - if(m == null) return; + if (ctx == null) return; + if (m == null) return; ctx.lastThreadCallName = m.getDeclaringClass().getName() + "#" + m.getName() + "()"; } public static void executorServiceSubmitted(Object callRunnable) { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; - if(callRunnable == null) return; + if (ctx == null) return; + if (callRunnable == null) return; ctx.lastThreadCallName = callRunnable.getClass().getName(); } @@ -1187,10 +1188,10 @@ public static void executorServiceSubmitted(Object callRunnable) { public static void executorServiceExecuted(Object callRunnable) { try { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; - if(callRunnable == null) return; + if (ctx == null) return; + if (callRunnable == null) return; - if(TransferMap.get(System.identityHashCode(callRunnable)) != null) { + if (TransferMap.get(System.identityHashCode(callRunnable)) != null) { return; } @@ -1238,32 +1239,32 @@ public static Object callRunnableCallInvoked(Object callRunnableObj) { return null; } - if(ctx != null) { - if(ctx.txid == id.caller) { + if (ctx != null) { + if (ctx.txid == id.caller) { return null; } else { Logger.trace("B110 - recevieAsyncPossibleStep -> caller txid : " + id.caller + "=" + Hexa32.toString32(id.caller) - + " ctx.txid : " + ctx.txid + "=" + Hexa32.toString32(ctx.txid) - + " id.callee : " + id.callee + "=" + Hexa32.toString32(id.callee) + + " ctx.txid : " + ctx.txid + "=" + Hexa32.toString32(ctx.txid) + + " id.callee : " + id.callee + "=" + Hexa32.toString32(id.callee) + " id.thread : " + id.callerThreadId + " current.thread : " + Thread.currentThread().getName() + "=" + Thread.currentThread().getId()); return null; } } - if(id.tcStep != null) { + if (id.tcStep != null) { id.tcStep.threaded = 1; } - LocalContext localContext = (LocalContext)startService(id.tcStep.nameTemp, null, null, null, null, null, XLogTypes.BACK_THREAD2); + LocalContext localContext = (LocalContext) startService(id.tcStep.nameTemp, null, null, null, null, null, XLogTypes.BACK_THREAD2); if (localContext == null) { return null; } localContext.service = true; - if(id.gxid != 0) localContext.context.gxid = id.gxid; - if(id.callee != 0) localContext.context.txid = id.callee; - if(id.caller != 0) localContext.context.caller = id.caller; + if (id.gxid != 0) localContext.context.gxid = id.gxid; + if (id.callee != 0) localContext.context.txid = id.callee; + if (id.caller != 0) localContext.context.caller = id.caller; return localContext; } catch (Throwable t) { @@ -1280,9 +1281,9 @@ public static void callRunnableCallEnd(Object oRtn, Object oLocalContext, Throwa public static void hystrixPrepareInvoked(Object hystrixCommand) { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; + if (ctx == null) return; - if(TransferMap.get(System.identityHashCode(hystrixCommand)) != null) { + if (TransferMap.get(System.identityHashCode(hystrixCommand)) != null) { return; } Class clazz = hystrixCommand.getClass(); @@ -1318,9 +1319,9 @@ public static void hystrixPrepareInvoked(Object hystrixCommand) { public static void callRunnableInitInvoked(Object callRunnableObj) { try { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; + if (ctx == null) return; - if(TransferMap.get(System.identityHashCode(callRunnableObj)) != null) { + if (TransferMap.get(System.identityHashCode(callRunnableObj)) != null) { return; } @@ -1351,13 +1352,13 @@ public static void endExceptionConstructor(String className, String methodDesc, TraceContext ctx = TraceContextManager.getContext(); if (ctx == null) return; - if(!(this0 instanceof Throwable)) { + if (!(this0 instanceof Throwable)) { return; } if (ctx.error != 0) { return; } - Throwable t = (Throwable)this0; + Throwable t = (Throwable) this0; String msg = t.getMessage(); if (msg == null) { @@ -1384,7 +1385,7 @@ public static void endExceptionConstructor(String className, String methodDesc, public static StringBuilder appendParentClassName(Class clazz, StringBuilder sb) { Class superClazz = clazz.getSuperclass(); - if(superClazz != null) { + if (superClazz != null) { sb.append(",").append(superClazz.getName()); return appendParentClassName(superClazz, sb); } else { @@ -1393,7 +1394,7 @@ public static StringBuilder appendParentClassName(Class clazz, StringBuilder sb) } public static String buildClassHierarchyConcatString(Class clazz) { - if(clazz == null) return null; + if (clazz == null) return null; StringBuilder sb = new StringBuilder(clazz.getName()); appendParentClassName(clazz, sb); return sb.toString(); @@ -1406,9 +1407,9 @@ public static void startExceptionHandler(String className, String methodName, St if (args == null || args.length == 0) return; Throwable t = null; - for(int i=0; i redisKeyMap =new ByteArrayKeyLinkedMap().setMax(100); + public static void setRedisKey(byte[] barr, Object key) { + redisKeyMap.put(barr, key.toString()); + } } diff --git a/scouter.common/src/main/java/scouter/util/ByteArrayEnumer.java b/scouter.common/src/main/java/scouter/util/ByteArrayEnumer.java new file mode 100644 index 000000000..8cd20b859 --- /dev/null +++ b/scouter.common/src/main/java/scouter/util/ByteArrayEnumer.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015 the original author or authors. + * @https://github.com/scouter-project/scouter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package scouter.util; +public interface ByteArrayEnumer { + public boolean hasMoreElements(); + public byte[] nextKey(); +} diff --git a/scouter.common/src/main/java/scouter/util/ByteArrayKeyLinkedMap.java b/scouter.common/src/main/java/scouter/util/ByteArrayKeyLinkedMap.java new file mode 100644 index 000000000..9ab29879e --- /dev/null +++ b/scouter.common/src/main/java/scouter/util/ByteArrayKeyLinkedMap.java @@ -0,0 +1,521 @@ +/* + * Copyright 2015 the original author or authors. + * @https://github.com/scouter-project/scouter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package scouter.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * @author Gun Lee (gunlee01@gmail.com) + * + */ +public class ByteArrayKeyLinkedMap { + private static final int DEFAULT_CAPACITY = 101; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + private ByteArrayKeyLinkedEntry table[]; + private ByteArrayKeyLinkedEntry header; + private int count; + private int threshold; + private float loadFactor; + + public ByteArrayKeyLinkedMap(int initCapacity, float loadFactor) { + if (initCapacity < 0) + throw new RuntimeException("Capacity Error: " + initCapacity); + if (loadFactor <= 0) + throw new RuntimeException("Load Count Error: " + loadFactor); + if (initCapacity == 0) + initCapacity = 1; + this.loadFactor = loadFactor; + this.table = new ByteArrayKeyLinkedEntry[initCapacity]; + this.header = new ByteArrayKeyLinkedEntry(new byte[0], null, null); + this.header.link_next = header.link_prev = header; + threshold = (int) (initCapacity * loadFactor); + } + + public ByteArrayKeyLinkedMap() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + public int size() { + return count; + } + + public byte[][] keyArray() { + byte[][] _keys = new byte[this.size()][]; + ByteArrayEnumer en = this.keys(); + for (int i = 0; i < _keys.length; i++) + _keys[i] = en.nextKey(); + return _keys; + } + + public synchronized ByteArrayEnumer keys() { + return new Enumer(TYPE.KEYS); + } + + public synchronized Enumeration values() { + return new Enumer(TYPE.VALUES); + } + + public synchronized Enumeration> entries() { + return new Enumer(TYPE.ENTRIES); + } + + public synchronized boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } + ByteArrayKeyLinkedEntry tab[] = table; + int i = tab.length; + while (i-- > 0) { + for (ByteArrayKeyLinkedEntry e = tab[i]; e != null; e = e.next) { + if (CompareUtil.equals(e.value, value)) { + return true; + } + } + } + return false; + } + + public synchronized boolean containsKey(byte[] key) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index]; e != null; e = e.next) { + if (CompareUtil.equals(e.key, key)) { + return true; + } + } + return false; + } + + public synchronized V get(byte[] key) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index]; e != null; e = e.next) { + if (CompareUtil.equals(e.key, key)) { + return e.value; + } + } + return null; + } + + public synchronized byte[] getFirstKey() { + return this.header.link_next.key; + } + + public synchronized byte[] getLastKey() { + return this.header.link_prev.key; + } + + public synchronized V getFirstValue() { + return this.header.link_next.value; + } + + public synchronized V getLastValue() { + return this.header.link_prev.value; + } + + protected void overflowed(byte[] key, V value) { + } + + protected V create(byte[] key) { + throw new RuntimeException("not implemented create()"); + } + + public V intern(byte[] key) { + return _intern(key, MODE.LAST); + } + + private synchronized V _intern(byte[] key, MODE m) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index]; e != null; e = e.next) { + if (CompareUtil.equals(e.key, key)) { + return e.value; + } + } + V value = create(key); + if (value == null) + return null; + + if (max > 0) { + switch (m) { + case FORCE_FIRST: + case FIRST: + while (count >= max) { + byte[] k = header.link_prev.key; + V v = remove(k); + overflowed(k, v); + } + break; + case FORCE_LAST: + case LAST: + while (count >= max) { + byte[] k = header.link_next.key; + V v = remove(k); + overflowed(k, v); + } + break; + } + } + if (count >= threshold) { + rehash(); + tab = table; + index = hash(key) % tab.length; + } + ByteArrayKeyLinkedEntry e = new ByteArrayKeyLinkedEntry(key, value, tab[index]); + tab[index] = e; + switch (m) { + case FORCE_FIRST: + case FIRST: + chain(header, header.link_next, e); + break; + case FORCE_LAST: + case LAST: + chain(header.link_prev, header, e); + break; + } + count++; + return value; + } + + private int hash(byte[] key) { + return Arrays.hashCode(key) & Integer.MAX_VALUE; + } + + protected void rehash() { + int oldCapacity = table.length; + ByteArrayKeyLinkedEntry oldMap[] = table; + int newCapacity = oldCapacity * 2 + 1; + ByteArrayKeyLinkedEntry newMap[] = new ByteArrayKeyLinkedEntry[newCapacity]; + threshold = (int) (newCapacity * loadFactor); + table = newMap; + for (int i = oldCapacity; i-- > 0;) { + ByteArrayKeyLinkedEntry old = oldMap[i]; + while (old != null) { + ByteArrayKeyLinkedEntry e = old; + old = old.next; + byte[] key = e.key; + int index = hash(key) % newCapacity; + e.next = newMap[index]; + newMap[index] = e; + } + } + } + + private int max; + + public ByteArrayKeyLinkedMap setMax(int max) { + this.max = max; + return this; + } + + private static enum MODE { + FORCE_FIRST, FORCE_LAST, FIRST, LAST + }; + + public V put(byte[] key, V value) { + return _put(key, value, MODE.LAST); + } + + public V putLast(byte[] key, V value) { + return _put(key, value, MODE.FORCE_LAST); + } + + public V putFirst(byte[] key, V value) { + return _put(key, value, MODE.FORCE_FIRST); + } + + private synchronized V _put(byte[] key, V value, MODE m) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index]; e != null; e = e.next) { + if (CompareUtil.equals(e.key, key)) { + V old = e.value; + e.value = value; + switch (m) { + case FORCE_FIRST: + if (header.link_next != e) { + unchain(e); + chain(header, header.link_next, e); + } + break; + case FORCE_LAST: + if (header.link_prev != e) { + unchain(e); + chain(header.link_prev, header, e); + } + break; + } + return old; + } + } + if (max > 0) { + switch (m) { + case FORCE_FIRST: + case FIRST: + while (count >= max) { + // removeLast(); + byte[] k = header.link_prev.key; + V v = remove(k); + overflowed(k, v); + } + break; + case FORCE_LAST: + case LAST: + while (count >= max) { + // removeFirst(); + byte[] k = header.link_next.key; + V v = remove(k); + overflowed(k, v); + + } + break; + } + } + if (count >= threshold) { + rehash(); + tab = table; + index = hash(key) % tab.length; + } + ByteArrayKeyLinkedEntry e = new ByteArrayKeyLinkedEntry(key, value, tab[index]); + tab[index] = e; + switch (m) { + case FORCE_FIRST: + case FIRST: + chain(header, header.link_next, e); + break; + case FORCE_LAST: + case LAST: + chain(header.link_prev, header, e); + break; + } + count++; + return null; + } + + public synchronized V remove(byte[] key) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) { + if (CompareUtil.equals(e.key, key)) { + if (prev != null) { + prev.next = e.next; + } else { + tab[index] = e.next; + } + count--; + V oldValue = e.value; + e.value = null; + // + unchain(e); + return oldValue; + } + } + return null; + } + + public synchronized V removeFirst() { + if (isEmpty()) + return null; + return remove(header.link_next.key); + } + + public synchronized V removeLast() { + if (isEmpty()) + return null; + return remove(header.link_prev.key); + } + + public boolean isEmpty() { + return size() == 0; + } + + public synchronized void clear() { + ByteArrayKeyLinkedEntry tab[] = table; + for (int index = tab.length; --index >= 0;) + tab[index] = null; + this.header.link_next = header.link_prev = header; + count = 0; + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + Enumeration it = entries(); + buf.append("{"); + for (int i = 0; it.hasMoreElements(); i++) { + ByteArrayKeyLinkedEntry e = (ByteArrayKeyLinkedEntry) (it.nextElement()); + if (i > 0) + buf.append(", "); + buf.append(Arrays.toString(e.getKey()) + "=" + e.getValue()); + } + buf.append("}"); + return buf.toString(); + } + + public String toFormatString() { + StringBuffer buf = new StringBuffer(); + Enumeration it = entries(); + buf.append("{\n"); + while (it.hasMoreElements()) { + ByteArrayKeyLinkedEntry e = (ByteArrayKeyLinkedEntry) it.nextElement(); + buf.append("\t").append(Arrays.toString(e.getKey()) + "=" + e.getValue()).append("\n"); + } + buf.append("}"); + return buf.toString(); + } + + private enum TYPE { + KEYS, VALUES, ENTRIES + } + + private class Enumer implements Enumeration, ByteArrayEnumer { + TYPE type; + ByteArrayKeyLinkedEntry entry = ByteArrayKeyLinkedMap.this.header.link_next; + + Enumer(TYPE type) { + this.type = type; + } + + public boolean hasMoreElements() { + return header != entry && entry != null; + } + + public V nextElement() { + if (hasMoreElements()) { + ByteArrayKeyLinkedEntry e = entry; + entry = e.link_next; + switch (type) { + case KEYS: + return (V) e.key; + case VALUES: + return (V) e.value; + default: + return (V) e; + } + } + throw new NoSuchElementException("no more next"); + } + + public byte[] nextKey() { + if (hasMoreElements()) { + ByteArrayKeyLinkedEntry e = entry; + entry = e.link_next; + return e.key; + } + throw new NoSuchElementException("no more next"); + } + } + + private void chain(ByteArrayKeyLinkedEntry link_prev, ByteArrayKeyLinkedEntry link_next, ByteArrayKeyLinkedEntry e) { + e.link_prev = link_prev; + e.link_next = link_next; + link_prev.link_next = e; + link_next.link_prev = e; + } + + private void unchain(ByteArrayKeyLinkedEntry e) { + e.link_prev.link_next = e.link_next; + e.link_next.link_prev = e.link_prev; + e.link_prev = null; + e.link_next = null; + } + + public static class ByteArrayKeyLinkedEntry { + byte[] key; + V value; + ByteArrayKeyLinkedEntry next; + ByteArrayKeyLinkedEntry link_next, link_prev; + + protected ByteArrayKeyLinkedEntry(byte[] key, V value, ByteArrayKeyLinkedEntry next) { + this.key = key; + this.value = value; + this.next = next; + } + + protected Object clone() { + return new ByteArrayKeyLinkedEntry(key, value, (next == null ? null : (ByteArrayKeyLinkedEntry) next.clone())); + } + + public byte[] getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + if (value == null) + throw new NullPointerException(); + V oldValue = this.value; + this.value = value; + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof ByteArrayKeyLinkedMap.ByteArrayKeyLinkedEntry)) + return false; + ByteArrayKeyLinkedEntry e = (ByteArrayKeyLinkedEntry) o; + return CompareUtil.equals(e.key, key) && CompareUtil.equals(e.value, value); + } + + public int hashCode() { + return Arrays.hashCode(key) ^ (value == null ? 0 : value.hashCode()); + } + + public String toString() { + return key + "=" + value; + } + } + + public synchronized void sort(Comparator> c) { + ArrayList> list = new ArrayList>(this.size()); + Enumeration> en = this.entries(); + while (en.hasMoreElements()) { + list.add(en.nextElement()); + } + Collections.sort(list, c); + this.clear(); + for (int i = 0; i < list.size(); i++) { + ByteArrayKeyLinkedEntry e = list.get(i); + this.put(e.getKey(), e.getValue()); + } + } + + public static void main(String[] args) { + ByteArrayKeyLinkedMap m = new ByteArrayKeyLinkedMap(); + for (int i = 0; i < 10; i++) { + byte[] b = new byte[1]; + b[0] = new Byte(String.valueOf(i)); + m.put(b, i); + } + System.out.println(m); + m.sort(new Comparator>() { + @Override + public int compare(ByteArrayKeyLinkedEntry o1, ByteArrayKeyLinkedEntry o2) { + return CompareUtil.compareTo(o2.getKey(), o1.getKey()); + } + }); + System.out.println(m); + } + +} diff --git a/scouter.common/src/main/java/scouter/util/ByteArrayKeyMap.java b/scouter.common/src/main/java/scouter/util/ByteArrayKeyMap.java new file mode 100644 index 000000000..03f9438e4 --- /dev/null +++ b/scouter.common/src/main/java/scouter/util/ByteArrayKeyMap.java @@ -0,0 +1,315 @@ +/* + * Copyright 2015 the original author or authors. + * @https://github.com/scouter-project/scouter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package scouter.util; + +import java.util.Arrays; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * @author Gun Lee (gunlee01@gmail.com) + * + */ +public class ByteArrayKeyMap { + private static final int DEFAULT_CAPACITY = 101; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + private ByteArrayKeyEntry table[]; + private int count; + private int threshold; + private float loadFactor; + + public ByteArrayKeyMap(int initCapacity, float loadFactor) { + if (initCapacity < 0) + throw new RuntimeException("Capacity Error: " + initCapacity); + if (loadFactor <= 0) + throw new RuntimeException("Load Count Error: " + loadFactor); + if (initCapacity == 0) + initCapacity = 1; + this.loadFactor = loadFactor; + table = new ByteArrayKeyEntry[initCapacity]; + threshold = (int) (initCapacity * loadFactor); + } + + public ByteArrayKeyMap() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + public int size() { + return count; + } + + public synchronized ByteArrayEnumer keys() { + return new Enumer(TYPE.KEYS); + } + + public synchronized Enumeration values() { + return new Enumer(TYPE.VALUES); + } + + public synchronized Enumeration> entries() { + return new Enumer(TYPE.ENTRIES); + } + + public synchronized boolean containsValue(V value) { + if (value == null) { + return false; + } + ByteArrayKeyEntry tab[] = table; + int i = tab.length; + while (i-- > 0) { + for (ByteArrayKeyEntry e = tab[i]; e != null; e = e.next) { + if (CompareUtil.equals(e.value, value)) { + return true; + } + } + } + return false; + } + + public synchronized boolean containsKey(byte[] key) { + ByteArrayKeyEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyEntry e = tab[index]; e != null; e = e.next) { + if (e.key == key) { + return true; + } + } + return false; + } + + private int hash(byte[] h) { + return Arrays.hashCode(h) & Integer.MAX_VALUE; + } + + public synchronized V get(byte[] key) { + ByteArrayKeyEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyEntry e = tab[index]; e != null; e = e.next) { + if (e.key == key) { + return e.value; + } + } + return null; + } + + protected void rehash() { + int oldCapacity = table.length; + ByteArrayKeyEntry oldMap[] = table; + int newCapacity = oldCapacity * 2 + 1; + ByteArrayKeyEntry newMap[] = new ByteArrayKeyEntry[newCapacity]; + threshold = (int) (newCapacity * loadFactor); + table = newMap; + for (int i = oldCapacity; i-- > 0;) { + for (ByteArrayKeyEntry old = oldMap[i]; old != null;) { + ByteArrayKeyEntry e = old; + old = old.next; + int index = hash(e.key) % newCapacity; + e.next = newMap[index]; + newMap[index] = e; + } + } + } + + public synchronized byte[][] keyArray() { + byte[][] _keys = new byte[this.size()][]; + ByteArrayEnumer en = this.keys(); + for (int i = 0; i < _keys.length; i++) + _keys[i] = en.nextKey(); + return _keys; + } + + public synchronized V put(byte[] key, V value) { + ByteArrayKeyEntry tab[] = table; + int _hash = hash(key); + int index = _hash % tab.length; + for (ByteArrayKeyEntry e = tab[index]; e != null; e = e.next) { + if (e.key == key) { + V old = e.value; + e.value = value; + return old; + } + } + if (count >= threshold) { + rehash(); + tab = table; + index = _hash % tab.length; + } + ByteArrayKeyEntry e = new ByteArrayKeyEntry(key, value, tab[index]); + tab[index] = e; + count++; + return null; + } + + public synchronized V remove(byte[] key) { + ByteArrayKeyEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) { + if (e.key == key) { + if (prev != null) { + prev.next = e.next; + } else { + tab[index] = e.next; + } + count--; + V oldValue = e.value; + e.value = null; + return oldValue; + } + } + return null; + } + + public synchronized void clear() { + ByteArrayKeyEntry tab[] = table; + for (int index = tab.length; --index >= 0;) + tab[index] = null; + count = 0; + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + Enumeration it = entries(); + buf.append("{"); + for (int i = 0; it.hasMoreElements(); i++) { + ByteArrayKeyEntry e = (ByteArrayKeyEntry) (it.nextElement()); + if (i > 0) + buf.append(", "); + buf.append(Arrays.toString(e.getKey()) + "=" + e.getValue()); + } + buf.append("}"); + return buf.toString(); + } + + public String toFormatString() { + StringBuffer buf = new StringBuffer(); + Enumeration it = entries(); + buf.append("{\n"); + while (it.hasMoreElements()) { + ByteArrayKeyEntry e = (ByteArrayKeyEntry) it.nextElement(); + buf.append("\t").append(Arrays.toString(e.getKey()) + "=" + e.getValue()).append("\n"); + } + buf.append("}"); + return buf.toString(); + } + + private enum TYPE { + KEYS, VALUES, ENTRIES + } + + private class Enumer implements Enumeration, ByteArrayEnumer { + ByteArrayKeyEntry[] table = ByteArrayKeyMap.this.table; + int index = table.length; + ByteArrayKeyEntry entry = null; + ByteArrayKeyEntry lastReturned = null; + TYPE type; + + Enumer(TYPE type) { + this.type = type; + } + + public boolean hasMoreElements() { + while (entry == null && index > 0) + entry = table[--index]; + return entry != null; + } + + public Object nextElement() { + while (entry == null && index > 0) + entry = table[--index]; + if (entry != null) { + ByteArrayKeyEntry e = lastReturned = entry; + entry = e.next; + switch (type) { + case KEYS: + return e.key; + case VALUES: + return e.value; + default: + return e; + } + } + throw new NoSuchElementException("no more next"); + } + + public byte[] nextKey() { + while (entry == null && index > 0) + entry = table[--index]; + if (entry != null) { + ByteArrayKeyEntry e = lastReturned = entry; + entry = e.next; + return e.key; + } + throw new NoSuchElementException("no more next"); + } + } + + public void putAll(ByteArrayKeyMap other) { + Enumeration it = other.entries(); + for (int i = 0, max = other.size(); i <= max; i++) { + ByteArrayKeyEntry e = (ByteArrayKeyEntry) (it.nextElement()); + this.put(e.getKey(), e.getValue()); + } + } + + public static class ByteArrayKeyEntry { + byte[] key; + V value; + ByteArrayKeyEntry next; + + protected ByteArrayKeyEntry(byte[] key, V value, ByteArrayKeyEntry next) { + this.key = key; + this.value = value; + this.next = next; + } + + protected Object clone() { + return new ByteArrayKeyEntry(key, value, (next == null ? null : (ByteArrayKeyEntry) next.clone())); + } + + public byte[] getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + if (value == null) + throw new NullPointerException(); + V oldValue = this.value; + this.value = value; + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof ByteArrayKeyMap.ByteArrayKeyEntry)) + return false; + ByteArrayKeyEntry e = (ByteArrayKeyEntry) o; + return (key == e.getKey()) && (value == null ? e.getValue() == null : value.equals(e.getValue())); + } + + public int hashCode() { + return Arrays.hashCode(key) ^ (value == null ? 0 : value.hashCode()); + } + + public String toString() { + return Arrays.toString(key) + "=" + value.toString(); + } + } +} From a294051b135af90d4bee486edeaeae78ed328ba3 Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Wed, 21 Mar 2018 09:23:09 +0900 Subject: [PATCH 10/24] [agent.java][WIP] spring data redis - jedis support. add jedis protocol ASM --- .../java/scouter/agent/AgentTransformer.java | 2 + .../agent/asm/redis/JedisConnectionASM.java | 116 ++++++++++++++++++ .../scouter/agent/asm/redis/RedisKeyASM.java | 2 +- .../java/scouter/agent/trace/TraceMain.java | 46 +++++++ 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java diff --git a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java index 72aa1356c..659732c6f 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java +++ b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java @@ -48,6 +48,7 @@ import scouter.agent.asm.asyncsupport.RequestStartAsyncASM; import scouter.agent.asm.asyncsupport.executor.ExecutorServiceASM; import scouter.agent.asm.asyncsupport.spring.SpringAsyncExecutionASM; +import scouter.agent.asm.redis.JedisConnectionASM; import scouter.agent.asm.redis.RedisKeyASM; import scouter.agent.asm.util.AsmUtil; import scouter.agent.util.AsyncRunner; @@ -120,6 +121,7 @@ public static void reload() { temp.add(new CallRunnableASM()); temp.add(new ExecutorServiceASM()); temp.add(new RedisKeyASM()); + temp.add(new JedisConnectionASM()); temp.add(new SpringReqMapASM()); temp.add(new HystrixCommandASM()); diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java new file mode 100644 index 000000000..599d7eeb5 --- /dev/null +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015 the original author or authors. + * @https://github.com/scouter-project/scouter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package scouter.agent.asm.redis; + +import scouter.agent.ClassDesc; +import scouter.agent.Configure; +import scouter.agent.asm.IASM; +import scouter.agent.asm.util.AsmUtil; +import scouter.agent.asm.util.HookingSet; +import scouter.agent.trace.TraceMain; +import scouter.org.objectweb.asm.ClassVisitor; +import scouter.org.objectweb.asm.MethodVisitor; +import scouter.org.objectweb.asm.Opcodes; +import scouter.org.objectweb.asm.Type; +import scouter.org.objectweb.asm.commons.LocalVariablesSorter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Gun Lee (gunlee01@gmail.com) on 2018. 3. 20. + */ +public class JedisConnectionASM implements IASM, Opcodes { + private Configure conf = Configure.getInstance(); + + private static List hookingPattern = new ArrayList(); + static { + hookingPattern.add("redis.clients.jedis.Protocol.sendCommand(Lredis/clients/util/RedisOutputStream;[B[[B)V"); + } + private List targetList; + + public JedisConnectionASM() { + targetList = HookingSet.getHookingMethodSet(HookingSet.buildPatterns("", hookingPattern)); + } + + public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) { + if (conf._hook_redis_enabled == false) + return cv; + + for (int i = 0; i < targetList.size(); i++) { + HookingSet mset = targetList.get(i); + if (mset.classMatch.include(className)) { + return new JedisConnectionCV(cv, mset, className); + } + } + + return cv; + } +} + +class JedisConnectionCV extends ClassVisitor implements Opcodes { + String className; + HookingSet mset; + + public JedisConnectionCV(ClassVisitor cv, HookingSet mset, String className) { + super(ASM5, cv); + this.mset = mset; + this.className = className; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + if (mv == null || mset.isA(name, desc) == false) { + return mv; + } + if (AsmUtil.isSpecial(name)) { + return mv; + } + + return new SendCommandMV(access, name, desc, mv); + } +} + +class SendCommandMV extends LocalVariablesSorter implements Opcodes { + private static final String TRACEMAIN = TraceMain.class.getName().replace('.', '/'); + private static final String START_METHOD = "startSendRedisCommand"; + private static final String START_SIGNATURE = "()Ljava/lang/Object;"; + private static final String END_METHOD = "endSendRedisCommand"; + private static final String END_SIGNATURE = "([B[[BLjava/lang/Object;Ljava/lang/Throwable;)V"; + + private String name; + private String desc; + private Type returnType; + + public SendCommandMV(int access, String name, String desc, MethodVisitor mv) { + super(ASM5, access, desc, mv); + this.name = name; + this.desc = desc; + this.returnType = Type.getReturnType(desc); + } + + @Override + public void visitCode() { + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitVarInsn(Opcodes.ALOAD, 3); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START_METHOD, START_SIGNATURE, false); + mv.visitCode(); + } +} \ No newline at end of file diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java index febc90bd4..f669dcd89 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java @@ -50,7 +50,7 @@ public RedisKeyASM() { } public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) { - if (conf._hook_spring_rest_enabled == false) + if (conf._hook_redis_enabled == false) return cv; for (int i = 0; i < targetList.size(); i++) { diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java index 35ed6f67e..8cd6d8969 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java +++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java @@ -39,6 +39,7 @@ import scouter.agent.wrapper.async.WrTask; import scouter.lang.AlertLevel; import scouter.lang.TextTypes; +import scouter.lang.enumeration.ParameterizedMessageLevel; import scouter.lang.pack.AlertPack; import scouter.lang.pack.XLogPack; import scouter.lang.pack.XLogTypes; @@ -47,6 +48,7 @@ import scouter.lang.step.MessageStep; import scouter.lang.step.MethodStep; import scouter.lang.step.MethodStep2; +import scouter.lang.step.ParameterizedMessageStep; import scouter.lang.step.ThreadCallPossibleStep; import scouter.lang.value.MapValue; import scouter.util.ArrayUtil; @@ -1447,7 +1449,51 @@ public static void startExceptionHandler(String className, String methodName, St } private static ByteArrayKeyLinkedMap redisKeyMap =new ByteArrayKeyLinkedMap().setMax(100); + private static String JEDIS_COMMAND_MSG = "[Redis][%s] KEY: %s"; + public static void setRedisKey(byte[] barr, Object key) { redisKeyMap.put(barr, key.toString()); } + + public static Object startSendRedisCommand() { + if (TraceContextManager.isForceDiscarded()) { + return null; + } + + TraceContext ctx = TraceContextManager.getContext(); + if (ctx == null) { + return null; + } + + ParameterizedMessageStep step = new ParameterizedMessageStep(); + step.start_time = (int) (System.currentTimeMillis() - ctx.startTime); + ctx.profile.push(step); + + return new LocalContext(ctx, step); + } + + public static void endSendRedisCommand(byte[] cmd, byte[][] args, Object localContext, Throwable thr) { + if (localContext == null) + return; + + LocalContext lctx = (LocalContext) localContext; + + ParameterizedMessageStep step = (ParameterizedMessageStep) lctx.stepSingle; + if (step == null) return; + + TraceContext tctx = lctx.context; + if (tctx == null) return; + + String key = redisKeyMap.get(args[0]); + if (key == null) { + key = "unknown"; + } + String command = new String(cmd); + + + step.setElapsed((int) (System.currentTimeMillis() - tctx.startTime) - step.start_time); + step.setMessage(DataProxy.sendHashedMessage(JEDIS_COMMAND_MSG), command, key); + step.setLevel(ParameterizedMessageLevel.INFO); + tctx.profile.pop(step); + } } From 282be2023d044d7e926b63d782069f167e83a05c Mon Sep 17 00:00:00 2001 From: Gun Lee Date: Wed, 21 Mar 2018 19:07:43 +0900 Subject: [PATCH 11/24] [agent.java][WIP] spring data redis - jedis support. add spring redis cache key support. --- .../java/scouter/agent/AgentTransformer.java | 2 + .../agent/asm/redis/JedisConnectionASM.java | 41 ++++- .../agent/asm/redis/RedisCacheKeyASM.java | 145 ++++++++++++++++++ .../scouter/agent/asm/redis/RedisKeyASM.java | 3 +- .../java/scouter/agent/trace/TraceMain.java | 13 +- .../src/scouter/client/xlog/ProfileText.java | 4 +- 6 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisCacheKeyASM.java diff --git a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java index 659732c6f..a20ec968b 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java +++ b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java @@ -49,6 +49,7 @@ import scouter.agent.asm.asyncsupport.executor.ExecutorServiceASM; import scouter.agent.asm.asyncsupport.spring.SpringAsyncExecutionASM; import scouter.agent.asm.redis.JedisConnectionASM; +import scouter.agent.asm.redis.RedisCacheKeyASM; import scouter.agent.asm.redis.RedisKeyASM; import scouter.agent.asm.util.AsmUtil; import scouter.agent.util.AsyncRunner; @@ -121,6 +122,7 @@ public static void reload() { temp.add(new CallRunnableASM()); temp.add(new ExecutorServiceASM()); temp.add(new RedisKeyASM()); + temp.add(new RedisCacheKeyASM()); temp.add(new JedisConnectionASM()); temp.add(new SpringReqMapASM()); diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java index 599d7eeb5..56ab69704 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java @@ -25,6 +25,7 @@ import scouter.agent.asm.util.HookingSet; import scouter.agent.trace.TraceMain; import scouter.org.objectweb.asm.ClassVisitor; +import scouter.org.objectweb.asm.Label; import scouter.org.objectweb.asm.MethodVisitor; import scouter.org.objectweb.asm.Opcodes; import scouter.org.objectweb.asm.Type; @@ -94,10 +95,13 @@ class SendCommandMV extends LocalVariablesSorter implements Opcodes { private static final String START_SIGNATURE = "()Ljava/lang/Object;"; private static final String END_METHOD = "endSendRedisCommand"; private static final String END_SIGNATURE = "([B[[BLjava/lang/Object;Ljava/lang/Throwable;)V"; + //private static final String END_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Throwable;)V"; private String name; private String desc; private Type returnType; + private int statIdx; + private Label startFinally = new Label(); public SendCommandMV(int access, String name, String desc, MethodVisitor mv) { super(ASM5, access, desc, mv); @@ -108,9 +112,40 @@ public SendCommandMV(int access, String name, String desc, MethodVisitor mv) { @Override public void visitCode() { - mv.visitVarInsn(Opcodes.ALOAD, 2); - mv.visitVarInsn(Opcodes.ALOAD, 3); mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START_METHOD, START_SIGNATURE, false); + statIdx = newLocal(Type.getType(Object.class)); + mv.visitVarInsn(Opcodes.ASTORE, statIdx); + mv.visitLabel(startFinally); mv.visitCode(); } -} \ No newline at end of file + + @Override + public void visitInsn(int opcode) { + if ((opcode >= IRETURN && opcode <= RETURN)) { + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitVarInsn(Opcodes.ALOAD, statIdx); + mv.visitInsn(Opcodes.ACONST_NULL); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, END_METHOD, END_SIGNATURE, false); + } + mv.visitInsn(opcode); + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + Label endFinally = new Label(); + mv.visitTryCatchBlock(startFinally, endFinally, endFinally, null); + mv.visitLabel(endFinally); + mv.visitInsn(DUP); + int errIdx = newLocal(Type.getType(Throwable.class)); + mv.visitVarInsn(Opcodes.ASTORE, errIdx); + + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitVarInsn(Opcodes.ALOAD, statIdx); + mv.visitVarInsn(Opcodes.ALOAD, errIdx); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, END_METHOD, END_SIGNATURE, false); + mv.visitInsn(ATHROW); + mv.visitMaxs(maxStack + 8, maxLocals + 2); + } +} diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisCacheKeyASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisCacheKeyASM.java new file mode 100644 index 000000000..ab39c2b17 --- /dev/null +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisCacheKeyASM.java @@ -0,0 +1,145 @@ +/* + * Copyright 2015 the original author or authors. + * @https://github.com/scouter-project/scouter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package scouter.agent.asm.redis; + +import scouter.agent.ClassDesc; +import scouter.agent.Configure; +import scouter.agent.Logger; +import scouter.agent.asm.IASM; +import scouter.agent.asm.util.AsmUtil; +import scouter.agent.asm.util.HookingSet; +import scouter.agent.trace.TraceMain; +import scouter.org.objectweb.asm.ClassVisitor; +import scouter.org.objectweb.asm.FieldVisitor; +import scouter.org.objectweb.asm.MethodVisitor; +import scouter.org.objectweb.asm.Opcodes; +import scouter.org.objectweb.asm.Type; +import scouter.org.objectweb.asm.commons.LocalVariablesSorter; + +import java.util.ArrayList; +import java.util.List; + +import static scouter.agent.asm.redis.RedisCacheKeyASM.KEY_ELEMENT_FIELD; +import static scouter.agent.asm.redis.RedisCacheKeyASM.KEY_ELEMENT_FIELD_DESC; + +/** + * @author Gun Lee (gunlee01@gmail.com) on 2018. 3. 20. + */ +public class RedisCacheKeyASM implements IASM, Opcodes { + public static final String KEY_ELEMENT_FIELD = "keyElement"; + public static final String KEY_ELEMENT_FIELD_DESC = "Ljava/lang/Object;"; + + private Configure conf = Configure.getInstance(); + + private static List pattern = new ArrayList(); + static { + pattern.add("org.springframework.data.redis.cache.RedisCacheKey.getKeyBytes()[B"); + } + private List targetList; + + public RedisCacheKeyASM() { + targetList = HookingSet.getHookingMethodSet(HookingSet.buildPatterns("", pattern)); + } + + public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) { + if (conf._hook_redis_enabled == false) + return cv; + + for (int i = 0; i < targetList.size(); i++) { + HookingSet mset = targetList.get(i); + if (mset.classMatch.include(className)) { + return new RedisCacheKeyCV(cv, mset, className); + } + } + + return cv; + } +} + +class RedisCacheKeyCV extends ClassVisitor implements Opcodes { + String className; + HookingSet mset; + boolean existKeyElementField; + + public RedisCacheKeyCV(ClassVisitor cv, HookingSet mset, String className) { + super(ASM5, cv); + this.mset = mset; + this.className = className; + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + if (name.equals(KEY_ELEMENT_FIELD) && desc.equals(KEY_ELEMENT_FIELD_DESC)) { + existKeyElementField = true; + } + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + if (!existKeyElementField) { + Logger.println("A902", "Ignore hooking - No Field " + KEY_ELEMENT_FIELD + " on " + className); + return mv; + } + + if (mv == null || mset.isA(name, desc) == false) { + return mv; + } + + if (AsmUtil.isSpecial(name)) { + return mv; + } + + return new GetKeyBytesMV(access, this.className, name, desc, mv); + } +} + +class GetKeyBytesMV extends LocalVariablesSorter implements Opcodes { + + private static final String TRACEMAIN = TraceMain.class.getName().replace('.', '/'); + private static final String START_METHOD = "setRedisKey"; + private static final String START_SIGNATURE = "([BLjava/lang/Object;)V"; + + private String className; + private String name; + private String desc; + private Type returnType; + + public GetKeyBytesMV(int access, String className, String name, String desc, MethodVisitor mv) { + super(ASM5, access, desc, mv); + this.className = className; + this.name = name; + this.desc = desc; + this.returnType = Type.getReturnType(desc); + } + + public void visitInsn(int opcode) { + if ((opcode >= IRETURN && opcode <= RETURN)) { + Type tp = returnType; + if (tp.getSort() == Type.ARRAY && tp.getElementType().getSort() == Type.BYTE) { + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, this.className, KEY_ELEMENT_FIELD, "Ljava/lang/Object;"); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START_METHOD, START_SIGNATURE, false); + } + } + mv.visitInsn(opcode); + } +} diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java index f669dcd89..e412559a2 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java @@ -104,7 +104,6 @@ public KeySetMV(int access, String name, String desc, MethodVisitor mv) { this.returnType = Type.getReturnType(desc); } - @Override public void visitInsn(int opcode) { if ((opcode >= IRETURN && opcode <= RETURN)) { Type tp = returnType; @@ -116,4 +115,4 @@ public void visitInsn(int opcode) { } mv.visitInsn(opcode); } -} \ No newline at end of file +} diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java index 8cd6d8969..a7bd6a63e 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java +++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java @@ -1449,7 +1449,7 @@ public static void startExceptionHandler(String className, String methodName, St } private static ByteArrayKeyLinkedMap redisKeyMap =new ByteArrayKeyLinkedMap().setMax(100); - private static String JEDIS_COMMAND_MSG = "[Redis][%s] KEY: %s"; + private static String JEDIS_COMMAND_MSG = "[REDIS]%s : %s"; public static void setRedisKey(byte[] barr, Object key) { redisKeyMap.put(barr, key.toString()); @@ -1484,9 +1484,12 @@ public static void endSendRedisCommand(byte[] cmd, byte[][] args, Object localCo TraceContext tctx = lctx.context; if (tctx == null) return; - String key = redisKeyMap.get(args[0]); + String key = null; + if (args.length > 0) { + key = redisKeyMap.get(args[0]); + } if (key == null) { - key = "unknown"; + key = "-"; } String command = new String(cmd); @@ -1496,4 +1499,8 @@ public static void endSendRedisCommand(byte[] cmd, byte[][] args, Object localCo step.setLevel(ParameterizedMessageLevel.INFO); tctx.profile.pop(step); } + + public static void endSendRedisCommand(Object localContext, Throwable thr) { + System.out.println(localContext); + } } diff --git a/scouter.client/src/scouter/client/xlog/ProfileText.java b/scouter.client/src/scouter/client/xlog/ProfileText.java index d94e2155b..20b333684 100644 --- a/scouter.client/src/scouter/client/xlog/ProfileText.java +++ b/scouter.client/src/scouter/client/xlog/ProfileText.java @@ -838,10 +838,10 @@ public static void toString(StringBuffer sb, ParameterizedMessageStep pmStep) { message = pmStep.buildMessasge(messageFormat); } + sb.append(message); if(pmStep.getElapsed() != -1) { - sb.append("[").append(FormatUtil.print(pmStep.getElapsed(), "#,##0")).append(" ms] "); + sb.append(" [").append(FormatUtil.print(pmStep.getElapsed(), "#,##0")).append(" ms]"); } - sb.append(message); } public static void toString(StringBuffer sb, DumpStep p, int lineHead) { From 081d90df3301e0eaa14c5433ec16f4148f3de098 Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Wed, 21 Mar 2018 22:25:51 +0900 Subject: [PATCH 12/24] [agent.java][DONE] spring data redis - jedis support. --- .../main/java/scouter/agent/Configure.java | 23 ++++++++-- .../java/scouter/agent/trace/TraceMain.java | 44 ++++++++++++++++--- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/scouter.agent.java/src/main/java/scouter/agent/Configure.java b/scouter.agent.java/src/main/java/scouter/agent/Configure.java index 99329b47a..63f2b3d9a 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/Configure.java +++ b/scouter.agent.java/src/main/java/scouter/agent/Configure.java @@ -167,6 +167,12 @@ public final static synchronized Configure getInstance() { public boolean profile_fullstack_sql_commit_enabled = false; @ConfigDesc("Stack profile in occurrence of sql error") public boolean profile_fullstack_hooked_exception_enabled = false; + + @ConfigDesc("Stack profile in occurrence of redis error") + public boolean profile_fullstack_redis_error_enabled = false; + @ConfigDesc("make unknown redis key stringify by force. (using new String(byte[])") + public boolean profile_redis_key_forcibly_stringify_enabled = false; + @ConfigDesc("Number of stack profile lines in occurrence of error") public int profile_fullstack_max_lines = 0; @@ -310,6 +316,8 @@ public final static synchronized Configure getInstance() { public boolean xlog_error_on_sqlexception_enabled = true; @ConfigDesc("mark as error on xlog flag if Api call errors are occured.") public boolean xlog_error_on_apicall_exception_enabled = true; + @ConfigDesc("mark as error on xlog flag if redis error is occured.") + public boolean xlog_error_on_redis_exception_enabled = true; //XLog hard sampling options @ConfigDesc("XLog hard sampling mode enabled\n - for the best performance but it affects all statistics data") @@ -557,11 +565,13 @@ public final static synchronized Configure getInstance() { @ConfigDesc("") public boolean _hook_usertx_enabled = true; @ConfigDesc("") - public String _hook_direct_patch_classes = ""; - @ConfigDesc("") public boolean _hook_spring_rest_enabled = true; @ConfigDesc("") public boolean _hook_redis_enabled = true; + + @ConfigDesc("") + public String _hook_direct_patch_classes = ""; + @ConfigDesc("") public String _hook_boot_prefix = null; @ConfigDesc("for warning a big Map type object that have a lot of entities.\n It may increase system load. be careful to enable this option.") @@ -893,6 +903,8 @@ private void apply() { this.profile_fullstack_sql_error_enabled = getBoolean("profile_fullstack_sql_error_enabled", false); this.profile_fullstack_sql_commit_enabled = getBoolean("profile_fullstack_sql_commit_enabled", false); this.profile_fullstack_hooked_exception_enabled = getBoolean("profile_fullstack_hooked_exception_enabled", false); + this.profile_fullstack_redis_error_enabled = getBoolean("profile_fullstack_redis_error_enabled", false); + this.profile_redis_key_forcibly_stringify_enabled = getBoolean("profile_redis_key_forcibly_stringify_enabled", false); this.profile_fullstack_max_lines = getInt("profile_fullstack_max_lines", 0); this.profile_fullstack_rs_leak_enabled = getBoolean("profile_fullstack_rs_leak_enabled", false); @@ -924,7 +936,11 @@ private void apply() { this._hook_async_enabled = getBoolean("_hook_async_enabled", true); this.trace_db2_enabled = getBoolean("trace_db2_enabled", true); this._hook_usertx_enabled = getBoolean("_hook_usertx_enabled", true); + this._hook_spring_rest_enabled = getBoolean("_hook_spring_rest_enabled", true); + this._hook_redis_enabled = getBoolean("_hook_redis_enabled", true); + this._hook_direct_patch_classes = getValue("_hook_direct_patch_classes", ""); + this._hook_boot_prefix = getValue("_hook_boot_prefix"); this._hook_map_impl_enabled = getBoolean("_hook_map_impl_enabled", false); this._hook_map_impl_warning_size = getInt("_hook_map_impl_warning_size", 50000); @@ -977,8 +993,6 @@ private void apply() { this.__ip_dummy_test = getBoolean("__ip_dummy_test", false); this.alert_perm_warning_pct = getInt("alert_perm_warning_pct", 90); - this._hook_spring_rest_enabled = getBoolean("_hook_spring_rest_enabled", true); - this._hook_redis_enabled = getBoolean("_hook_redis_enabled", true); this.alert_message_length = getInt("alert_message_length", 3000); this.alert_send_interval_ms = getInt("alert_send_interval_ms", 10000); @@ -986,6 +1000,7 @@ private void apply() { this.xlog_error_sql_time_max_ms = getInt("xlog_error_sql_time_max_ms", 30000); this.xlog_error_on_sqlexception_enabled = getBoolean("xlog_error_on_sqlexception_enabled", true); this.xlog_error_on_apicall_exception_enabled = getBoolean("xlog_error_on_apicall_exception_enabled", true); + this.xlog_error_on_redis_exception_enabled = getBoolean("xlog_error_on_redis_exception_enabled", true); this._log_asm_enabled = getBoolean("_log_asm_enabled", false); this.obj_type_inherit_to_child_enabled = getBoolean("obj_type_inherit_to_child_enabled", false); diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java index a7bd6a63e..c445ea7f7 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java +++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java @@ -1449,7 +1449,8 @@ public static void startExceptionHandler(String className, String methodName, St } private static ByteArrayKeyLinkedMap redisKeyMap =new ByteArrayKeyLinkedMap().setMax(100); - private static String JEDIS_COMMAND_MSG = "[REDIS]%s : %s"; + private static String JEDIS_COMMAND_MSG = "[REDIS]%s: %s"; + private static String JEDIS_COMMAND_ERROR_MSG = "[REDIS][ERROR]%s: %s [Exception:%s] %s"; public static void setRedisKey(byte[] barr, Object key) { redisKeyMap.put(barr, key.toString()); @@ -1489,15 +1490,48 @@ public static void endSendRedisCommand(byte[] cmd, byte[][] args, Object localCo key = redisKeyMap.get(args[0]); } if (key == null) { - key = "-"; + if (conf.profile_redis_key_forcibly_stringify_enabled) { + if (args.length > 0) { + key = new String(args[0]); + } else { + key = "EMPTY"; + } + } else { + key = "-"; + } } String command = new String(cmd); step.setElapsed((int) (System.currentTimeMillis() - tctx.startTime) - step.start_time); - step.setMessage(DataProxy.sendHashedMessage(JEDIS_COMMAND_MSG), command, key); - step.setLevel(ParameterizedMessageLevel.INFO); - tctx.profile.pop(step); + if (thr == null) { + step.setMessage(DataProxy.sendHashedMessage(JEDIS_COMMAND_MSG), command, key); + step.setLevel(ParameterizedMessageLevel.INFO); + tctx.profile.pop(step); + } else { + String msg = thr.toString(); + step.setMessage(DataProxy.sendHashedMessage(JEDIS_COMMAND_ERROR_MSG), command, key, thr.getClass().getName(), msg); + step.setLevel(ParameterizedMessageLevel.ERROR); + tctx.profile.pop(step); + + if (tctx.error == 0 && conf.xlog_error_on_redis_exception_enabled) { + if (conf.profile_fullstack_redis_error_enabled) { + StringBuffer sb = new StringBuffer(); + sb.append(msg).append("\n"); + ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines); + thr = thr.getCause(); + while (thr != null) { + sb.append("\nCause...\n"); + ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines); + thr = thr.getCause(); + } + msg = sb.toString(); + } + + int hash = DataProxy.sendError(msg); + tctx.error = hash; + } + } } public static void endSendRedisCommand(Object localContext, Throwable thr) { From 8e649b5d3d9d7820c4fc0194634576fb3a110c51 Mon Sep 17 00:00:00 2001 From: Gun Lee Date: Thu, 22 Mar 2018 20:58:06 +0900 Subject: [PATCH 13/24] [webapp] add ip value and dump stack values on steps of profile-data response. --- .../java/scouterx/webapp/model/ProfileStepData.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/scouter.webapp/src/main/java/scouterx/webapp/model/ProfileStepData.java b/scouter.webapp/src/main/java/scouterx/webapp/model/ProfileStepData.java index 64ef2aa2d..f500a71dd 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/model/ProfileStepData.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/model/ProfileStepData.java @@ -25,13 +25,16 @@ import scouter.lang.step.DispatchStep; import scouter.lang.step.DumpStep; import scouter.lang.step.HashedMessageStep; +import scouter.lang.step.MessageStep; import scouter.lang.step.MethodStep; import scouter.lang.step.ParameterizedMessageStep; +import scouter.lang.step.SocketStep; import scouter.lang.step.SqlStep; import scouter.lang.step.Step; import scouter.lang.step.StepEnum; import scouter.lang.step.ThreadCallPossibleStep; import scouter.lang.step.ThreadSubmitStep; +import scouter.util.IPUtil; import scouterx.webapp.framework.client.model.TextLoader; import scouterx.webapp.framework.client.model.TextModel; import scouterx.webapp.framework.client.model.TextTypeEnum; @@ -125,7 +128,8 @@ private static String getStepMainValue(Step step, long date, int serverId) { mainValue = textTypeEnum.getTextModel().getTextIfNullDefault(date, ((HashedMessageStep) step).getHash(), serverId); break; case PARAMETERIZED_MESSAGE: - mainValue = textTypeEnum.getTextModel().getTextIfNullDefault(date, ((ParameterizedMessageStep) step).getHash(), serverId); + ParameterizedMessageStep pmStep = (ParameterizedMessageStep) step; + mainValue = pmStep.buildMessasge(textTypeEnum.getTextModel().getTextIfNullDefault(date, pmStep.getHash(), serverId)); break; case DISPATCH: mainValue = textTypeEnum.getTextModel().getTextIfNullDefault(date, ((DispatchStep) step).getHash(), serverId); @@ -136,7 +140,10 @@ private static String getStepMainValue(Step step, long date, int serverId) { case DUMP: break; case MESSAGE: + mainValue = ((MessageStep) step).getMessage(); + break; case SOCKET: + mainValue = IPUtil.toString(((SocketStep) step).getIpaddr()); default: break; } @@ -152,7 +159,7 @@ private static List getStepAdditionalValue(Step step, long date, int ser case DUMP: DumpStep dumpStep = (DumpStep) step; for (int stackHash : dumpStep.stacks) { - valueList.add(textTypeEnum.getTextModel().getTextIfNullDefault(date, stackHash, serverId)); + valueList.add(TextTypeEnum.STACK_ELEMENT.getTextModel().getTextIfNullDefault(date, stackHash, serverId)); } break; @@ -220,7 +227,7 @@ private static void addAdditionalValueHashesToTextLoader(Step step, TextLoader t case DUMP: DumpStep dumpStep = (DumpStep) step; for (int stackHash : dumpStep.stacks) { - textLoader.addTextHash(textTypeEnum, stackHash); + textLoader.addTextHash(TextTypeEnum.STACK_ELEMENT, stackHash); } break; From 19caa16216ff35ba4997a7185e48bf2021d12fa9 Mon Sep 17 00:00:00 2001 From: knamkim Date: Fri, 23 Mar 2018 17:06:40 +0900 Subject: [PATCH 14/24] Support getConnection method hooking. In some cases, you may not be able to see sql information through jdbc class hooking. In such cases, you need to hook getConnecton method in DataSource or framework. If you use this function, add below configuration option in config file. ex)hook_get_connection_patterns=weblogic.jdbc.common.internal.RmiDataSource.getConnection --- .../java/scouter/agent/AgentTransformer.java | 3 +- .../main/java/scouter/agent/Configure.java | 8 +- .../agent/asm/JDBCGetConnectionASM.java | 128 ++++++++++++++++++ .../scouter/agent/asm/util/HookingSet.java | 1 - .../java/scouter/agent/trace/TraceSQL.java | 8 ++ 5 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 scouter.agent.java/src/main/java/scouter/agent/asm/JDBCGetConnectionASM.java diff --git a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java index a20ec968b..815d2c0e2 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java +++ b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java @@ -27,6 +27,7 @@ import scouter.agent.asm.IASM; import scouter.agent.asm.InitialContextASM; import scouter.agent.asm.JDBCConnectionOpenASM; +import scouter.agent.asm.JDBCGetConnectionASM; import scouter.agent.asm.JDBCDriverASM; import scouter.agent.asm.JDBCPreparedStatementASM; import scouter.agent.asm.JDBCResultSetASM; @@ -105,7 +106,7 @@ public static void reload() { temp.add(new JDBCStatementASM()); temp.add(new SqlMapASM()); temp.add(new UserTxASM()); - + temp.add(new JDBCGetConnectionASM()); temp.add(new JDBCConnectionOpenASM()); temp.add(new JDBCDriverASM()); temp.add(new InitialContextASM()); diff --git a/scouter.agent.java/src/main/java/scouter/agent/Configure.java b/scouter.agent.java/src/main/java/scouter/agent/Configure.java index 63f2b3d9a..cad3db15b 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/Configure.java +++ b/scouter.agent.java/src/main/java/scouter/agent/Configure.java @@ -426,6 +426,11 @@ public final static synchronized Configure getInstance() { @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) public String hook_connection_open_patterns = ""; + @ConfigDesc("Method set for getconnection hooking") + @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) + public String hook_get_connection_patterns = ""; + + @ConfigDesc("IntialContext Class Set") @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) public String hook_context_classes = "javax/naming/InitialContext"; @@ -810,7 +815,8 @@ private void apply() { this.hook_return_patterns = getValue("hook_return_patterns", ""); this.hook_constructor_patterns = getValue("hook_constructor_patterns", ""); this.hook_connection_open_patterns = getValue("hook_connection_open_patterns", ""); - + this.hook_get_connection_patterns = getValue("hook_get_connection_patterns",""); + this._log_datasource_lookup_enabled = getBoolean("_log_datasource_lookup_enabled", true); this.profile_connection_open_enabled = getBoolean("profile_connection_open_enabled", true); this._summary_connection_leak_fullstack_enabled = getBoolean("_summary_connection_leak_fullstack_enabled", false); diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/JDBCGetConnectionASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/JDBCGetConnectionASM.java new file mode 100644 index 000000000..d6674f6dd --- /dev/null +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/JDBCGetConnectionASM.java @@ -0,0 +1,128 @@ +/* + * Copyright 2015 the original author or authors. + * @https://github.com/scouter-project/scouter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package scouter.agent.asm; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import scouter.agent.ClassDesc; +import scouter.agent.Configure; +import scouter.agent.Logger; +import scouter.agent.asm.util.AsmUtil; +import scouter.agent.asm.util.HookingSet; +import scouter.agent.trace.TraceSQL; +import scouter.org.objectweb.asm.ClassVisitor; +import scouter.org.objectweb.asm.MethodVisitor; +import scouter.org.objectweb.asm.Opcodes; +import scouter.org.objectweb.asm.Type; +import scouter.org.objectweb.asm.commons.LocalVariablesSorter; + +public class JDBCGetConnectionASM implements IASM, Opcodes { + private List target = HookingSet.getHookingMethodSet(Configure.getInstance().hook_get_connection_patterns); + private Map reserved = new HashMap(); + + public JDBCGetConnectionASM() { + //AsmUtil.add(reserved, "weblogic/jdbc/common/internal/RmiDataSource", "getConnection()Ljava/sql/Connection;"); + + } + + public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) { + if (Configure.getInstance()._hook_dbsql_enabled == false) { + return cv; + } + + HookingSet mset = reserved.get(className); + if (mset != null) + return new DataSourceCV(cv, mset, className); + + for (int i = 0; i < target.size(); i++) { + mset = target.get(i); + if (mset.classMatch.include(className)) { + return new DataSourceCV(cv, mset, className); + } + } + return cv; + } +} + +class DataSourceCV extends ClassVisitor implements Opcodes { + + public String className; + private HookingSet mset; + + public DataSourceCV(ClassVisitor cv, HookingSet mset, String className) { + super(ASM5, cv); + this.mset = mset; + this.className = className; + + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + if (mv == null || mset.isA(name, desc) == false) { + return mv; + } + if (AsmUtil.isSpecial(name)) { + return mv; + } + return new DataSourceMV(access, desc, mv, className,name); + } +} + +// /////////////////////////////////////////////////////////////////////////// +class DataSourceMV extends LocalVariablesSorter implements Opcodes { + private static final String TRACE_SQL = TraceSQL.class.getName().replace('.', '/'); + private final static String METHOD = "getConnection"; + private static final String SIGNATURE = "(Ljava/sql/Connection;)Ljava/sql/Connection;"; + + private Type returnType; + private String className; + private String methodName; + private String methodDesc; + + public DataSourceMV(int access, String desc, MethodVisitor mv, String className, String methodName) { + super(ASM5,access, desc, mv); + this.returnType = Type.getReturnType(desc); + this.className = className; + this.methodName = methodName; + this.methodDesc = desc; + } + + + @Override + public void visitInsn(int opcode) { + if ((opcode >= IRETURN && opcode <= RETURN)) { + int i = newLocal(this.returnType); + mv.visitVarInsn(ASTORE, i); + mv.visitVarInsn(Opcodes.ALOAD, i); + AsmUtil.PUSH(mv, className); + AsmUtil.PUSH(mv, methodName); + AsmUtil.PUSH(mv, methodDesc); + mv.visitVarInsn(Opcodes.ALOAD, 0); + + mv.visitVarInsn(Opcodes.ALOAD, i); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE_SQL, METHOD, SIGNATURE,false); + + } + mv.visitInsn(opcode); + } + + +} \ No newline at end of file diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/util/HookingSet.java b/scouter.agent.java/src/main/java/scouter/agent/asm/util/HookingSet.java index 654b8f6a3..b06e963ad 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/asm/util/HookingSet.java +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/util/HookingSet.java @@ -218,7 +218,6 @@ public static String classPattrensToMethodPatterns(String classPatterns, String String s = classes[i]; classMethodPatterns.add(s + "." + method); } - return buildPatterns("", classMethodPatterns); } } diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceSQL.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceSQL.java index 6ac0e28af..9baa55d36 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceSQL.java +++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceSQL.java @@ -530,6 +530,14 @@ public static void driverConnect(String url, Throwable thr) { } } + public static Connection getConnection(Connection conn) { + if (conn == null) + return conn; + if (conn instanceof WrConnection) + return conn; + return new WrConnection(conn); + } + public static void userTxOpen() { TraceContext ctx = TraceContextManager.getContext(); if (ctx == null) From 6a08bcaeed84c6037d9a9db57a622de4ed6574c9 Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Fri, 23 Mar 2018 23:38:12 +0900 Subject: [PATCH 15/24] [java.webapp] cors default true --- .../src/main/java/scouter/server/Configure.java | 8 ++++---- .../webapp/framework/configure/StandAloneConfigure.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scouter.server/src/main/java/scouter/server/Configure.java b/scouter.server/src/main/java/scouter/server/Configure.java index 5f6f16901..68ebf9aa1 100644 --- a/scouter.server/src/main/java/scouter/server/Configure.java +++ b/scouter.server/src/main/java/scouter/server/Configure.java @@ -144,9 +144,9 @@ public final static synchronized Configure getInstance() { @ConfigDesc("Swagger option of host's ip or domain to call APIs.") public String net_http_api_swagger_host_ip = ""; @ConfigDesc("API CORS support for Access-Control-Allow-Origin") - public String net_http_api_cors_allow_origin = ""; + public String net_http_api_cors_allow_origin = "*"; @ConfigDesc("Access-Control-Allow-Credentials") - public String net_http_api_cors_allow_credentials = "false"; + public String net_http_api_cors_allow_credentials = "true"; @ConfigDesc("size of webapp connection pool to collector") public int net_webapp_tcp_client_pool_size = 12; @@ -392,8 +392,8 @@ private void apply() { this.net_http_api_enabled = getBoolean("net_http_api_enabled", false); this.net_http_api_swagger_enabled = getBoolean("net_http_api_swagger_enabled", false); this.net_http_api_swagger_host_ip = getValue("net_http_api_swagger_host_ip", ""); - this.net_http_api_cors_allow_origin = getValue("net_http_api_cors_allow_origin", ""); - this.net_http_api_cors_allow_credentials = getValue("net_http_api_cors_allow_credentials", "false"); + this.net_http_api_cors_allow_origin = getValue("net_http_api_cors_allow_origin", "*"); + this.net_http_api_cors_allow_credentials = getValue("net_http_api_cors_allow_credentials", "true"); this.net_webapp_tcp_client_pool_size = getInt("net_webapp_tcp_client_pool_size", 12); this.net_webapp_tcp_client_pool_timeout = getInt("net_webapp_tcp_client_pool_timeout", net_tcp_client_so_timeout_ms); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/configure/StandAloneConfigure.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/configure/StandAloneConfigure.java index ff0c48d62..33906182b 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/configure/StandAloneConfigure.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/configure/StandAloneConfigure.java @@ -83,9 +83,9 @@ protected final static synchronized StandAloneConfigure getInstance() { @ConfigDesc("Swagger option of host's ip or domain to call APIs.") public String net_http_api_swagger_host_ip = ""; @ConfigDesc("API CORS support for Access-Control-Allow-Origin") - public String net_http_api_cors_allow_origin = ""; + public String net_http_api_cors_allow_origin = "*"; @ConfigDesc("Access-Control-Allow-Credentials") - public String net_http_api_cors_allow_credentials = "false"; + public String net_http_api_cors_allow_credentials = "true"; @ConfigDesc("Log directory") public String log_dir = "./logs"; @@ -187,8 +187,8 @@ private void apply() { this.net_http_api_swagger_enabled = getBoolean("net_http_api_swagger_enabled", false); this.net_http_api_swagger_host_ip = getValue("net_http_api_swagger_host_ip", ""); - this.net_http_api_cors_allow_origin = getValue("net_http_api_cors_allow_origin", ""); - this.net_http_api_cors_allow_credentials = getValue("net_http_api_cors_allow_credentials", "false"); + this.net_http_api_cors_allow_origin = getValue("net_http_api_cors_allow_origin", "*"); + this.net_http_api_cors_allow_credentials = getValue("net_http_api_cors_allow_credentials", "true"); this.log_dir = getValue("log_dir", "./logs"); From cec1fce78950074a5048499abcb8f4631175226f Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Sat, 24 Mar 2018 21:02:38 +0900 Subject: [PATCH 16/24] [client] misc - trigger XLog filtering on filter dialog being clicked. - filter & search icons added on XLog top bar. --- scouter.client/icons/filter-old.png | Bin 0 -> 635 bytes scouter.client/icons/filter.png | Bin 635 -> 714 bytes scouter.client/icons/search.png | Bin 0 -> 879 bytes scouter.client/src/scouter/client/Images.java | 1 + .../client/xlog/dialog/XLogFilterDialog.java | 5 +- .../client/xlog/views/XLogRealTimeView.java | 16 ++++-- .../client/xlog/views/XLogViewCommon.java | 52 ++++++++++++++---- 7 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 scouter.client/icons/filter-old.png mode change 100644 => 100755 scouter.client/icons/filter.png create mode 100755 scouter.client/icons/search.png diff --git a/scouter.client/icons/filter-old.png b/scouter.client/icons/filter-old.png new file mode 100644 index 0000000000000000000000000000000000000000..03ddd799fa0a3aec561c75d4221f195db65d6eb9 GIT binary patch literal 635 zcmV->0)+jEP);68^d)m`eN0o>(5%D`Q(1;j>g@G;xlf`0VBQ`PFY?6)!N&f?*K}$p; zB!U=NBn{eB8${1}&-2_L*HuZp@ZP1@clS@cHp)4iM1ewzw59vko7eMM{e9z|%NNdX z0V;`?KKSzTCvTm5bc{L^CIKLUxc2X{i{ISz$8Sgf{q)1nXTP{`{s?9mQ$4&hPiKC- zY8q7(Y1Xu5iCf33=O4Vy(+|zQ?rW#gkKB0f%}?+6{G*qT22|DQB-73`YzA{N4W^=s zq0kQYcbtFfz zLz)H<&|z(Y4k}Y-$~Mv)x)%+!PL??G^sJD6Do`_ay(fa#Lz-N2Eu)K6nH>t z?@ngW22A>^B-!oZYVSN-?f^9GzFhLfLb)q(wYSu%4q%*ptQLSfGttroM_a6U_j zI2@=Rp5n%6qJIMzb$r9o$vI{h%dl~ZRq!fShMg(H9nHe)^JDJA2120_0)YUQS2tmx zt)OXILjc2bTR3C^cBVp{-u#X&*L*&YU@(YWE{Cvdfg*Ucf>9 z9P^0^-hR9&S2%@qIt{DU3aL~IrBX@Oh{xkltJMZF!GGZF2KJ8|aHpEizNIROt-?~N z#5uL6(rR@m7K?DXT#(D5$E4S&$Y& zp%7Hx^H11g0fBVWIk?h{jYb2>WD*R+!0-1Hh>mS`I-Q6_A`pp0dKPenx^G%V?dS}Z zgH!C(PIXYM)bVxq7@xNfVKf>EfSZO}hif#MOo&FK8~|dm_!1>Cq-V!R0000lL_t&-83n<=uby)l2jJ`eKF@pF(^E&4h%*uKJ2B9R7=(p^ zE(VjuVq+sVG1+XAjs{8p1EWDpL?R@D7+53?+Eg1v&w0=D+#lCfNxbmhrPp`&PrNqD zIh;g+LW;Dd``4S-^I83Ui~r+<$Lz)H<&|z(Y4u2^&)39t8FheiViTA(TD$zw! zG^v1u23)*n9%RE;vYcDXv@!|UC-p+K_yN_@8k_@2%x9`p%VRuui^k%Am)}^n&3|0B? v#kViLQYKYTL{0$+K?Ddm?Cx*p;UfGGL^vK+HG^sr00000NkvXXu0mjfPACp1 diff --git a/scouter.client/icons/search.png b/scouter.client/icons/search.png new file mode 100755 index 0000000000000000000000000000000000000000..2f193889f7ea091c292acdd684c595dcb206b5c4 GIT binary patch literal 879 zcmV-#1CacQP)@+1&aazfGU7ezSm^v zpACwO+tu0su66!(dT=`e05DeeCnCFJW(8|RKtKa{4LGONnx2V85A4m%PEQ?MEtR-esdM$pB-`H542D0)N2zSC6Imf)4L8?>%ZrW+H>xCKi$unm zvGZq-*Q%Aahx;C*=l+K%-?>XB)6TB$-L$r*`RUvlA`xP1NG2?)ge8@TQ4EN|Jks0u zcDg;oFC#-#R`YbWB`D?Q`1#y7l$LXhjSLf8AvQuB84}i#j0^!#g{VE#(K7h@5pFHy zSenl=@XBEdxp`h2Ji>CR%=qXJ7!e|?paKet-~;#ok#jETyeB(5&Bkhp;!+;51~G=) zH?L7xmDUu_h+a$+xuWom;AWW!mS$%%+436Rjc@}y?l1134kgD0AOf$OmjOR zstUlshZk$ZC!bAyIg{Y29z#&@3SJ;6D4+_eFume9^#TmMccC5u0J!ZCTnO6m$lnD| z5JeFHf`Xs~1vP>RLKI1GKDY<~pjr2&bi(fX;6Nj-ss@Ds0CcoO0H{JsEQk Date: Mon, 26 Mar 2018 00:26:45 +0900 Subject: [PATCH 17/24] [webapp][server] fix kv store error when same keys are repeatly saved. --- .../main/scala/scouter/server/db/io/IndexKeyFile2.scala | 8 +++++--- .../scouterx/webapp/framework/exception/ErrorState.java | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala b/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala index 404984018..2e7bd1119 100644 --- a/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala +++ b/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala @@ -83,7 +83,7 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { looping += 1; } if(looping > conf.log_index_traversal_warning_count) { - Logger.println("S161", 10, "[warn] Too many index deep searching. " + DataInputX.toLong(key, 0)); + Logger.println("S161", 10, "[warn] Too many index deep searching. " + new String(key, "UTF8")); } } catch { case e: IOException => @@ -119,7 +119,7 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { looping += 1; } if(looping > conf.log_index_traversal_warning_count) { - Logger.println("S161", 10, "[warn] Too many index deep searching. " + DataInputX.toLong(key, 0)); + Logger.println("S161", 10, "[warn] Too many index deep searching. " + new String(key, "UTF8")); } } catch { case e: IOException => @@ -153,7 +153,7 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { looping += 1; } if(looping > conf.log_index_traversal_warning_count) { - Logger.println("S152", 10, "[warn] Too many index deep searching. " + DataInputX.toLong(key, 0)); + Logger.println("S152", 10, "[warn] Too many index deep searching. " + new String(key, "UTF8")); } } catch { case e: IOException => @@ -226,6 +226,7 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { return 0; } + //TODO not yet implemented def read(handler: (Array[Byte], Array[Byte]) => Any) { if (this.keyFile == null) return @@ -247,6 +248,7 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { } } + //TODO not yet implemented def read(handler: (Array[Byte], Array[Byte]) => Any, reader: (Long)=>Array[Byte]) { if (this.keyFile == null) return ; diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/exception/ErrorState.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/exception/ErrorState.java index 820afdf27..3149aa107 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/exception/ErrorState.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/exception/ErrorState.java @@ -38,8 +38,8 @@ public enum ErrorState { ILLEGAL_KEY_ACCESS(Response.Status.BAD_REQUEST, Response.Status.BAD_REQUEST.getStatusCode(), "illegal key access error"), - LOGIN_REQUIRED(Response.Status.FORBIDDEN, Response.Status.FORBIDDEN.getStatusCode(), "login required."), - LOGIN_FAIL(Response.Status.UNAUTHORIZED, Response.Status.UNAUTHORIZED.getStatusCode(), "id or password is incorrect."), + LOGIN_REQUIRED(Response.Status.UNAUTHORIZED, Response.Status.UNAUTHORIZED.getStatusCode(), "login required."), + LOGIN_FAIL(Response.Status.NOT_FOUND, Response.Status.NOT_FOUND.getStatusCode(), "id or password is incorrect."), SESSION_EXPIRED(Response.Status.UNAUTHORIZED, Response.Status.UNAUTHORIZED.getStatusCode(), "authorization token or session is expired."), NOT_IMPLEMENTED(Response.Status.NOT_IMPLEMENTED, Response.Status.NOT_IMPLEMENTED.getStatusCode(), "This API is not yet implemented."), VALIDATE_ERROR(Response.Status.BAD_REQUEST, Response.Status.BAD_REQUEST.getStatusCode(), "fail to validate input parameters. : "), From 7046a1c2332d9e9c7327790a8a0985a99679f974 Mon Sep 17 00:00:00 2001 From: Gun Lee Date: Tue, 27 Mar 2018 20:00:04 +0900 Subject: [PATCH 18/24] add metric about api call & sql time by service --- .../src/main/resources/scouter/lang/counters/counters.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scouter.common/src/main/resources/scouter/lang/counters/counters.xml b/scouter.common/src/main/resources/scouter/lang/counters/counters.xml index b10e828d4..cb9673eb9 100644 --- a/scouter.common/src/main/resources/scouter/lang/counters/counters.xml +++ b/scouter.common/src/main/resources/scouter/lang/counters/counters.xml @@ -38,6 +38,9 @@ + + + From 41b3627d781c6bb1e1ab8318cdb643831b5403d0 Mon Sep 17 00:00:00 2001 From: Gun Lee Date: Tue, 27 Mar 2018 22:23:05 +0900 Subject: [PATCH 19/24] [client][WIP] add filter - start time --- .../scouter/client/xlog/XLogFilterStatus.java | 6 ++++ .../client/xlog/dialog/XLogFilterDialog.java | 32 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/scouter.client/src/scouter/client/xlog/XLogFilterStatus.java b/scouter.client/src/scouter/client/xlog/XLogFilterStatus.java index 214cf464c..41fffa728 100644 --- a/scouter.client/src/scouter/client/xlog/XLogFilterStatus.java +++ b/scouter.client/src/scouter/client/xlog/XLogFilterStatus.java @@ -7,6 +7,8 @@ public class XLogFilterStatus { public String objName = ""; public String service = ""; public String ip = ""; + public String startHmsFrom = ""; + public String startHmsTo = ""; public String login = ""; public String desc = ""; public String text1 = ""; @@ -24,6 +26,8 @@ public int hashCode() { int filter_hash = HashUtil.hash(objName); filter_hash ^= HashUtil.hash(service); filter_hash ^= HashUtil.hash(ip); + filter_hash ^= HashUtil.hash(startHmsFrom); + filter_hash ^= HashUtil.hash(startHmsTo); filter_hash ^= HashUtil.hash(login); filter_hash ^= HashUtil.hash(desc); filter_hash ^= HashUtil.hash(text1); @@ -43,6 +47,8 @@ public XLogFilterStatus clone() { status.objName = objName; status.service = service; status.ip = ip; + status.startHmsFrom = startHmsFrom; + status.startHmsTo = startHmsTo; status.login = login; status.desc = desc; status.text1 = text1; diff --git a/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java b/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java index 7c446bc70..5d93eecce 100644 --- a/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java +++ b/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java @@ -42,7 +42,7 @@ public class XLogFilterDialog extends Dialog { Combo objCombo; - Text serviceTxt, ipTxt, userAgentTxt, loginText, descText, text1Text, text2Text, text3Text, text4Text, text5Text; + Text serviceTxt, ipTxt, startHmsFromTxt, startHmsToTxt, userAgentTxt, loginText, descText, text1Text, text2Text, text3Text, text4Text, text5Text; Button onlySqlBtn, onlyApiBtn, onlyErrorBtn; Button clearBtn, applyBtn; @@ -111,6 +111,34 @@ public void modifyText(ModifyEvent arg0) { } }); + label = new Label(filterGrp, SWT.NONE); + label.setText("Start(hhmmss)"); + label.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, false)); + + Group startTimeGroup = new Group(container, SWT.NONE); + startTimeGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + startTimeGroup.setLayout(new GridLayout(2, false)); + + startHmsFromTxt = new Text(startTimeGroup, SWT.BORDER | SWT.SINGLE); + startHmsFromTxt.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, false)); + startHmsFromTxt.setText(status.startHmsFrom); + startHmsFromTxt.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent arg0) { + newStatus.startHmsFrom = startHmsFromTxt.getText(); + compareHash(); + } + }); + + startHmsToTxt = new Text(startTimeGroup, SWT.BORDER | SWT.SINGLE); + startHmsToTxt.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + startHmsToTxt.setText(status.startHmsTo); + startHmsToTxt.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent arg0) { + newStatus.startHmsTo = startHmsToTxt.getText(); + compareHash(); + } + }); + label = new Label(filterGrp, SWT.NONE); label.setText("LOGIN"); label.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, false)); @@ -266,6 +294,8 @@ public void widgetSelected(SelectionEvent e) { objCombo.setText(""); serviceTxt.setText(""); ipTxt.setText(""); + startHmsFromTxt.setText(""); + startHmsToTxt.setText(""); loginText.setText(""); descText.setText(""); text1Text.setText(""); From d1b9d142616fa3c61558dce8d18731dc1c7da6e7 Mon Sep 17 00:00:00 2001 From: Gun Lee Date: Wed, 28 Mar 2018 18:50:41 +0900 Subject: [PATCH 20/24] [client][WIP] filter by start time --- .../client/xlog/dialog/XLogFilterDialog.java | 85 ++++++++++++++----- .../main/java/scouter/util/StringUtil.java | 12 +++ 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java b/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java index 5d93eecce..741d5796c 100644 --- a/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java +++ b/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java @@ -23,7 +23,9 @@ import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowData; @@ -38,6 +40,11 @@ import org.eclipse.swt.widgets.Text; import scouter.client.xlog.XLogFilterStatus; import scouter.client.xlog.views.XLogViewCommon; +import scouter.util.StringUtil; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; public class XLogFilterDialog extends Dialog { @@ -112,31 +119,47 @@ public void modifyText(ModifyEvent arg0) { }); label = new Label(filterGrp, SWT.NONE); - label.setText("Start(hhmmss)"); + label.setText("StartHMS"); label.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, false)); - Group startTimeGroup = new Group(container, SWT.NONE); - startTimeGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - startTimeGroup.setLayout(new GridLayout(2, false)); +// Group startTimeGroup = new Group(filterGrp, SWT.NONE); +// startTimeGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); +// startTimeGroup.setLayout(new GridLayout(2, false)); + + Composite startTimeComposite = new Composite(filterGrp, SWT.NONE); + startTimeComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + +// startTimeComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); +// startTimeComposite.setLayout(new GridLayout(2, false)); - startHmsFromTxt = new Text(startTimeGroup, SWT.BORDER | SWT.SINGLE); - startHmsFromTxt.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, false)); + FillLayout filllayout = new FillLayout(); + filllayout.marginWidth = 0; + filllayout.marginHeight = 0; + startTimeComposite.setLayout(filllayout); + + + startHmsFromTxt = new Text(startTimeComposite, SWT.BORDER | SWT.SINGLE); + startHmsFromTxt.setTextLimit(6); +// startHmsFromTxt.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, false)); startHmsFromTxt.setText(status.startHmsFrom); - startHmsFromTxt.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent arg0) { - newStatus.startHmsFrom = startHmsFromTxt.getText(); - compareHash(); - } + startHmsFromTxt.addVerifyListener(hhmmssListener); + startHmsFromTxt.addModifyListener(arg0 -> { + newStatus.startHmsFrom = startHmsFromTxt.getText(); + compareHash(); }); - startHmsToTxt = new Text(startTimeGroup, SWT.BORDER | SWT.SINGLE); - startHmsToTxt.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + label = new Label(startTimeComposite, SWT.CENTER); + label.setText(" ~ "); + + startHmsToTxt = new Text(startTimeComposite, SWT.BORDER | SWT.SINGLE); + startHmsToTxt.setTextLimit(6); +// startHmsToTxt.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); startHmsToTxt.setText(status.startHmsTo); - startHmsToTxt.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent arg0) { - newStatus.startHmsTo = startHmsToTxt.getText(); - compareHash(); - } + startHmsToTxt.addVerifyListener(hhmmssListener); + startHmsToTxt.addModifyListener(arg0 -> { + newStatus.startHmsTo = startHmsToTxt.getText(); + compareHash(); }); label = new Label(filterGrp, SWT.NONE); @@ -356,8 +379,26 @@ protected void configureShell(Shell newShell) { newShell.setText("XLog Filter"); } - @Override - protected boolean isResizable() { - return true; - } + VerifyListener hhmmssListener = e -> { + if (!StringUtil.isInteger(e.text)) { + e.doit = false; + return; + } + + Text text = (Text) e.getSource(); + final String prev = text.getText(); + String after = prev.substring(0, e.start) + e.text + prev.substring(e.end); + + for(int i = after.length(); i < 8; i++) { + after += '0'; + } + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmmss"); + try { + LocalTime.parse(after, formatter); + } catch (DateTimeParseException ignore) { + e.doit = false; + } + + }; } diff --git a/scouter.common/src/main/java/scouter/util/StringUtil.java b/scouter.common/src/main/java/scouter/util/StringUtil.java index ef3f1d672..9bcf1b9a3 100644 --- a/scouter.common/src/main/java/scouter/util/StringUtil.java +++ b/scouter.common/src/main/java/scouter/util/StringUtil.java @@ -370,4 +370,16 @@ public static String emptyToDefault(String text, String defaultText) { return text; } } + + public static boolean isInteger(String s) { + try { + Integer.parseInt(s); + } catch(NumberFormatException e) { + return false; + } catch(NullPointerException e) { + return false; + } + // only got here if we didn't return false + return true; + } } From ddc4b8a385400574e0e9edb575a653fd43911bc7 Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Fri, 30 Mar 2018 09:03:42 +0900 Subject: [PATCH 21/24] [client][WIP] filter by start time --- .../client/xlog/dialog/XLogFilterDialog.java | 13 ++------ .../client/xlog/views/XLogViewPainter.java | 30 ++++++++++++++++--- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java b/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java index 741d5796c..9f841f4b2 100644 --- a/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java +++ b/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java @@ -122,16 +122,9 @@ public void modifyText(ModifyEvent arg0) { label.setText("StartHMS"); label.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, false)); -// Group startTimeGroup = new Group(filterGrp, SWT.NONE); -// startTimeGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); -// startTimeGroup.setLayout(new GridLayout(2, false)); - Composite startTimeComposite = new Composite(filterGrp, SWT.NONE); startTimeComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); -// startTimeComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); -// startTimeComposite.setLayout(new GridLayout(2, false)); - FillLayout filllayout = new FillLayout(); filllayout.marginWidth = 0; filllayout.marginHeight = 0; @@ -140,7 +133,6 @@ public void modifyText(ModifyEvent arg0) { startHmsFromTxt = new Text(startTimeComposite, SWT.BORDER | SWT.SINGLE); startHmsFromTxt.setTextLimit(6); -// startHmsFromTxt.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, false)); startHmsFromTxt.setText(status.startHmsFrom); startHmsFromTxt.addVerifyListener(hhmmssListener); startHmsFromTxt.addModifyListener(arg0 -> { @@ -154,7 +146,6 @@ public void modifyText(ModifyEvent arg0) { startHmsToTxt = new Text(startTimeComposite, SWT.BORDER | SWT.SINGLE); startHmsToTxt.setTextLimit(6); -// startHmsToTxt.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); startHmsToTxt.setText(status.startHmsTo); startHmsToTxt.addVerifyListener(hhmmssListener); startHmsToTxt.addModifyListener(arg0 -> { @@ -380,7 +371,7 @@ protected void configureShell(Shell newShell) { } VerifyListener hhmmssListener = e -> { - if (!StringUtil.isInteger(e.text)) { + if (!StringUtil.isInteger(e.text) && !StringUtil.isEmpty(e.text)) { e.doit = false; return; } @@ -389,7 +380,7 @@ protected void configureShell(Shell newShell) { final String prev = text.getText(); String after = prev.substring(0, e.start) + e.text + prev.substring(e.end); - for(int i = after.length(); i < 8; i++) { + for(int i = after.length(); i < 6; i++) { after += '0'; } diff --git a/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java b/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java index 5ee944abc..9e1d08848 100644 --- a/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java +++ b/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java @@ -38,8 +38,18 @@ import scouter.client.xlog.XLogFilterStatus; import scouter.client.xlog.XLogYAxisEnum; import scouter.lang.pack.XLogPack; -import scouter.util.*; - +import scouter.util.DateTimeHelper; +import scouter.util.DateUtil; +import scouter.util.FormatUtil; +import scouter.util.HashUtil; +import scouter.util.IPUtil; +import scouter.util.LongKeyLinkedMap; +import scouter.util.Pair; +import scouter.util.StrMatch; +import scouter.util.StringUtil; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Enumeration; @@ -74,6 +84,7 @@ public class XLogViewPainter { public StrMatch objNameMat; public StrMatch serviceMat; public StrMatch ipMat; + public Pair startFromToMat; public StrMatch loginMat; public StrMatch descMat; public StrMatch text1Mat; @@ -163,10 +174,21 @@ public void build() { onGoing = false; } } - + + public static void main(String[] args) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmmss"); + LocalTime localTime = LocalTime.parse("000001", formatter); + int sec = localTime.toSecondOfDay(); + //DateUtil.yyyymmdd(paintedEndTime); + long paintedEndTime = 0; + DateTimeHelper helper = DateTimeHelper.getDefault(); + long dateMillis = helper.dateUnitToTimeMillis(helper.getDateUnit(paintedEndTime)); + long start = dateMillis + sec; + } + int chart_x; long paintedEndTime; - + public long getLastTime() { return paintedEndTime; } From de47511fb5ac7aee6c8efec13c7e540129664732 Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Sat, 31 Mar 2018 17:45:59 +0900 Subject: [PATCH 22/24] [client] XLog Filter by start-time --- .../client/xlog/views/XLogViewPainter.java | 31 +++++++++++-------- .../src/main/java/scouter/util/DateUtil.java | 4 +++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java b/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java index 9e1d08848..997958f28 100644 --- a/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java +++ b/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java @@ -38,7 +38,6 @@ import scouter.client.xlog.XLogFilterStatus; import scouter.client.xlog.XLogYAxisEnum; import scouter.lang.pack.XLogPack; -import scouter.util.DateTimeHelper; import scouter.util.DateUtil; import scouter.util.FormatUtil; import scouter.util.HashUtil; @@ -175,17 +174,6 @@ public void build() { } } - public static void main(String[] args) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmmss"); - LocalTime localTime = LocalTime.parse("000001", formatter); - int sec = localTime.toSecondOfDay(); - //DateUtil.yyyymmdd(paintedEndTime); - long paintedEndTime = 0; - DateTimeHelper helper = DateTimeHelper.getDefault(); - long dateMillis = helper.dateUnitToTimeMillis(helper.getDateUnit(paintedEndTime)); - long start = dateMillis + sec; - } - int chart_x; long paintedEndTime; @@ -636,6 +624,7 @@ public boolean isFilterOk(XLogData d) { return isObjNameFilterOk(d) && isServiceFilterOk(d) && isIpFilterOk(d.p) + && isStartTimeFilterOk(d.p) && isLoginFilterOk(d) && isDescFilterOk(d) && isText1FilterOk(d) @@ -682,6 +671,14 @@ public boolean isIpFilterOk(XLogPack p) { return ipMat.include(value); } + public boolean isStartTimeFilterOk(XLogPack p) { + if (StringUtil.isEmpty(filterStatus.startHmsFrom) || StringUtil.isEmpty(filterStatus.startHmsTo)) { + return true; + } + long start = p.endTime - p.elapsed; + return startFromToMat.getLeft() <= start && start <= startFromToMat.getRight(); + } + public boolean isLoginFilterOk(XLogData d) { if (StringUtil.isEmpty(filterStatus.login)) { return true; @@ -783,13 +780,15 @@ public void setYAxisMode(XLogYAxisEnum yAxis) { this.yValueMax = yAxis.getDefaultMax(); this.yValueMin = 0; } - + + private DateTimeFormatter hmsFormatter = DateTimeFormatter.ofPattern("HHmmss"); public void setFilterStatus(XLogFilterStatus status) { this.filterStatus = status; filter_hash = filterStatus.hashCode(); objNameMat = new StrMatch(status.objName); serviceMat = new StrMatch(status.service); ipMat = new StrMatch(status.ip); + loginMat = new StrMatch(status.login); text1Mat = new StrMatch(status.text1); text2Mat = new StrMatch(status.text2); @@ -798,5 +797,11 @@ public void setFilterStatus(XLogFilterStatus status) { text5Mat = new StrMatch(status.text5); descMat = new StrMatch(status.desc); userAgentMat = new StrMatch(status.userAgent); + + long dateMillis = DateUtil.dateUnitToTimeMillis(DateUtil.getDateUnit(paintedEndTime)); + long startFrom = dateMillis + LocalTime.parse(status.startHmsFrom, hmsFormatter).toSecondOfDay() * 1000; + long startTo = dateMillis + LocalTime.parse(status.startHmsTo, hmsFormatter).toSecondOfDay() * 1000; + + startFromToMat = new Pair<>(startFrom, startTo); } } diff --git a/scouter.common/src/main/java/scouter/util/DateUtil.java b/scouter.common/src/main/java/scouter/util/DateUtil.java index 2aa5a3039..432d934df 100644 --- a/scouter.common/src/main/java/scouter/util/DateUtil.java +++ b/scouter.common/src/main/java/scouter/util/DateUtil.java @@ -50,6 +50,10 @@ public static long getDateUnit() { return helper.getDateUnit(); } + public static long dateUnitToTimeMillis(long dateUnit) { + return helper.dateUnitToTimeMillis(dateUnit); + } + public static long getDateUnit(long time) { return helper.getDateUnit(time); } From 35b449c8373a199ce202310fab51b12127cb9ffa Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Sat, 31 Mar 2018 17:47:08 +0900 Subject: [PATCH 23/24] [client] fix the bug that the windows client makes broken exported file. --- scouter.client/src/scouter/client/util/ZipUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scouter.client/src/scouter/client/util/ZipUtil.java b/scouter.client/src/scouter/client/util/ZipUtil.java index d01422360..f9e037e0e 100644 --- a/scouter.client/src/scouter/client/util/ZipUtil.java +++ b/scouter.client/src/scouter/client/util/ZipUtil.java @@ -85,7 +85,7 @@ private static void compressZip(File file, String root, ZipOutputStream zos) thr FileInputStream fis = null; try { String zipName = file.getPath().replace(root + "\\", ""); - zipName = file.getPath().replace(root + "/", ""); + zipName = zipName.replace(root + "/", ""); System.out.println("zipname:" + zipName); // 파일을 읽어드림 From f15e27457f806d8b9e0f494a0db90dafe76352dc Mon Sep 17 00:00:00 2001 From: Gunhee Lee Date: Tue, 3 Apr 2018 22:15:36 +0900 Subject: [PATCH 24/24] version 1.8.4 --- pom.xml | 2 +- scouter.agent.batch/pom.xml | 2 +- scouter.agent.host/pom.xml | 2 +- scouter.agent.java/pom.xml | 2 +- scouter.common/pom.xml | 2 +- scouter.deploy/pom.xml | 2 +- scouter.server.boot/pom.xml | 2 +- scouter.server/pom.xml | 2 +- scouter.webapp/pom.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 0e5f3103c..18f7aa8d2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.4.SNAPSHOT + 1.8.4 pom SCOUTER APM diff --git a/scouter.agent.batch/pom.xml b/scouter.agent.batch/pom.xml index 78abcc657..0be8c9f96 100644 --- a/scouter.agent.batch/pom.xml +++ b/scouter.agent.batch/pom.xml @@ -5,7 +5,7 @@ io.github.scouter-project scouter-parent - 1.8.4.SNAPSHOT + 1.8.4 scouter-agent-batch diff --git a/scouter.agent.host/pom.xml b/scouter.agent.host/pom.xml index b8a5dba03..b22276cad 100644 --- a/scouter.agent.host/pom.xml +++ b/scouter.agent.host/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.4.SNAPSHOT + 1.8.4 scouter-agent-host diff --git a/scouter.agent.java/pom.xml b/scouter.agent.java/pom.xml index 0f86e7748..d13e8487d 100644 --- a/scouter.agent.java/pom.xml +++ b/scouter.agent.java/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.4.SNAPSHOT + 1.8.4 scouter-agent-java diff --git a/scouter.common/pom.xml b/scouter.common/pom.xml index fd49860b3..af6adf623 100644 --- a/scouter.common/pom.xml +++ b/scouter.common/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.4.SNAPSHOT + 1.8.4 scouter-common diff --git a/scouter.deploy/pom.xml b/scouter.deploy/pom.xml index fbc90c446..0ba89cc6c 100644 --- a/scouter.deploy/pom.xml +++ b/scouter.deploy/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.4.SNAPSHOT + 1.8.4 scouter-deploy diff --git a/scouter.server.boot/pom.xml b/scouter.server.boot/pom.xml index a5c2887e5..4d907fcc0 100644 --- a/scouter.server.boot/pom.xml +++ b/scouter.server.boot/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.4.SNAPSHOT + 1.8.4 scouter-server-boot diff --git a/scouter.server/pom.xml b/scouter.server/pom.xml index eb64063ef..a206c5bce 100644 --- a/scouter.server/pom.xml +++ b/scouter.server/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.4.SNAPSHOT + 1.8.4 scouter-server diff --git a/scouter.webapp/pom.xml b/scouter.webapp/pom.xml index 0fcb21a21..ca98e82f0 100644 --- a/scouter.webapp/pom.xml +++ b/scouter.webapp/pom.xml @@ -5,7 +5,7 @@ scouter-parent io.github.scouter-project - 1.8.4.SNAPSHOT + 1.8.4 4.0.0