From b6d55d0b0bca5c7a88b0ce0229daec0b8a485e99 Mon Sep 17 00:00:00 2001 From: kylixs Date: Wed, 18 Mar 2020 10:33:50 +0800 Subject: [PATCH 001/135] refactor http read resource file --- .../term/impl/http/HttpRequestHandler.java | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java index eb98a4e4..d241fe40 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java @@ -1,6 +1,7 @@ package com.taobao.arthas.core.shell.term.impl.http; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; @@ -61,58 +62,66 @@ public class HttpRequestHandler extends SimpleChannelInboundHandler Date: Mon, 23 Mar 2020 09:55:55 +0800 Subject: [PATCH 002/135] add http api handler --- .../core/shell/session/SessionManager.java | 9 ++ .../session/impl/SessionManagerImpl.java | 9 ++ .../term/impl/http/HttpRequestHandler.java | 46 +++++--- .../term/impl/http/api/ApiException.java | 16 +++ .../shell/term/impl/http/api/ApiRequest.java | 56 +++++++++ .../shell/term/impl/http/api/ApiResponse.java | 72 ++++++++++++ .../shell/term/impl/http/api/ApiState.java | 14 +++ .../term/impl/http/api/HttpApiHandler.java | 111 ++++++++++++++++++ 8 files changed, 319 insertions(+), 14 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiException.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiRequest.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiResponse.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiState.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java b/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java new file mode 100644 index 00000000..011888fe --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java @@ -0,0 +1,9 @@ +package com.taobao.arthas.core.shell.session; + +/** + * Arthas Session Manager + * @author gongdewei 2020-03-20 + */ +public interface SessionManager { + +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java new file mode 100644 index 00000000..9ba2ed8e --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java @@ -0,0 +1,9 @@ +package com.taobao.arthas.core.shell.session.impl; + +/** + * Arthas Session Manager + * @author gongdewei 2020-03-20 + */ +public interface SessionManagerImpl { + +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java index d241fe40..8276ecf4 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java @@ -7,6 +7,7 @@ import java.net.URI; import java.net.URL; import com.taobao.arthas.common.IOUtils; +import com.taobao.arthas.core.shell.term.impl.http.api.HttpApiHandler; import com.taobao.arthas.core.util.LogUtil; import com.taobao.middleware.logger.Logger; @@ -30,6 +31,7 @@ import io.termd.core.util.Logging; /** * @author Julien Viet * @author hengyunabc 2019-11-06 + * @author gongdewei 2020-03-18 */ public class HttpRequestHandler extends SimpleChannelInboundHandler { private static final Logger logger = LogUtil.getArthasLogger(); @@ -38,10 +40,13 @@ public class HttpRequestHandler extends SimpleChannelInboundHandler options; + + @Override + public String toString() { + return "ApiRequest{" + + "action='" + action + '\'' + + ", sync=" + sync + + ", command='" + command + '\'' + + ", options=" + options + + '}'; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public boolean isSync() { + return sync; + } + + public void setSync(boolean sync) { + this.sync = sync; + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiResponse.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiResponse.java new file mode 100644 index 00000000..5382aedf --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiResponse.java @@ -0,0 +1,72 @@ +package com.taobao.arthas.core.shell.term.impl.http.api; + +/** + * Http Api exception + * @author gongdewei 2020-03-19 + */ +public class ApiResponse { + private String requestId; + private ApiState state; + private String message; + private String sessionId; + private String consumerId; + private String jobId; + private T body; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public ApiState getState() { + return state; + } + + public void setState(ApiState state) { + this.state = state; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getConsumerId() { + return consumerId; + } + + public void setConsumerId(String consumerId) { + this.consumerId = consumerId; + } + + public String getJobId() { + return jobId; + } + + public void setJobId(String jobId) { + this.jobId = jobId; + } + + public T getBody() { + return body; + } + + public void setBody(T body) { + this.body = body; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiState.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiState.java new file mode 100644 index 00000000..ec3cfce0 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiState.java @@ -0,0 +1,14 @@ +package com.taobao.arthas.core.shell.term.impl.http.api; + +/** + * Http API response state + * @author gongdewei 2020-03-19 + */ +public enum ApiState { + /** accepted */ + SCHEDULED, + RUNNING, + SUCCEEDED, + FAILED, + REFUSED +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java new file mode 100644 index 00000000..f4662c3c --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java @@ -0,0 +1,111 @@ +package com.taobao.arthas.core.shell.term.impl.http.api; + +import com.alibaba.fastjson.JSON; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.taobao.arthas.core.server.ArthasBootstrap; +import com.taobao.arthas.core.shell.ShellServer; +import com.taobao.arthas.core.util.LogUtil; +import com.taobao.arthas.core.util.StringUtils; +import com.taobao.middleware.logger.Logger; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; +import io.netty.util.CharsetUtil; + + +/** + * Http Restful Api Handler + * @author gongdewei 2020-03-18 + */ +public class HttpApiHandler { + + private static final Logger logger = LogUtil.getArthasLogger(); + + public HttpResponse handle(FullHttpRequest request) throws Exception { + DefaultFullHttpResponse response = new DefaultFullHttpResponse(request.protocolVersion(), + HttpResponseStatus.OK); + + ApiResponse result = null; + String requestBody = null; + try { + HttpMethod method = request.method(); + if (HttpMethod.POST.equals(method)){ + requestBody = getBody(request); + ApiRequest apiRequest = parseRequest(requestBody); + result = processRequest(apiRequest); + } else { + result = createResponse(ApiState.REFUSED, "Unsupported http method: "+method.name()); + } + } catch (Throwable e) { + result = createResponse(ApiState.FAILED, "Process request error: "+e.getMessage()); + logger.error("arthas", "arthas process http api request error: " + request.uri()+", request body: "+requestBody, e); + } + if (result == null) { + result = createResponse(ApiState.FAILED, "The request was not processed"); + } + + String jsonResult = JSON.toJSONString(result); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8"); + response.content().writeBytes(jsonResult.getBytes("UTF-8")); + return response; + } + + private ApiRequest parseRequest(String requestBody) throws ApiException { + if (StringUtils.isBlank(requestBody)){ + throw new ApiException("parse request failed: request body is empty"); + } + try { + //Object jsonRequest = JSON.parse(requestBody); + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(requestBody, ApiRequest.class); + } catch (Exception e) { + throw new ApiException("parse request failed: "+e.getMessage(), e); + } + } + + private ApiResponse processRequest(ApiRequest apiRequest) { + + String action = apiRequest.getAction(); + if ("exec".equalsIgnoreCase(action)){ + return processExecRequest(apiRequest); + } else if("init_session".equalsIgnoreCase(action)){ + return processInitSessionRequest(apiRequest); + } else if("close_session".equalsIgnoreCase(action)){ + return processCloseSessionRequest(apiRequest); + } + + return createResponse(ApiState.REFUSED, "Unsupported action: "+action); + } + + private ApiResponse processInitSessionRequest(ApiRequest apiRequest) { +// ShellServer shellServer = getShellServer(); +// shellServer.createShell() + + return null; + } + + private ApiResponse processCloseSessionRequest(ApiRequest apiRequest) { + return null; + } + + private ApiResponse processExecRequest(ApiRequest apiRequest) { + String command = apiRequest.getCommand(); + //TODO + return null; + } + + private ApiResponse createResponse(ApiState apiState, String message) { + ApiResponse apiResponse = new ApiResponse(); + apiResponse.setState(apiState); + apiResponse.setMessage(message); + return apiResponse; + } + + private String getBody(FullHttpRequest request){ + ByteBuf buf = request.content(); + return buf.toString(CharsetUtil.UTF_8); + } + +// private ShellServer getShellServer() { +// return ArthasBootstrap.getInstance().getShellServer(); +// } +} -- Gitee From dbed24a2f4debf0c2be0e0a897c2dc13713413b1 Mon Sep 17 00:00:00 2001 From: kylixs Date: Mon, 23 Mar 2020 15:56:48 +0800 Subject: [PATCH 003/135] removing dependency ShellServer from Session --- .../arthas/core/command/basic1000/ShutdownCommand.java | 4 +++- .../com/taobao/arthas/core/server/ArthasBootstrap.java | 4 ++++ .../com/taobao/arthas/core/shell/session/Session.java | 8 +------- .../arthas/core/shell/session/impl/SessionImpl.java | 6 ------ 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/com/taobao/arthas/core/command/basic1000/ShutdownCommand.java b/core/src/main/java/com/taobao/arthas/core/command/basic1000/ShutdownCommand.java index 7fc2bd63..c1cfc2e6 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/basic1000/ShutdownCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/basic1000/ShutdownCommand.java @@ -1,6 +1,7 @@ package com.taobao.arthas.core.command.basic1000; import com.taobao.arthas.core.advisor.Enhancer; +import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.ShellServer; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; @@ -39,7 +40,8 @@ public class ShutdownCommand extends AnnotatedCommand { // ignore } finally { process.end(); - ShellServer server = process.session().getServer(); + //ShellServer server = process.session().getServer(); + ShellServer server = ArthasBootstrap.getInstance().getShellServer(); server.close(); } } diff --git a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java index 3bf10784..574833fa 100644 --- a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java +++ b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java @@ -357,4 +357,8 @@ public class ArthasBootstrap { public TunnelClient getTunnelClient() { return tunnelClient; } + + public ShellServer getShellServer() { + return shellServer; + } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java b/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java index 5242a684..5166e8a6 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java @@ -10,6 +10,7 @@ import java.util.List; * A shell session. * * @author Julien Viet + * @author gongdewei 2020-03-23 */ public interface Session { String COMMAND_MANAGER = "arthas-command-manager"; @@ -80,13 +81,6 @@ public interface Session { */ String getSessionId(); - /** - * Get shell server - * - * @return shell server - */ - ShellServer getServer(); - /** * Get Java PID * diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java index a0a10497..a93931b0 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java @@ -1,6 +1,5 @@ package com.taobao.arthas.core.shell.session.impl; -import com.taobao.arthas.core.shell.ShellServer; import com.taobao.arthas.core.shell.command.CommandResolver; import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; @@ -69,11 +68,6 @@ public class SessionImpl implements Session { return (String) data.get(ID); } - @Override - public ShellServer getServer() { - return (ShellServer) data.get(SERVER); - } - @Override public long getPid() { return (Long) data.get(PID); -- Gitee From 64cee4f389222cce425c4af60773fbffe69cf8a5 Mon Sep 17 00:00:00 2001 From: kylixs Date: Mon, 23 Mar 2020 22:43:39 +0800 Subject: [PATCH 004/135] extract JobListener, remove dependency ShellImpl --- .../taobao/arthas/core/shell/system/Job.java | 5 + .../arthas/core/shell/system/JobListener.java | 18 +++ .../shell/system/impl/JobControllerImpl.java | 38 +++++- .../core/shell/system/impl/JobImpl.java | 109 +++++++++++------- 4 files changed, 125 insertions(+), 45 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/system/JobListener.java diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/Job.java b/core/src/main/java/com/taobao/arthas/core/shell/system/Job.java index d884ba36..e061264f 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/Job.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/Job.java @@ -57,6 +57,11 @@ public interface Job { */ Job resume(); + /** + * @return true if the job is running in background + */ + boolean isRunInBackground(); + /** * Send the job to background. * diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/JobListener.java b/core/src/main/java/com/taobao/arthas/core/shell/system/JobListener.java new file mode 100644 index 00000000..fb40ca90 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/JobListener.java @@ -0,0 +1,18 @@ +package com.taobao.arthas.core.shell.system; + +import com.taobao.arthas.core.shell.system.impl.JobImpl; + +/** + * Job listener + * @author gongdewei 2020-03-23 + */ +public interface JobListener { + + void onForeground(Job job); + + void onBackground(Job job); + + void onTerminated(Job job); + + void onSuspend(Job job); +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java index d7a0c200..20ad4001 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java @@ -11,6 +11,7 @@ import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.impl.ShellImpl; import com.taobao.arthas.core.shell.system.Job; import com.taobao.arthas.core.shell.system.JobController; +import com.taobao.arthas.core.shell.system.JobListener; import com.taobao.arthas.core.shell.system.Process; import com.taobao.arthas.core.shell.system.impl.ProcessImpl.ProcessOutput; import com.taobao.arthas.core.shell.term.Term; @@ -34,6 +35,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** * @author Julien Viet * @author hengyunabc 2019-05-14 + * @author gongdewei 2020-03-23 */ public class JobControllerImpl implements JobController { @@ -66,7 +68,7 @@ public class JobControllerImpl implements JobController { boolean runInBackground = runInBackground(tokens); Process process = createProcess(tokens, commandManager, jobId, shell.term()); process.setJobId(jobId); - JobImpl job = new JobImpl(jobId, this, process, line.toString(), runInBackground, shell); + JobImpl job = new JobImpl(jobId, this, process, line.toString(), runInBackground, shell.session(), new ShellJobHandler(shell)); jobs.put(jobId, job); return job; } @@ -225,4 +227,38 @@ public class JobControllerImpl implements JobController { public void close() { close(null); } + + private class ShellJobHandler implements JobListener { + ShellImpl shell; + + public ShellJobHandler(ShellImpl shell) { + this.shell = shell; + } + + @Override + public void onForeground(Job job) { + shell.setForegroundJob(job); + } + + @Override + public void onBackground(Job job) { + shell.setForegroundJob(null); + shell.readline(); + } + + @Override + public void onTerminated(Job job) { + if (!job.isRunInBackground()){ + shell.readline(); + } + } + + @Override + public void onSuspend(Job job) { + if (!job.isRunInBackground()){ + shell.readline(); + } + } + } + } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java index 6f37e863..42afa301 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java @@ -7,11 +7,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import com.taobao.arthas.core.shell.future.Future; import com.taobao.arthas.core.shell.handlers.Handler; -import com.taobao.arthas.core.shell.handlers.shell.ShellForegroundUpdateHandler; -import com.taobao.arthas.core.shell.impl.ShellImpl; import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.system.ExecStatus; import com.taobao.arthas.core.shell.system.Job; +import com.taobao.arthas.core.shell.system.JobListener; import com.taobao.arthas.core.shell.system.Process; import com.taobao.arthas.core.shell.term.Term; import com.taobao.arthas.core.shell.term.impl.TermImpl; @@ -21,6 +20,7 @@ import com.taobao.arthas.core.util.FileUtils; /** * @author Julien Viet * @author hengyunabc 2019-05-14 + * @author gongdewei 2020-03-23 */ public class JobImpl implements Job { @@ -28,25 +28,30 @@ public class JobImpl implements Job { final JobControllerImpl controller; final Process process; final String line; + private volatile Session session; private volatile ExecStatus actualStatus; // Used internally for testing only volatile long lastStopped; // When the job was last stopped - volatile ShellImpl shell; + volatile JobListener jobHandler; volatile Handler statusUpdateHandler; volatile Date timeoutDate; final Future terminateFuture; final AtomicBoolean runInBackground; - final Handler foregroundUpdatedHandler; + //final Handler foregroundUpdatedHandler; JobImpl(int id, final JobControllerImpl controller, Process process, String line, boolean runInBackground, - ShellImpl shell) { + Session session, JobListener jobHandler) { this.id = id; this.controller = controller; this.process = process; this.line = line; + this.session = session; this.terminateFuture = Future.future(); this.runInBackground = new AtomicBoolean(runInBackground); - this.shell = shell; - this.foregroundUpdatedHandler = new ShellForegroundUpdateHandler(shell); + this.jobHandler = jobHandler; + if (jobHandler == null) { + throw new IllegalArgumentException("JobListener is required"); + } + //this.foregroundUpdatedHandler = new ShellForegroundUpdateHandler(shell); process.terminatedHandler(new TerminatedHandler(controller)); } @@ -76,7 +81,7 @@ public class JobImpl implements Job { @Override public Session getSession() { - return shell.session(); + return session; } @Override @@ -89,19 +94,19 @@ public class JobImpl implements Job { runInBackground.set(!foreground); - if (foreground) { - if (foregroundUpdatedHandler != null) { - foregroundUpdatedHandler.handle(this); - } - } +// if (foreground) { +// if (foregroundUpdatedHandler != null) { +// foregroundUpdatedHandler.handle(this); +// } +// } if (statusUpdateHandler != null) { statusUpdateHandler.handle(process.status()); } if (foreground) { - shell.setForegroundJob(this); + jobHandler.onForeground(this); } else { - shell.setForegroundJob(null); + jobHandler.onBackground(this); } return this; } @@ -113,14 +118,15 @@ public class JobImpl implements Job { } catch (IllegalStateException ignore) { return this; } - if (!runInBackground.get() && foregroundUpdatedHandler != null) { - foregroundUpdatedHandler.handle(null); - } +// if (!runInBackground.get() && foregroundUpdatedHandler != null) { +// foregroundUpdatedHandler.handle(null); +// } if (statusUpdateHandler != null) { statusUpdateHandler.handle(process.status()); } - shell.setForegroundJob(null); +// shell.setForegroundJob(null); + jobHandler.onSuspend(this); return this; } @@ -147,6 +153,11 @@ public class JobImpl implements Job { return line; } + @Override + public boolean isRunInBackground() { + return runInBackground.get(); + } + @Override public Job toBackground() { if (!this.runInBackground.get()) { @@ -156,10 +167,12 @@ public class JobImpl implements Job { if (statusUpdateHandler != null) { statusUpdateHandler.handle(process.status()); } + jobHandler.onBackground(this); } } - shell.setForegroundJob(null); +// shell.setForegroundJob(null); +// jobHandler.onBackground(this); return this; } @@ -167,15 +180,16 @@ public class JobImpl implements Job { public Job toForeground() { if (this.runInBackground.get()) { if (runInBackground.compareAndSet(true, false)) { - if (foregroundUpdatedHandler != null) { - foregroundUpdatedHandler.handle(this); - } +// if (foregroundUpdatedHandler != null) { +// foregroundUpdatedHandler.handle(this); +// } process.toForeground(); if (statusUpdateHandler != null) { statusUpdateHandler.handle(process.status()); } - shell.setForegroundJob(this); +// shell.setForegroundJob(this); + jobHandler.onForeground(this); } } @@ -194,26 +208,32 @@ public class JobImpl implements Job { @Override public Job run(boolean foreground) { - if (foreground && foregroundUpdatedHandler != null) { - foregroundUpdatedHandler.handle(this); - } +// if (foreground && foregroundUpdatedHandler != null) { +// foregroundUpdatedHandler.handle(this); +// } actualStatus = ExecStatus.RUNNING; if (statusUpdateHandler != null) { statusUpdateHandler.handle(ExecStatus.RUNNING); } - process.setTty(shell.term()); - process.setSession(shell.session()); + //TODO process's tty + //process.setTty(shell.term()); + process.setSession(this.session); process.run(foreground); - if (!foreground && foregroundUpdatedHandler != null) { - foregroundUpdatedHandler.handle(null); - } - +// if (!foreground && foregroundUpdatedHandler != null) { +// foregroundUpdatedHandler.handle(null); +// } +// +// if (foreground) { +// shell.setForegroundJob(this); +// } else { +// shell.setForegroundJob(null); +// } if (foreground) { - shell.setForegroundJob(this); + jobHandler.onForeground(this); } else { - shell.setForegroundJob(null); + jobHandler.onBackground(this); } return this; } @@ -230,9 +250,10 @@ public class JobImpl implements Job { public void handle(Integer exitCode) { if (!runInBackground.get() && actualStatus.equals(ExecStatus.RUNNING)) { // 只有前台在运行的任务,才需要调用foregroundUpdateHandler - if (foregroundUpdatedHandler != null) { - foregroundUpdatedHandler.handle(null); - } +// if (foregroundUpdatedHandler != null) { +// foregroundUpdatedHandler.handle(null); +// } + jobHandler.onTerminated(JobImpl.this); } controller.removeJob(JobImpl.this.id); if (statusUpdateHandler != null) { @@ -240,12 +261,12 @@ public class JobImpl implements Job { } terminateFuture.complete(); - // save command history - Term term = shell.term(); - if (term instanceof TermImpl) { - List history = ((TermImpl) term).getReadline().getHistory(); - FileUtils.saveCommandHistory(history, new File(Constants.CMD_HISTORY_FILE)); - } + // TODO save command history +// Term term = shell.term(); +// if (term instanceof TermImpl) { +// List history = ((TermImpl) term).getReadline().getHistory(); +// FileUtils.saveCommandHistory(history, new File(Constants.CMD_HISTORY_FILE)); +// } } } -- Gitee From a1d4a39a117a55bce96e76cf6b3d006be7116122 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Tue, 24 Mar 2020 19:26:01 +0800 Subject: [PATCH 005/135] init process's tty, save command history on terminated --- .../shell/system/impl/JobControllerImpl.java | 32 +++++++++++++++---- .../core/shell/system/impl/JobImpl.java | 8 +++-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java index 20ad4001..5d417607 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java @@ -15,7 +15,9 @@ import com.taobao.arthas.core.shell.system.JobListener; import com.taobao.arthas.core.shell.system.Process; import com.taobao.arthas.core.shell.system.impl.ProcessImpl.ProcessOutput; import com.taobao.arthas.core.shell.term.Term; +import com.taobao.arthas.core.shell.term.impl.TermImpl; import com.taobao.arthas.core.util.Constants; +import com.taobao.arthas.core.util.FileUtils; import com.taobao.arthas.core.util.TokenUtils; import io.termd.core.function.Function; @@ -200,7 +202,9 @@ public class JobControllerImpl implements JobController { } } ProcessOutput ProcessOutput = new ProcessOutput(stdoutHandlerChain, cacheLocation, term); - return new ProcessImpl(command, remaining, command.processHandler(), ProcessOutput); + ProcessImpl process = new ProcessImpl(command, remaining, command.processHandler(), ProcessOutput); + process.setTty(term); + return process; } private String getRedirectFileName(ListIterator tokens) { @@ -238,27 +242,43 @@ public class JobControllerImpl implements JobController { @Override public void onForeground(Job job) { shell.setForegroundJob(job); + //reset stdin handler to job's origin handler + //shell.term().stdinHandler(job.process().getStdinHandler()); } @Override public void onBackground(Job job) { - shell.setForegroundJob(null); - shell.readline(); + resetAndReadLine(); } - @Override + @Override public void onTerminated(Job job) { if (!job.isRunInBackground()){ - shell.readline(); + resetAndReadLine(); + } + + // save command history + Term term = shell.term(); + if (term instanceof TermImpl) { + List history = ((TermImpl) term).getReadline().getHistory(); + FileUtils.saveCommandHistory(history, new File(Constants.CMD_HISTORY_FILE)); } } @Override public void onSuspend(Job job) { if (!job.isRunInBackground()){ - shell.readline(); + resetAndReadLine(); } } + + private void resetAndReadLine() { + //reset stdin handler to echo handler + //shell.term().stdinHandler(null); + shell.setForegroundJob(null); + shell.readline(); + } + } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java index 42afa301..c6d5bb8f 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java @@ -216,7 +216,7 @@ public class JobImpl implements Job { if (statusUpdateHandler != null) { statusUpdateHandler.handle(ExecStatus.RUNNING); } - //TODO process's tty + //set process's tty in JobControllerImpl.createCommandProcess //process.setTty(shell.term()); process.setSession(this.session); process.run(foreground); @@ -248,11 +248,13 @@ public class JobImpl implements Job { @Override public void handle(Integer exitCode) { - if (!runInBackground.get() && actualStatus.equals(ExecStatus.RUNNING)) { +// if (!runInBackground.get() && actualStatus.equals(ExecStatus.RUNNING)) { // 只有前台在运行的任务,才需要调用foregroundUpdateHandler // if (foregroundUpdatedHandler != null) { // foregroundUpdatedHandler.handle(null); // } +// } + if (actualStatus.equals(ExecStatus.RUNNING)) { jobHandler.onTerminated(JobImpl.this); } controller.removeJob(JobImpl.this.id); @@ -261,7 +263,7 @@ public class JobImpl implements Job { } terminateFuture.complete(); - // TODO save command history + // save command history (move to JobControllerImpl.ShellJobHandler.onTerminated) // Term term = shell.term(); // if (term instanceof TermImpl) { // List history = ((TermImpl) term).getReadline().getHistory(); -- Gitee From 5acfe6ada47d2961514cf14c06cd6c15384ee123 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Wed, 25 Mar 2020 11:40:42 +0800 Subject: [PATCH 006/135] remove dependency ShellImpl from JobController --- .../arthas/core/shell/impl/ShellImpl.java | 63 +++++++++++++++-- .../core/shell/system/JobController.java | 9 ++- .../system/impl/GlobalJobControllerImpl.java | 19 +++-- .../shell/system/impl/JobControllerImpl.java | 69 ++----------------- 4 files changed, 75 insertions(+), 85 deletions(-) diff --git a/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java index 01120c4e..10975748 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java @@ -5,23 +5,23 @@ import com.taobao.arthas.core.shell.ShellServer; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.cli.CliTokens; import com.taobao.arthas.core.shell.future.Future; -import com.taobao.arthas.core.shell.handlers.shell.CloseHandler; -import com.taobao.arthas.core.shell.handlers.shell.CommandManagerCompletionHandler; -import com.taobao.arthas.core.shell.handlers.shell.FutureHandler; -import com.taobao.arthas.core.shell.handlers.shell.InterruptHandler; -import com.taobao.arthas.core.shell.handlers.shell.ShellLineHandler; -import com.taobao.arthas.core.shell.handlers.shell.SuspendHandler; +import com.taobao.arthas.core.shell.handlers.shell.*; import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.session.impl.SessionImpl; import com.taobao.arthas.core.shell.system.ExecStatus; import com.taobao.arthas.core.shell.system.Job; import com.taobao.arthas.core.shell.system.JobController; +import com.taobao.arthas.core.shell.system.JobListener; import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; import com.taobao.arthas.core.shell.system.impl.JobControllerImpl; import com.taobao.arthas.core.shell.term.Term; +import com.taobao.arthas.core.shell.term.impl.TermImpl; +import com.taobao.arthas.core.util.Constants; +import com.taobao.arthas.core.util.FileUtils; import com.taobao.arthas.core.util.LogUtil; import com.taobao.middleware.logger.Logger; +import java.io.File; import java.lang.instrument.Instrumentation; import java.util.Date; import java.util.List; @@ -78,7 +78,7 @@ public class ShellImpl implements Shell { @Override public synchronized Job createJob(List args) { - Job job = jobController.createJob(commandManager, args, this); + Job job = jobController.createJob(commandManager, args, session, new ShellJobHandler(this), term); return job; } @@ -179,4 +179,53 @@ public class ShellImpl implements Shell { public Job getForegroundJob() { return currentForegroundJob; } + + private class ShellJobHandler implements JobListener { + ShellImpl shell; + + public ShellJobHandler(ShellImpl shell) { + this.shell = shell; + } + + @Override + public void onForeground(Job job) { + shell.setForegroundJob(job); + //reset stdin handler to job's origin handler + //shell.term().stdinHandler(job.process().getStdinHandler()); + } + + @Override + public void onBackground(Job job) { + resetAndReadLine(); + } + + @Override + public void onTerminated(Job job) { + if (!job.isRunInBackground()){ + resetAndReadLine(); + } + + // save command history + Term term = shell.term(); + if (term instanceof TermImpl) { + List history = ((TermImpl) term).getReadline().getHistory(); + FileUtils.saveCommandHistory(history, new File(Constants.CMD_HISTORY_FILE)); + } + } + + @Override + public void onSuspend(Job job) { + if (!job.isRunInBackground()){ + resetAndReadLine(); + } + } + + private void resetAndReadLine() { + //reset stdin handler to echo handler + //shell.term().stdinHandler(null); + shell.setForegroundJob(null); + shell.readline(); + } + } + } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/JobController.java b/core/src/main/java/com/taobao/arthas/core/shell/system/JobController.java index a0045486..785f52a0 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/JobController.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/JobController.java @@ -2,8 +2,9 @@ package com.taobao.arthas.core.shell.system; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.handlers.Handler; -import com.taobao.arthas.core.shell.impl.ShellImpl; +import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; +import com.taobao.arthas.core.shell.term.Term; import java.util.List; import java.util.Set; @@ -33,10 +34,12 @@ public interface JobController { * * @param commandManager command manager * @param tokens the command tokens - * @param shell the current shell + * @param session the current session + * @param jobHandler job event handler + * @param term telnet term * @return the created job */ - Job createJob(InternalCommandManager commandManager, List tokens, ShellImpl shell); + Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term); /** * Close the controller and terminate all the underlying jobs, a closed controller does not accept anymore jobs. diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java index 61545fa5..efda8fe3 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java @@ -1,21 +1,18 @@ package com.taobao.arthas.core.shell.system.impl; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.TimeUnit; - import com.taobao.arthas.core.GlobalOptions; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.handlers.Handler; -import com.taobao.arthas.core.shell.impl.ShellImpl; +import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.system.Job; +import com.taobao.arthas.core.shell.system.JobListener; +import com.taobao.arthas.core.shell.term.Term; import com.taobao.arthas.core.util.LogUtil; import com.taobao.middleware.logger.Logger; +import java.util.*; +import java.util.concurrent.TimeUnit; + /** * 全局的Job Controller,不应该存在启停的概念,不需要在连接的断开时关闭, * @@ -53,8 +50,8 @@ public class GlobalJobControllerImpl extends JobControllerImpl { } @Override - public Job createJob(InternalCommandManager commandManager, List tokens, ShellImpl shell) { - final Job job = super.createJob(commandManager, tokens, shell); + public Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term) { + final Job job = super.createJob(commandManager, tokens, session, jobHandler, term); /* * 达到超时时间将会停止job diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java index 5d417607..2836ff13 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java @@ -8,30 +8,20 @@ import com.taobao.arthas.core.shell.command.internal.StdoutHandler; import com.taobao.arthas.core.shell.command.internal.TermHandler; import com.taobao.arthas.core.shell.future.Future; import com.taobao.arthas.core.shell.handlers.Handler; -import com.taobao.arthas.core.shell.impl.ShellImpl; +import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.system.Job; import com.taobao.arthas.core.shell.system.JobController; import com.taobao.arthas.core.shell.system.JobListener; import com.taobao.arthas.core.shell.system.Process; import com.taobao.arthas.core.shell.system.impl.ProcessImpl.ProcessOutput; import com.taobao.arthas.core.shell.term.Term; -import com.taobao.arthas.core.shell.term.impl.TermImpl; import com.taobao.arthas.core.util.Constants; -import com.taobao.arthas.core.util.FileUtils; import com.taobao.arthas.core.util.TokenUtils; - import io.termd.core.function.Function; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** @@ -61,16 +51,16 @@ public class JobControllerImpl implements JobController { } @Override - public Job createJob(InternalCommandManager commandManager, List tokens, ShellImpl shell) { + public Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term) { int jobId = idGenerator.incrementAndGet(); StringBuilder line = new StringBuilder(); for (CliToken arg : tokens) { line.append(arg.raw()); } boolean runInBackground = runInBackground(tokens); - Process process = createProcess(tokens, commandManager, jobId, shell.term()); + Process process = createProcess(tokens, commandManager, jobId, term); process.setJobId(jobId); - JobImpl job = new JobImpl(jobId, this, process, line.toString(), runInBackground, shell.session(), new ShellJobHandler(shell)); + JobImpl job = new JobImpl(jobId, this, process, line.toString(), runInBackground, session, jobHandler); jobs.put(jobId, job); return job; } @@ -232,53 +222,4 @@ public class JobControllerImpl implements JobController { close(null); } - private class ShellJobHandler implements JobListener { - ShellImpl shell; - - public ShellJobHandler(ShellImpl shell) { - this.shell = shell; - } - - @Override - public void onForeground(Job job) { - shell.setForegroundJob(job); - //reset stdin handler to job's origin handler - //shell.term().stdinHandler(job.process().getStdinHandler()); - } - - @Override - public void onBackground(Job job) { - resetAndReadLine(); - } - - @Override - public void onTerminated(Job job) { - if (!job.isRunInBackground()){ - resetAndReadLine(); - } - - // save command history - Term term = shell.term(); - if (term instanceof TermImpl) { - List history = ((TermImpl) term).getReadline().getHistory(); - FileUtils.saveCommandHistory(history, new File(Constants.CMD_HISTORY_FILE)); - } - } - - @Override - public void onSuspend(Job job) { - if (!job.isRunInBackground()){ - resetAndReadLine(); - } - } - - private void resetAndReadLine() { - //reset stdin handler to echo handler - //shell.term().stdinHandler(null); - shell.setForegroundJob(null); - shell.readline(); - } - - } - } -- Gitee From ca9441a0d8cbd4d280e4918330b3f7f1e3150827 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Wed, 25 Mar 2020 19:50:37 +0800 Subject: [PATCH 007/135] creating session for http api --- .../arthas/core/server/ArthasBootstrap.java | 63 +++++----- .../taobao/arthas/core/shell/ShellServer.java | 12 ++ .../core/shell/impl/ShellServerImpl.java | 8 ++ .../arthas/core/shell/session/Session.java | 29 ++++- .../core/shell/session/SessionManager.java | 18 +++ .../core/shell/session/impl/SessionImpl.java | 21 ++++ .../session/impl/SessionManagerImpl.java | 80 +++++++++++- .../term/impl/http/HttpRequestHandler.java | 17 ++- .../shell/term/impl/http/api/ApiAction.java | 12 ++ .../shell/term/impl/http/api/ApiRequest.java | 20 +++ .../shell/term/impl/http/api/ApiResponse.java | 21 ++-- .../term/impl/http/api/HttpApiHandler.java | 114 +++++++++++++++--- 12 files changed, 352 insertions(+), 63 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiAction.java diff --git a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java index 574833fa..d05d9465 100644 --- a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java +++ b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java @@ -1,24 +1,5 @@ package com.taobao.arthas.core.server; -import java.arthas.Spy; -import java.io.File; -import java.io.IOException; -import java.lang.instrument.Instrumentation; -import java.lang.reflect.Method; -import java.net.URI; -import java.security.CodeSource; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - import com.alibaba.arthas.tunnel.client.TunnelClient; import com.taobao.arthas.common.PidUtils; import com.taobao.arthas.core.advisor.AdviceWeaver; @@ -35,20 +16,33 @@ import com.taobao.arthas.core.shell.ShellServerOptions; import com.taobao.arthas.core.shell.command.CommandResolver; import com.taobao.arthas.core.shell.handlers.BindHandler; import com.taobao.arthas.core.shell.impl.ShellServerImpl; +import com.taobao.arthas.core.shell.session.SessionManager; +import com.taobao.arthas.core.shell.session.impl.SessionManagerImpl; import com.taobao.arthas.core.shell.term.impl.HttpTermServer; import com.taobao.arthas.core.shell.term.impl.httptelnet.HttpTelnetTermServer; -import com.taobao.arthas.core.util.ArthasBanner; -import com.taobao.arthas.core.util.Constants; -import com.taobao.arthas.core.util.FileUtils; -import com.taobao.arthas.core.util.LogUtil; -import com.taobao.arthas.core.util.UserStatUtil; +import com.taobao.arthas.core.util.*; import com.taobao.middleware.logger.Logger; - import io.netty.channel.ChannelFuture; +import java.arthas.Spy; +import java.io.File; +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Method; +import java.net.URI; +import java.security.CodeSource; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + /** * @author vlinux on 15/5/2. + * @author gongdewei 2020-03-25 */ public class ArthasBootstrap { @@ -65,6 +59,7 @@ public class ArthasBootstrap { private Instrumentation instrumentation; private Thread shutdown; private ShellServer shellServer; + private SessionManager sessionManager; private ExecutorService executorService; private TunnelClient tunnelClient; @@ -247,13 +242,13 @@ public class ArthasBootstrap { shellServer.registerTermServer(new HttpTelnetTermServer(configure.getIp(), configure.getTelnetPort(), options.getConnectionTimeout())); } else { - logger.info("telnet port is {}, skip bind telnet server.", configure.getTelnetPort()); + logger.info("arthas", "telnet port is {}, skip bind telnet server.", configure.getTelnetPort()); } if (configure.getHttpPort() > 0) { shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(), options.getConnectionTimeout())); } else { - logger.info("http port is {}, skip bind http server.", configure.getHttpPort()); + logger.info("arthas", "http port is {}, skip bind http server.", configure.getHttpPort()); } for (CommandResolver resolver : resolvers) { @@ -262,16 +257,20 @@ public class ArthasBootstrap { shellServer.listen(new BindHandler(isBindRef)); - logger.info("as-server listening on network={};telnet={};http={};timeout={};", configure.getIp(), + //http api session manager + sessionManager = new SessionManagerImpl(options, this, shellServer.getCommandManager(), shellServer.getJobController()); + + logger.info("arthas", "as-server listening on network={};telnet={};http={};timeout={};", configure.getIp(), configure.getTelnetPort(), configure.getHttpPort(), options.getConnectionTimeout()); + // 异步回报启动次数 if (configure.getStatUrl() != null) { - logger.info("arthas stat url: {}", configure.getStatUrl()); + logger.info("arthas", "arthas stat url: {}", configure.getStatUrl()); } UserStatUtil.setStatUrl(configure.getStatUrl()); UserStatUtil.arthasStart(); - logger.info("as-server started in {} ms", System.currentTimeMillis() - start ); + logger.info("arthas", "as-server started in {} ms", System.currentTimeMillis() - start ); } catch (Throwable e) { logger.error(null, "Error during bind to port " + configure.getTelnetPort(), e); if (shellServer != null) { @@ -361,4 +360,8 @@ public class ArthasBootstrap { public ShellServer getShellServer() { return shellServer; } + + public SessionManager getSessionManager() { + return sessionManager; + } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/ShellServer.java b/core/src/main/java/com/taobao/arthas/core/shell/ShellServer.java index 49192c84..b69ae425 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/ShellServer.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/ShellServer.java @@ -5,6 +5,8 @@ import com.taobao.arthas.core.shell.future.Future; import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.handlers.NoOpHandler; import com.taobao.arthas.core.shell.impl.ShellServerImpl; +import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; +import com.taobao.arthas.core.shell.system.impl.JobControllerImpl; import com.taobao.arthas.core.shell.term.Term; import com.taobao.arthas.core.shell.term.TermServer; @@ -102,4 +104,14 @@ public abstract class ShellServer { * @param completionHandler handler for getting notified when service is stopped */ public abstract void close(Handler> completionHandler); + + /** + * @return global job controller instance + */ + public abstract JobControllerImpl getJobController(); + + /** + * @return get command manager instance + */ + public abstract InternalCommandManager getCommandManager(); } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellServerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellServerImpl.java index 444e42e5..d1cc2fa5 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellServerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellServerImpl.java @@ -241,4 +241,12 @@ public class ShellServerImpl extends ShellServer { bootstrap.destroy(); } } + + public JobControllerImpl getJobController() { + return jobController; + } + + public InternalCommandManager getCommandManager() { + return commandManager; + } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java b/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java index 5166e8a6..51ed4144 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java @@ -1,6 +1,5 @@ package com.taobao.arthas.core.shell.session; -import com.taobao.arthas.core.shell.ShellServer; import com.taobao.arthas.core.shell.command.CommandResolver; import java.lang.instrument.Instrumentation; @@ -23,6 +22,16 @@ public interface Session { */ String TTY = "tty"; + /** + * Session create time + */ + String CREATE_TIME = "createTime"; + + /** + * Session last active time + */ + String LAST_ACCESS_TIME = "lastAccessedTime"; + /** * Put some data in a session * @@ -101,4 +110,22 @@ public interface Session { * @return instrumentation instance */ Instrumentation getInstrumentation(); + + /** + * Update session last access time + * @param time new time + */ + void setLastAccessTime(long time); + + /** + * Get session last access time + * @return session last access time + */ + long getLastAccessTime(); + + /** + * Get session create time + * @return session create time + */ + long getCreateTime(); } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java b/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java index 011888fe..d64d277d 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java @@ -1,9 +1,27 @@ package com.taobao.arthas.core.shell.session; +import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; +import com.taobao.arthas.core.shell.system.impl.JobControllerImpl; + +import java.lang.instrument.Instrumentation; + /** * Arthas Session Manager * @author gongdewei 2020-03-20 */ public interface SessionManager { + Session createSession(); + + Session getSession(String sessionId); + + Session removeSession(String sessionId); + + void updateAccessTime(Session session); + + InternalCommandManager getCommandManager(); + + Instrumentation getInstrumentation(); + + JobControllerImpl getJobController(); } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java index a93931b0..f9a34b84 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java @@ -20,6 +20,12 @@ public class SessionImpl implements Session { private Map data = new HashMap(); + public SessionImpl() { + long now = System.currentTimeMillis(); + data.put(CREATE_TIME, now); + this.setLastAccessTime(now); + } + @Override public Session put(String key, Object obj) { if (obj == null) { @@ -83,4 +89,19 @@ public class SessionImpl implements Session { public Instrumentation getInstrumentation() { return (Instrumentation) data.get(INSTRUMENTATION); } + + @Override + public void setLastAccessTime(long time) { + data.put(LAST_ACCESS_TIME, time); + } + + @Override + public long getLastAccessTime() { + return (Long)data.get(LAST_ACCESS_TIME); + } + + @Override + public long getCreateTime() { + return (Long)data.get(CREATE_TIME); + } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java index 9ba2ed8e..0a82a647 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java @@ -1,9 +1,87 @@ package com.taobao.arthas.core.shell.session.impl; +import com.taobao.arthas.core.server.ArthasBootstrap; +import com.taobao.arthas.core.shell.ShellServerOptions; +import com.taobao.arthas.core.shell.session.Session; +import com.taobao.arthas.core.shell.session.SessionManager; +import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; +import com.taobao.arthas.core.shell.system.impl.JobControllerImpl; + +import java.lang.instrument.Instrumentation; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + /** * Arthas Session Manager * @author gongdewei 2020-03-20 */ -public interface SessionManagerImpl { +public class SessionManagerImpl implements SessionManager { + private final ArthasBootstrap bootstrap; + private final InternalCommandManager commandManager; + private final Instrumentation instrumentation; + private final JobControllerImpl jobController; + private final long timeoutMillis; + private final long reaperInterval; + private final Map sessions; + private final long pid; + private boolean closed = false; + + public SessionManagerImpl(ShellServerOptions options, ArthasBootstrap bootstrap, InternalCommandManager commandManager, + JobControllerImpl jobController) { + this.bootstrap = bootstrap; + this.commandManager = commandManager; + this.jobController = jobController; + this.sessions = new ConcurrentHashMap(); + this.timeoutMillis = options.getSessionTimeout(); + this.reaperInterval = options.getReaperInterval(); + this.instrumentation = options.getInstrumentation(); + this.pid = options.getPid(); + } + + + @Override + public Session createSession() { + Session session = new SessionImpl(); + session.put(Session.COMMAND_MANAGER, commandManager); + session.put(Session.INSTRUMENTATION, instrumentation); + session.put(Session.PID, pid); + //session.put(Session.SERVER, server); + //session.put(Session.TTY, term); + String sessionId = UUID.randomUUID().toString(); + session.put(Session.ID, sessionId); + + sessions.put(sessionId, session); + return session; + } + + @Override + public Session getSession(String sessionId) { + return sessions.get(sessionId); + } + + @Override + public Session removeSession(String sessionId) { + return sessions.remove(sessionId); + } + + @Override + public void updateAccessTime(Session session) { + session.setLastAccessTime(System.currentTimeMillis()); + } + + @Override + public InternalCommandManager getCommandManager() { + return commandManager; + } + + @Override + public Instrumentation getInstrumentation() { + return instrumentation; + } + @Override + public JobControllerImpl getJobController() { + return jobController; + } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java index 8276ecf4..1b04b836 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java @@ -3,6 +3,7 @@ package com.taobao.arthas.core.shell.term.impl.http; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URL; @@ -46,7 +47,7 @@ public class HttpRequestHandler extends SimpleChannelInboundHandler options; @Override @@ -18,6 +20,8 @@ public class ApiRequest { "action='" + action + '\'' + ", sync=" + sync + ", command='" + command + '\'' + + ", requestId='" + requestId + '\'' + + ", sessionId='" + sessionId + '\'' + ", options=" + options + '}'; } @@ -53,4 +57,20 @@ public class ApiRequest { public void setOptions(Map options) { this.options = options; } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiResponse.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiResponse.java index 5382aedf..e0d16a51 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiResponse.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiResponse.java @@ -17,56 +17,63 @@ public class ApiResponse { return requestId; } - public void setRequestId(String requestId) { + public ApiResponse setRequestId(String requestId) { this.requestId = requestId; + return this; } public ApiState getState() { return state; } - public void setState(ApiState state) { + public ApiResponse setState(ApiState state) { this.state = state; + return this; } public String getMessage() { return message; } - public void setMessage(String message) { + public ApiResponse setMessage(String message) { this.message = message; + return this; } public String getSessionId() { return sessionId; } - public void setSessionId(String sessionId) { + public ApiResponse setSessionId(String sessionId) { this.sessionId = sessionId; + return this; } public String getConsumerId() { return consumerId; } - public void setConsumerId(String consumerId) { + public ApiResponse setConsumerId(String consumerId) { this.consumerId = consumerId; + return this; } public String getJobId() { return jobId; } - public void setJobId(String jobId) { + public ApiResponse setJobId(String jobId) { this.jobId = jobId; + return this; } public T getBody() { return body; } - public void setBody(T body) { + public ApiResponse setBody(T body) { this.body = body; + return this; } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java index f4662c3c..aff84767 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java @@ -3,7 +3,8 @@ package com.taobao.arthas.core.shell.term.impl.http.api; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.ObjectMapper; import com.taobao.arthas.core.server.ArthasBootstrap; -import com.taobao.arthas.core.shell.ShellServer; +import com.taobao.arthas.core.shell.session.Session; +import com.taobao.arthas.core.shell.session.SessionManager; import com.taobao.arthas.core.util.LogUtil; import com.taobao.arthas.core.util.StringUtils; import com.taobao.middleware.logger.Logger; @@ -11,6 +12,10 @@ import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.*; import io.netty.util.CharsetUtil; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + /** * Http Restful Api Handler @@ -19,18 +24,36 @@ import io.netty.util.CharsetUtil; public class HttpApiHandler { private static final Logger logger = LogUtil.getArthasLogger(); + private final SessionManager sessionManager; + private final AtomicInteger requestIdGenerator = new AtomicInteger(0); + private static HttpApiHandler instance; + + public static HttpApiHandler getInstance() { + if (instance == null){ + synchronized (HttpApiHandler.class){ + instance = new HttpApiHandler(); + } + } + return instance; + } + + private HttpApiHandler() { + sessionManager = ArthasBootstrap.getInstance().getSessionManager(); + } public HttpResponse handle(FullHttpRequest request) throws Exception { DefaultFullHttpResponse response = new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.OK); - ApiResponse result = null; + ApiResponse result; String requestBody = null; + String requestId = "req_" + requestIdGenerator.addAndGet(1); try { HttpMethod method = request.method(); if (HttpMethod.POST.equals(method)){ requestBody = getBody(request); ApiRequest apiRequest = parseRequest(requestBody); + apiRequest.setRequestId(requestId); result = processRequest(apiRequest); } else { result = createResponse(ApiState.REFUSED, "Unsupported http method: "+method.name()); @@ -43,6 +66,7 @@ public class HttpApiHandler { result = createResponse(ApiState.FAILED, "The request was not processed"); } + result.setRequestId(requestId); String jsonResult = JSON.toJSONString(result); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8"); response.content().writeBytes(jsonResult.getBytes("UTF-8")); @@ -64,30 +88,85 @@ public class HttpApiHandler { private ApiResponse processRequest(ApiRequest apiRequest) { - String action = apiRequest.getAction(); - if ("exec".equalsIgnoreCase(action)){ - return processExecRequest(apiRequest); - } else if("init_session".equalsIgnoreCase(action)){ - return processInitSessionRequest(apiRequest); - } else if("close_session".equalsIgnoreCase(action)){ - return processCloseSessionRequest(apiRequest); + String actionStr = apiRequest.getAction(); + try { + if (StringUtils.isBlank(actionStr)){ + throw new ApiException("'action' is required"); + } + ApiAction action; + try { + action = ApiAction.valueOf(actionStr.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new ApiException("unknown action: "+actionStr); + } + + //no session required + if(ApiAction.INIT_SESSION.equals(action)){ + return processInitSessionRequest(apiRequest); + } + + //required session + String sessionId = apiRequest.getSessionId(); + if (StringUtils.isBlank(sessionId)) { + throw new ApiException("'sessionId' is required"); + } + Session session = sessionManager.getSession(sessionId); + if (session == null){ + throw new ApiException("session not found"); + } + sessionManager.updateAccessTime(session); + + switch (action) { + case EXEC: + return processExecRequest(apiRequest, session); + case SESSION_INFO: + return processSessionInfoRequest(apiRequest, session); + case CLOSE_SESSION: + return processCloseSessionRequest(apiRequest, session); + } + + } catch (ApiException e) { + logger.info("arthas", e.getMessage(), e); + return createResponse(ApiState.FAILED, e.getMessage()); + } catch (Throwable e) { + logger.error("arthas", "process http api request failed", e); + return createResponse(ApiState.FAILED, "process http api request failed"); } - return createResponse(ApiState.REFUSED, "Unsupported action: "+action); + return createResponse(ApiState.REFUSED, "Unsupported action: "+actionStr); + } + + private ApiResponse processInitSessionRequest(ApiRequest apiRequest) throws ApiException { + ApiResponse response = new ApiResponse(); + Session session = sessionManager.createSession(); + if (session != null) { + response.setSessionId(session.getSessionId()) + .setState(ApiState.SUCCEEDED); + } else { + throw new ApiException("create api session failed"); + } + return response; } - private ApiResponse processInitSessionRequest(ApiRequest apiRequest) { -// ShellServer shellServer = getShellServer(); -// shellServer.createShell() + private ApiResponse processSessionInfoRequest(ApiRequest apiRequest, Session session) { + ApiResponse response = new ApiResponse(); + Map body = new TreeMap(); + body.put("pid", session.getPid()); + body.put("sessionId", session.getSessionId()); + body.put("createTime", session.getCreateTime()); + body.put("lastAccessTime", session.getLastAccessTime()); - return null; + response.setState(ApiState.SUCCEEDED).setBody(body); + return response; } - private ApiResponse processCloseSessionRequest(ApiRequest apiRequest) { + private ApiResponse processCloseSessionRequest(ApiRequest apiRequest, Session session) { + sessionManager.removeSession(session.getSessionId()); + return null; } - private ApiResponse processExecRequest(ApiRequest apiRequest) { + private ApiResponse processExecRequest(ApiRequest apiRequest, Session session) { String command = apiRequest.getCommand(); //TODO return null; @@ -105,7 +184,4 @@ public class HttpApiHandler { return buf.toString(CharsetUtil.UTF_8); } -// private ShellServer getShellServer() { -// return ArthasBootstrap.getInstance().getShellServer(); -// } } -- Gitee From 418d463bddb8dfcba9373974f9190659422be93e Mon Sep 17 00:00:00 2001 From: gongdewei Date: Thu, 26 Mar 2020 20:18:09 +0800 Subject: [PATCH 008/135] add ResultDistributor to adapt Telnet term/Http API output, refactor Watch command --- .../command/basic1000/VersionCommand.java | 30 ++- .../command/monitor200/EnhancerCommand.java | 33 ++- .../monitor200/WatchAdviceListener.java | 9 +- .../command/result/EnhancerAffectResult.java | 30 +++ .../core/command/result/ErrorResult.java | 41 ++++ .../core/command/result/ExecResult.java | 35 ++++ .../core/command/result/WatchResult.java | 38 ++++ .../PackingResultDistributor.java | 14 ++ .../core/distribution/ResultConsumer.java | 27 +++ .../core/distribution/ResultDistributor.java | 17 ++ .../SharingResultDistributor.java | 17 ++ .../impl/PackingResultDistributorImpl.java | 39 ++++ .../impl/SharingResultDistributorImpl.java | 72 +++++++ .../impl/TermResultDistributorImpl.java | 24 +++ .../core/shell/command/CommandProcess.java | 15 ++ .../arthas/core/shell/impl/ShellImpl.java | 2 +- .../core/shell/system/JobController.java | 4 +- .../system/impl/GlobalJobControllerImpl.java | 5 +- .../shell/system/impl/JobControllerImpl.java | 14 +- .../core/shell/system/impl/JobImpl.java | 4 +- .../core/shell/system/impl/ProcessImpl.java | 27 ++- .../term/impl/http/api/HttpApiHandler.java | 197 +++++++++++++++++- 22 files changed, 650 insertions(+), 44 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/command/result/EnhancerAffectResult.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/result/ErrorResult.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/result/ExecResult.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/result/WatchResult.java create mode 100644 core/src/main/java/com/taobao/arthas/core/distribution/PackingResultDistributor.java create mode 100644 core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumer.java create mode 100644 core/src/main/java/com/taobao/arthas/core/distribution/ResultDistributor.java create mode 100644 core/src/main/java/com/taobao/arthas/core/distribution/SharingResultDistributor.java create mode 100644 core/src/main/java/com/taobao/arthas/core/distribution/impl/PackingResultDistributorImpl.java create mode 100644 core/src/main/java/com/taobao/arthas/core/distribution/impl/SharingResultDistributorImpl.java create mode 100644 core/src/main/java/com/taobao/arthas/core/distribution/impl/TermResultDistributorImpl.java diff --git a/core/src/main/java/com/taobao/arthas/core/command/basic1000/VersionCommand.java b/core/src/main/java/com/taobao/arthas/core/command/basic1000/VersionCommand.java index 04b60773..c4ef8896 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/basic1000/VersionCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/basic1000/VersionCommand.java @@ -1,6 +1,7 @@ package com.taobao.arthas.core.command.basic1000; +import com.taobao.arthas.core.command.result.ExecResult; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.ArthasBanner; @@ -15,8 +16,35 @@ import com.taobao.middleware.cli.annotations.Summary; @Name("version") @Summary("Display Arthas version") public class VersionCommand extends AnnotatedCommand { + @Override public void process(CommandProcess process) { - process.write(ArthasBanner.version()).write("\n").end(); + VersionResult result = new VersionResult(); + result.setVersion(ArthasBanner.version()); + process.appendResult(result); + process.end(); + } + + public static class VersionResult extends ExecResult { + + private String version; + + @Override + public String getType() { + return "version"; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + @Override + protected void write(CommandProcess process) { + writeln(process, this.version); + } } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java index bc57b17d..e1c0f153 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java @@ -8,6 +8,7 @@ import java.util.List; import com.taobao.arthas.core.advisor.AdviceListener; import com.taobao.arthas.core.advisor.Enhancer; import com.taobao.arthas.core.advisor.InvokeTraceable; +import com.taobao.arthas.core.command.result.EnhancerAffectResult; import com.taobao.arthas.core.shell.cli.Completion; import com.taobao.arthas.core.shell.cli.CompletionUtils; import com.taobao.arthas.core.shell.command.AnnotatedCommand; @@ -91,8 +92,7 @@ public abstract class EnhancerCommand extends AnnotatedCommand { protected void enhance(CommandProcess process) { Session session = process.session(); if (!session.tryLock()) { - process.write("someone else is enhancing classes, pls. wait.\n"); - process.end(); + process.end(-1, "someone else is enhancing classes, pls. wait."); return; } int lock = session.getLock(); @@ -100,7 +100,8 @@ public abstract class EnhancerCommand extends AnnotatedCommand { Instrumentation inst = session.getInstrumentation(); AdviceListener listener = getAdviceListener(process); if (listener == null) { - warn(process, "advice listener is null"); + logger.error(null, "advice listener is null"); + process.end(-1, "cannot operate the current command, pls. check arthas.log"); return; } boolean skipJDKTrace = false; @@ -108,18 +109,18 @@ public abstract class EnhancerCommand extends AnnotatedCommand { skipJDKTrace = ((AbstractTraceAdviceListener) listener).getCommand().isSkipJDKTrace(); } - EnhancerAffect effect = Enhancer.enhance(inst, lock, listener instanceof InvokeTraceable, + EnhancerAffect affect = Enhancer.enhance(inst, lock, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getMethodNameMatcher()); - if (effect.cCnt() == 0 || effect.mCnt() == 0) { + if (affect.cCnt() == 0 || affect.mCnt() == 0) { // no class effected // might be method code too large - process.write("No class or method is affected, try:\n" - + "1. sm CLASS_NAME METHOD_NAME to make sure the method you are tracing actually exists (it might be in your parent class).\n" - + "2. reset CLASS_NAME and try again, your method body might be too large.\n" - + "3. check arthas log: " + LogUtil.LOGGER_FILE + "\n" - + "4. visit https://github.com/alibaba/arthas/issues/47 for more details.\n"); - process.end(); + String msg = "No class or method is affected, try:\n" + + "1. sm CLASS_NAME METHOD_NAME to make sure the method you are tracing actually exists (it might be in your parent class).\n" + + "2. reset CLASS_NAME and try again, your method body might be too large.\n" + + "3. check arthas log: " + LogUtil.LOGGER_FILE + "\n" + + "4. visit https://github.com/alibaba/arthas/issues/47 for more details."; + process.end(-1, msg); return; } @@ -132,7 +133,7 @@ public abstract class EnhancerCommand extends AnnotatedCommand { } } - process.write(effect + "\n"); + process.appendResult(new EnhancerAffectResult(affect)); } catch (UnmodifiableClassException e) { logger.error(null, "error happens when enhancing class", e); } finally { @@ -147,12 +148,4 @@ public abstract class EnhancerCommand extends AnnotatedCommand { super.complete(completion); } - private static void warn(CommandProcess process, String message) { - logger.error(null, message); - process.write("cannot operate the current command, pls. check arthas.log\n"); - if (process.isForeground()) { - process.echoTips(Constants.Q_OR_CTRL_C_ABORT_MSG + "\n"); - } - } - } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java index 7bda4dcc..c14f16da 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java @@ -3,6 +3,7 @@ package com.taobao.arthas.core.command.monitor200; import com.taobao.arthas.core.advisor.Advice; import com.taobao.arthas.core.advisor.ArthasMethod; import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter; +import com.taobao.arthas.core.command.result.WatchResult; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.DateUtils; import com.taobao.arthas.core.util.LogUtil; @@ -82,7 +83,8 @@ class WatchAdviceListener extends ReflectAdviceListenerAdapter { Object value = getExpressionResult(command.getExpress(), advice, cost); String result = StringUtils.objectToString( isNeedExpand() ? new ObjectView(value, command.getExpand(), command.getSizeLimit()).draw() : value); - process.write("ts=" + DateUtils.getCurrentDate() + "; [cost=" + cost + "ms] result=" + result + "\n"); + //process.write("ts=" + DateUtils.getCurrentDate() + "; [cost=" + cost + "ms] result=" + result + "\n"); + process.appendResult(new WatchResult(DateUtils.getCurrentDate(), cost, result)); process.times().incrementAndGet(); if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) { abortProcess(process, command.getNumberOfLimit()); @@ -90,10 +92,9 @@ class WatchAdviceListener extends ReflectAdviceListenerAdapter { } } catch (Exception e) { logger.warn("watch failed.", e); - process.write("watch failed, condition is: " + command.getConditionExpress() + ", express is: " + process.end(-1, "watch failed, condition is: " + command.getConditionExpress() + ", express is: " + command.getExpress() + ", " + e.getMessage() + ", visit " + LogUtil.LOGGER_FILE - + " for more details.\n"); - process.end(); + + " for more details."); } } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/EnhancerAffectResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/EnhancerAffectResult.java new file mode 100644 index 00000000..63384551 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/result/EnhancerAffectResult.java @@ -0,0 +1,30 @@ +package com.taobao.arthas.core.command.result; + +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.affect.EnhancerAffect; + +public class EnhancerAffectResult extends ExecResult { + private EnhancerAffect affect; + + public EnhancerAffectResult(EnhancerAffect affect) { + this.affect = affect; + } + + public int getClassCount() { + return affect.cCnt(); + } + + public int getMethodCount() { + return affect.mCnt(); + } + + @Override + public String getType() { + return "EnhancerAffect"; + } + + @Override + protected void write(CommandProcess process) { + writeln(process, affect+""); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/ErrorResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/ErrorResult.java new file mode 100644 index 00000000..5b849410 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/result/ErrorResult.java @@ -0,0 +1,41 @@ +package com.taobao.arthas.core.command.result; + +import com.taobao.arthas.core.shell.command.CommandProcess; + +public class ErrorResult extends ExecResult { + private int statusCode; + private String message; + + public ErrorResult(int statusCode, String message) { + this.statusCode = statusCode; + this.message = message; + } + + public int getStatusCode() { + return statusCode; + } + + public ExecResult setStatusCode(int statusCode) { + this.statusCode = statusCode; + return this; + } + + public String getMessage() { + return message; + } + + public ExecResult setMessage(String message) { + this.message = message; + return this; + } + + @Override + public String getType() { + return "error"; + } + + @Override + protected void write(CommandProcess process) { + writeln(process, message); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/ExecResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/ExecResult.java new file mode 100644 index 00000000..bc295be6 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/result/ExecResult.java @@ -0,0 +1,35 @@ +package com.taobao.arthas.core.command.result; + +import com.taobao.arthas.core.shell.command.CommandProcess; + +/** + * Command execute result + * @author gongdewei 2020-03-26 + */ +public abstract class ExecResult { + + /** + * Command type (name) + * @return + */ + public abstract String getType(); + + /** + * Write command execute result to tty / term + * @param process + */ + public final void writeToTty(CommandProcess process){ + this.write(process); + } + + protected abstract void write(CommandProcess process); + + /** + * write str and append a new line + * @param process + * @param str + */ + protected void writeln(CommandProcess process, String str) { + process.write(str).write("\n"); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/WatchResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/WatchResult.java new file mode 100644 index 00000000..2a1ef935 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/result/WatchResult.java @@ -0,0 +1,38 @@ +package com.taobao.arthas.core.command.result; + +import com.taobao.arthas.core.shell.command.CommandProcess; + +public class WatchResult extends ExecResult { + + private String ts; + private double cost; + private String result; + + public WatchResult(String ts, double cost, String result) { + this.ts = ts; + this.cost = cost; + this.result = result; + } + + @Override + public String getType() { + return "watch"; + } + + public String getTs() { + return ts; + } + + public double getCost() { + return cost; + } + + public String getResult() { + return result; + } + + @Override + protected void write(CommandProcess process) { + process.write("ts=" + ts + "; [cost=" + cost + "ms] result=" + result + "\n"); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/PackingResultDistributor.java b/core/src/main/java/com/taobao/arthas/core/distribution/PackingResultDistributor.java new file mode 100644 index 00000000..2da090ad --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/distribution/PackingResultDistributor.java @@ -0,0 +1,14 @@ +package com.taobao.arthas.core.distribution; + +import com.taobao.arthas.core.command.result.ExecResult; + +import java.util.List; + +public interface PackingResultDistributor extends ResultDistributor { + + /** + * Get results of command + */ + List getResults(); + +} diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumer.java b/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumer.java new file mode 100644 index 00000000..a44df8b5 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumer.java @@ -0,0 +1,27 @@ +package com.taobao.arthas.core.distribution; + +import com.taobao.arthas.core.command.result.ExecResult; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Command result consumer + * @author gongdewei 2020-03-26 + */ +public interface ResultConsumer { + + /** + * Append the phased result to queue + * @param result a phased result of the command + */ + void appendResult(ExecResult result); + + /** + * Retrieves and removes a pack of results from the head + * @return a pack of results + */ + List pollResults(long timeout, TimeUnit unit); + + +} diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/ResultDistributor.java b/core/src/main/java/com/taobao/arthas/core/distribution/ResultDistributor.java new file mode 100644 index 00000000..3a855ad2 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/distribution/ResultDistributor.java @@ -0,0 +1,17 @@ +package com.taobao.arthas.core.distribution; + +import com.taobao.arthas.core.command.result.ExecResult; + +/** + * Command result distributor, sending results to consumers who joins in the same session. + * @author gongdewei 2020-03-26 + */ +public interface ResultDistributor { + + /** + * Append the phased result to queue + * @param result a phased result of the command + */ + void appendResult(ExecResult result); + +} diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/SharingResultDistributor.java b/core/src/main/java/com/taobao/arthas/core/distribution/SharingResultDistributor.java new file mode 100644 index 00000000..225c0454 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/distribution/SharingResultDistributor.java @@ -0,0 +1,17 @@ +package com.taobao.arthas.core.distribution; + +public interface SharingResultDistributor extends ResultDistributor { + + /** + * Add consumer to sharing session + * @param consumer + */ + void addConsumer(ResultConsumer consumer); + + /** + * Remove consumer from sharing session + * @param consumer + */ + void removeConsumer(ResultConsumer consumer); + +} diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/impl/PackingResultDistributorImpl.java b/core/src/main/java/com/taobao/arthas/core/distribution/impl/PackingResultDistributorImpl.java new file mode 100644 index 00000000..80c9a9d2 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/distribution/impl/PackingResultDistributorImpl.java @@ -0,0 +1,39 @@ +package com.taobao.arthas.core.distribution.impl; + +import com.alibaba.fastjson.JSON; +import com.taobao.arthas.core.command.result.ExecResult; +import com.taobao.arthas.core.distribution.PackingResultDistributor; +import com.taobao.arthas.core.shell.session.Session; +import com.taobao.arthas.core.util.LogUtil; +import com.taobao.middleware.logger.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public class PackingResultDistributorImpl implements PackingResultDistributor { + private static final Logger logger = LogUtil.getArthasLogger(); + + private BlockingQueue resultQueue = new ArrayBlockingQueue(500); + private final Session session; + + public PackingResultDistributorImpl(Session session) { + this.session = session; + } + + @Override + public void appendResult(ExecResult result) { + if (!resultQueue.offer(result)){ + logger.warn("arthas", "result queue is full: {}, discard later result: {}", resultQueue.size(), JSON.toJSONString(result)); + } + } + + @Override + public List getResults() { + ArrayList results = new ArrayList(resultQueue.size()); + resultQueue.drainTo(results); + return results; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/impl/SharingResultDistributorImpl.java b/core/src/main/java/com/taobao/arthas/core/distribution/impl/SharingResultDistributorImpl.java new file mode 100644 index 00000000..9f7e569f --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/distribution/impl/SharingResultDistributorImpl.java @@ -0,0 +1,72 @@ +package com.taobao.arthas.core.distribution.impl; + +import com.alibaba.fastjson.JSON; +import com.taobao.arthas.core.command.result.ExecResult; +import com.taobao.arthas.core.distribution.ResultConsumer; +import com.taobao.arthas.core.distribution.SharingResultDistributor; +import com.taobao.arthas.core.shell.session.Session; +import com.taobao.arthas.core.util.LogUtil; +import com.taobao.middleware.logger.Logger; + +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +public class SharingResultDistributorImpl implements SharingResultDistributor { + private static final Logger logger = LogUtil.getArthasLogger(); + + private List consumers = new CopyOnWriteArrayList(); + private BlockingQueue resultQueue = new ArrayBlockingQueue(500); + private final Session session; + private Thread distributorThread; + private volatile boolean running; + + public SharingResultDistributorImpl(Session session) { + this.session = session; + this.running = true; + distributorThread = new Thread(new DistributorTask(), "ResultDistributor"); + distributorThread.start(); + } + + @Override + public void appendResult(ExecResult result) { + while (!resultQueue.offer(result)) { + ExecResult discardResult = resultQueue.poll(); + //logger.warn("arthas", "result queue is full: {}, discard early result: {}", resultQueue.size(), JSON.toJSONString(discardResult)); + } + } + + private void distribute() { + while (running) { + try { + ExecResult result = resultQueue.poll(100, TimeUnit.MILLISECONDS); + if (result != null) { + for (ResultConsumer consumer : consumers) { + consumer.appendResult(result); + } + } + } catch (Throwable e) { + logger.warn("arthas", "distribute result failed", e); + } + } + } + + @Override + public void addConsumer(ResultConsumer consumer) { + consumers.add(consumer); + } + + @Override + public void removeConsumer(ResultConsumer consumer) { + consumers.remove(consumer); + } + + private class DistributorTask implements Runnable { + @Override + public void run() { + distribute(); + } + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/impl/TermResultDistributorImpl.java b/core/src/main/java/com/taobao/arthas/core/distribution/impl/TermResultDistributorImpl.java new file mode 100644 index 00000000..91cc1e4a --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/distribution/impl/TermResultDistributorImpl.java @@ -0,0 +1,24 @@ +package com.taobao.arthas.core.distribution.impl; + +import com.taobao.arthas.core.command.result.ExecResult; +import com.taobao.arthas.core.distribution.ResultDistributor; +import com.taobao.arthas.core.shell.command.CommandProcess; + +/** + * Term/Tty Result Distributor + * @author gongdewei 2020-03-26 + */ +public class TermResultDistributorImpl implements ResultDistributor { + + private final CommandProcess commandProcess; + + public TermResultDistributorImpl(CommandProcess commandProcess) { + this.commandProcess = commandProcess; + } + + @Override + public void appendResult(ExecResult result) { + result.writeToTty(commandProcess); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java b/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java index 6f85ca0b..3a0aa26c 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java @@ -1,6 +1,7 @@ package com.taobao.arthas.core.shell.command; import com.taobao.arthas.core.advisor.AdviceListener; +import com.taobao.arthas.core.command.result.ExecResult; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.session.Session; @@ -118,6 +119,13 @@ public interface CommandProcess extends Tty { */ void end(int status); + /** + * End the process. + * + * @param status the exit status. + */ + void end(int status, String message); + /** * Register listener @@ -167,4 +175,11 @@ public interface CommandProcess extends Tty { * Whether the process is running */ boolean isRunning(); + + /** + * Append the phased result to queue + * @param result a phased result of the command + */ + void appendResult(ExecResult result); + } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java index 10975748..70fc1e97 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java @@ -78,7 +78,7 @@ public class ShellImpl implements Shell { @Override public synchronized Job createJob(List args) { - Job job = jobController.createJob(commandManager, args, session, new ShellJobHandler(this), term); + Job job = jobController.createJob(commandManager, args, session, new ShellJobHandler(this), term, null); return job; } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/JobController.java b/core/src/main/java/com/taobao/arthas/core/shell/system/JobController.java index 785f52a0..4ba2455c 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/JobController.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/JobController.java @@ -1,5 +1,6 @@ package com.taobao.arthas.core.shell.system; +import com.taobao.arthas.core.distribution.ResultDistributor; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.session.Session; @@ -37,9 +38,10 @@ public interface JobController { * @param session the current session * @param jobHandler job event handler * @param term telnet term + * @param resultDistributor * @return the created job */ - Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term); + Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term, ResultDistributor resultDistributor); /** * Close the controller and terminate all the underlying jobs, a closed controller does not accept anymore jobs. diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java index efda8fe3..8855a554 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java @@ -1,6 +1,7 @@ package com.taobao.arthas.core.shell.system.impl; import com.taobao.arthas.core.GlobalOptions; +import com.taobao.arthas.core.distribution.ResultDistributor; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.session.Session; @@ -50,8 +51,8 @@ public class GlobalJobControllerImpl extends JobControllerImpl { } @Override - public Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term) { - final Job job = super.createJob(commandManager, tokens, session, jobHandler, term); + public Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term, ResultDistributor resultDistributor) { + final Job job = super.createJob(commandManager, tokens, session, jobHandler, term, resultDistributor); /* * 达到超时时间将会停止job diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java index 2836ff13..9e32ccd9 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java @@ -1,6 +1,7 @@ package com.taobao.arthas.core.shell.system.impl; import com.taobao.arthas.core.GlobalOptions; +import com.taobao.arthas.core.distribution.ResultDistributor; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.command.Command; import com.taobao.arthas.core.shell.command.internal.RedirectHandler; @@ -51,14 +52,14 @@ public class JobControllerImpl implements JobController { } @Override - public Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term) { + public Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term, ResultDistributor resultDistributor) { int jobId = idGenerator.incrementAndGet(); StringBuilder line = new StringBuilder(); for (CliToken arg : tokens) { line.append(arg.raw()); } boolean runInBackground = runInBackground(tokens); - Process process = createProcess(tokens, commandManager, jobId, term); + Process process = createProcess(tokens, commandManager, jobId, term, resultDistributor); process.setJobId(jobId); JobImpl job = new JobImpl(jobId, this, process, line.toString(), runInBackground, session, jobHandler); jobs.put(jobId, job); @@ -113,9 +114,10 @@ public class JobControllerImpl implements JobController { * @param commandManager command manager * @param jobId job id * @param term term + * @param resultDistributor * @return the created process */ - private Process createProcess(List line, InternalCommandManager commandManager, int jobId, Term term) { + private Process createProcess(List line, InternalCommandManager commandManager, int jobId, Term term, ResultDistributor resultDistributor) { try { ListIterator tokens = line.listIterator(); while (tokens.hasNext()) { @@ -123,7 +125,7 @@ public class JobControllerImpl implements JobController { if (token.isText()) { Command command = commandManager.getCommand(token.value()); if (command != null) { - return createCommandProcess(command, tokens, jobId, term); + return createCommandProcess(command, tokens, jobId, term, resultDistributor); } else { throw new IllegalArgumentException(token.value() + ": command not found"); } @@ -145,7 +147,7 @@ public class JobControllerImpl implements JobController { return runInBackground; } - private Process createCommandProcess(Command command, ListIterator tokens, int jobId, Term term) throws IOException { + private Process createCommandProcess(Command command, ListIterator tokens, int jobId, Term term, ResultDistributor resultDistributor) throws IOException { List remaining = new ArrayList(); List pipelineTokens = new ArrayList(); boolean isPipeline = false; @@ -192,7 +194,7 @@ public class JobControllerImpl implements JobController { } } ProcessOutput ProcessOutput = new ProcessOutput(stdoutHandlerChain, cacheLocation, term); - ProcessImpl process = new ProcessImpl(command, remaining, command.processHandler(), ProcessOutput); + ProcessImpl process = new ProcessImpl(command, remaining, command.processHandler(), ProcessOutput, resultDistributor); process.setTty(term); return process; } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java index c6d5bb8f..cd7763ea 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java @@ -254,9 +254,7 @@ public class JobImpl implements Job { // foregroundUpdatedHandler.handle(null); // } // } - if (actualStatus.equals(ExecStatus.RUNNING)) { - jobHandler.onTerminated(JobImpl.this); - } + jobHandler.onTerminated(JobImpl.this); controller.removeJob(JobImpl.this.id); if (statusUpdateHandler != null) { statusUpdateHandler.handle(ExecStatus.TERMINATED); diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java index d457129c..6c26ac88 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java @@ -2,6 +2,10 @@ package com.taobao.arthas.core.shell.system.impl; import com.taobao.arthas.core.advisor.AdviceListener; import com.taobao.arthas.core.advisor.AdviceWeaver; +import com.taobao.arthas.core.command.result.ErrorResult; +import com.taobao.arthas.core.command.result.ExecResult; +import com.taobao.arthas.core.distribution.ResultDistributor; +import com.taobao.arthas.core.distribution.impl.TermResultDistributorImpl; import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.command.Command; @@ -30,6 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** * @author beiwei30 on 10/11/2016. + * @author gongdewei 2020-03-26 */ public class ProcessImpl implements Process { @@ -57,12 +62,14 @@ public class ProcessImpl implements Process { private Date startTime; private ProcessOutput processOutput; private int jobId; + private ResultDistributor resultDistributor; public ProcessImpl(Command commandContext, List args, Handler handler, - ProcessOutput processOutput) { + ProcessOutput processOutput, ResultDistributor resultDistributor) { this.commandContext = commandContext; this.handler = handler; this.args = args; + this.resultDistributor = resultDistributor; this.processStatus = ExecStatus.READY; this.processOutput = processOutput; } @@ -328,6 +335,7 @@ public class ProcessImpl implements Process { CommandLine cl = null; try { if (commandContext.cli() != null) { + //TODO refactor help command if (commandContext.cli().parse(args2, false).isAskingForHelp()) { UsageMessageFormatter formatter = new StyledUsageFormatter(Color.green); formatter.setWidth(tty.width()); @@ -348,6 +356,9 @@ public class ProcessImpl implements Process { } process = new CommandProcessImpl(args2, tty, cl); + if (resultDistributor == null) { + resultDistributor = new TermResultDistributorImpl(process); + } if (cacheLocation() != null) { process.echoTips("job id : " + this.jobId + "\n"); process.echoTips("cache location : " + cacheLocation() + "\n"); @@ -370,8 +381,7 @@ public class ProcessImpl implements Process { handler.handle(process); } catch (Throwable t) { logger.error(null, "Error during processing the command:", t); - process.write("Error during processing the command: " + t.getMessage() + "\n"); - terminate(1, null); + process.end(1, "Error during processing the command: " + t.getMessage() ); } } } @@ -558,10 +568,21 @@ public class ProcessImpl implements Process { terminate(statusCode, null); } + @Override + public void end(int statusCode, String message) { + appendResult(new ErrorResult(statusCode, message)); + end(statusCode); + } + @Override public boolean isRunning() { return processStatus == ExecStatus.RUNNING; } + + @Override + public void appendResult(ExecResult result) { + resultDistributor.appendResult(result); + } } static class ProcessOutput { diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java index aff84767..7a6107d0 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java @@ -2,16 +2,31 @@ package com.taobao.arthas.core.shell.term.impl.http.api; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.ObjectMapper; +import com.taobao.arthas.core.distribution.PackingResultDistributor; +import com.taobao.arthas.core.distribution.ResultDistributor; +import com.taobao.arthas.core.distribution.impl.PackingResultDistributorImpl; import com.taobao.arthas.core.server.ArthasBootstrap; +import com.taobao.arthas.core.shell.cli.CliToken; +import com.taobao.arthas.core.shell.cli.CliTokens; +import com.taobao.arthas.core.shell.cli.Completion; +import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.session.SessionManager; +import com.taobao.arthas.core.shell.system.Job; +import com.taobao.arthas.core.shell.system.JobListener; +import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; +import com.taobao.arthas.core.shell.system.impl.JobControllerImpl; +import com.taobao.arthas.core.shell.term.SignalHandler; +import com.taobao.arthas.core.shell.term.Term; import com.taobao.arthas.core.util.LogUtil; import com.taobao.arthas.core.util.StringUtils; import com.taobao.middleware.logger.Logger; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.*; import io.netty.util.CharsetUtil; +import io.termd.core.function.Function; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; @@ -27,6 +42,8 @@ public class HttpApiHandler { private final SessionManager sessionManager; private final AtomicInteger requestIdGenerator = new AtomicInteger(0); private static HttpApiHandler instance; + private final InternalCommandManager commandManager; + private final JobControllerImpl jobController; public static HttpApiHandler getInstance() { if (instance == null){ @@ -39,6 +56,8 @@ public class HttpApiHandler { private HttpApiHandler() { sessionManager = ArthasBootstrap.getInstance().getSessionManager(); + commandManager = sessionManager.getCommandManager(); + jobController = sessionManager.getJobController(); } public HttpResponse handle(FullHttpRequest request) throws Exception { @@ -167,9 +186,64 @@ public class HttpApiHandler { } private ApiResponse processExecRequest(ApiRequest apiRequest, Session session) { - String command = apiRequest.getCommand(); - //TODO - return null; + + PackingResultDistributor packingResultDistributor = new PackingResultDistributorImpl(session); + + String commandLine = apiRequest.getCommand(); + Job job = this.createJob(commandLine, session, packingResultDistributor); + + if (apiRequest.isSync()) { + job.run(); + //get job result + } else { + //schedule job + } + + //wait for job completed or timeout + boolean timeExpired = !waitForJob(job); + if (timeExpired) { + logger.warn("arthas", "Job is exceeded time limit, force interrupt it, jobId: {}", job.id()); + job.interrupt(); + } + + //packing results + Map body = new TreeMap(); + body.put("jobId", job.id()); + body.put("jobStatus", job.status()); + body.put("sessionId", session.getSessionId()); + body.put("timeExpired", timeExpired); + body.put("results", packingResultDistributor.getResults()); + + ApiResponse response = new ApiResponse(); + response.setState(ApiState.SUCCEEDED).setBody(body); + return response; + } + + private boolean waitForJob(Job job) { + long startTime = System.currentTimeMillis(); + while(true){ + switch (job.status()){ + case STOPPED: + case TERMINATED: + return true; + } + if (System.currentTimeMillis() - startTime > 30000){ + return false; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + } + + private synchronized Job createJob(List args, Session session, ResultDistributor resultDistributor) { + Job job = jobController.createJob(commandManager, args, session, new ApiJobHandler(session), new ApiTerm(session), resultDistributor); + return job; + } + + private Job createJob(String line, Session session, ResultDistributor resultDistributor) { + return createJob(CliTokens.tokenize(line), session, resultDistributor); } private ApiResponse createResponse(ApiState apiState, String message) { @@ -184,4 +258,121 @@ public class HttpApiHandler { return buf.toString(CharsetUtil.UTF_8); } + private class ApiJobHandler implements JobListener { + + private Session session; + + public ApiJobHandler(Session session) { + this.session = session; + } + + @Override + public void onForeground(Job job) { + + } + + @Override + public void onBackground(Job job) { + + } + + @Override + public void onTerminated(Job job) { + + } + + @Override + public void onSuspend(Job job) { + + } + } + + private class ApiTerm implements Term { + + private Session session; + + public ApiTerm(Session session) { + this.session = session; + } + + @Override + public Term resizehandler(Handler handler) { + return this; + } + + @Override + public String type() { + return "web"; + } + + @Override + public int width() { + return 1000; + } + + @Override + public int height() { + return 200; + } + + @Override + public Term stdinHandler(Handler handler) { + return this; + } + + @Override + public Term stdoutHandler(Function handler) { + return this; + } + + @Override + public Term write(String data) { + return this; + } + + @Override + public long lastAccessedTime() { + return session.getLastAccessTime(); + } + + @Override + public Term echo(String text) { + return this; + } + + @Override + public Term setSession(Session session) { + return this; + } + + @Override + public Term interruptHandler(SignalHandler handler) { + return this; + } + + @Override + public Term suspendHandler(SignalHandler handler) { + return this; + } + + @Override + public void readline(String prompt, Handler lineHandler) { + + } + + @Override + public void readline(String prompt, Handler lineHandler, Handler completionHandler) { + + } + + @Override + public Term closeHandler(Handler handler) { + return this; + } + + @Override + public void close() { + + } + } } -- Gitee From 76a1ae5de09fa8486b18bd08eb1c7f27af34f7ae Mon Sep 17 00:00:00 2001 From: gongdewei Date: Fri, 27 Mar 2020 11:50:48 +0800 Subject: [PATCH 009/135] Extract ResultView from result model --- .gitignore | 2 +- .../command/basic1000/SessionCommand.java | 33 +++++----- .../command/basic1000/VersionCommand.java | 24 +------ .../command/result/EnhancerAffectResult.java | 12 ++-- .../core/command/result/ErrorResult.java | 9 +-- .../core/command/result/ExecResult.java | 22 +------ .../core/command/result/SessionResult.java | 60 +++++++++++++++++ .../core/command/result/VersionResult.java | 20 ++++++ .../core/command/result/WatchResult.java | 14 ++-- .../core/command/view/EnhancerAffectView.java | 16 +++++ .../arthas/core/command/view/ErrorView.java | 16 +++++ .../arthas/core/command/view/ResultView.java | 30 +++++++++ .../core/command/view/ResultViewResolver.java | 64 +++++++++++++++++++ .../arthas/core/command/view/SessionView.java | 36 +++++++++++ .../arthas/core/command/view/VersionView.java | 16 +++++ .../arthas/core/command/view/WatchView.java | 16 +++++ .../impl/TermResultDistributorImpl.java | 8 ++- 17 files changed, 321 insertions(+), 77 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/command/result/SessionResult.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/result/VersionResult.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/EnhancerAffectView.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/ErrorView.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/ResultView.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/SessionView.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/VersionView.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/WatchView.java diff --git a/.gitignore b/.gitignore index 15006371..bfdeb011 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ **/.settings **/.classpath **/.project -/.idea +.idea **/*.iml /nb-configuration.xml **/target diff --git a/core/src/main/java/com/taobao/arthas/core/command/basic1000/SessionCommand.java b/core/src/main/java/com/taobao/arthas/core/command/basic1000/SessionCommand.java index 8b74b4d4..ad4698ab 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/basic1000/SessionCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/basic1000/SessionCommand.java @@ -1,5 +1,7 @@ package com.taobao.arthas.core.command.basic1000; +import com.taobao.arthas.core.command.result.SessionResult; +import com.taobao.arthas.core.command.view.SessionView; import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; @@ -24,30 +26,31 @@ import com.alibaba.arthas.tunnel.client.TunnelClient; @Name("session") @Summary("Display current session information") public class SessionCommand extends AnnotatedCommand { + private SessionView sessionView = new SessionView(); + @Override public void process(CommandProcess process) { - process.write(RenderUtil.render(sessionTable(process.session()), process.width())).end(); - } + SessionResult result = new SessionResult(); + Session session = process.session(); + result.setJavaPid(session.getPid()); + result.setSessionId(session.getSessionId()); - /* - * 会话详情 - */ - private Element sessionTable(Session session) { - TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1); - table.row(true, label("Name").style(Decoration.bold.bold()), label("Value").style(Decoration.bold.bold())); - table.row("JAVA_PID", "" + session.getPid()).row("SESSION_ID", "" + session.getSessionId()); + //tunnel TunnelClient tunnelClient = ArthasBootstrap.getInstance().getTunnelClient(); if (tunnelClient != null) { String id = tunnelClient.getId(); if (id != null) { - table.row("AGENT_ID", "" + id); + result.setAgentId(id); } - table.row("TUNNEL_SERVER", "" + tunnelClient.getTunnelServerUrl()); + result.setTunnelServer(tunnelClient.getTunnelServerUrl()); } + + //statUrl String statUrl = UserStatUtil.getStatUrl(); - if (statUrl != null) { - table.row("STAT_URL", statUrl); - } - return table; + result.setStatUrl(statUrl); + + process.appendResult(result); + process.end(); } + } diff --git a/core/src/main/java/com/taobao/arthas/core/command/basic1000/VersionCommand.java b/core/src/main/java/com/taobao/arthas/core/command/basic1000/VersionCommand.java index c4ef8896..57a18688 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/basic1000/VersionCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/basic1000/VersionCommand.java @@ -1,7 +1,7 @@ package com.taobao.arthas.core.command.basic1000; -import com.taobao.arthas.core.command.result.ExecResult; +import com.taobao.arthas.core.command.result.VersionResult; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.ArthasBanner; @@ -25,26 +25,4 @@ public class VersionCommand extends AnnotatedCommand { process.end(); } - public static class VersionResult extends ExecResult { - - private String version; - - @Override - public String getType() { - return "version"; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - @Override - protected void write(CommandProcess process) { - writeln(process, this.version); - } - } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/EnhancerAffectResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/EnhancerAffectResult.java index 63384551..f07db5b8 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/result/EnhancerAffectResult.java +++ b/core/src/main/java/com/taobao/arthas/core/command/result/EnhancerAffectResult.java @@ -1,11 +1,13 @@ package com.taobao.arthas.core.command.result; -import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.affect.EnhancerAffect; public class EnhancerAffectResult extends ExecResult { private EnhancerAffect affect; + public EnhancerAffectResult() { + } + public EnhancerAffectResult(EnhancerAffect affect) { this.affect = affect; } @@ -18,13 +20,13 @@ public class EnhancerAffectResult extends ExecResult { return affect.mCnt(); } + public EnhancerAffect affect() { + return affect; + } + @Override public String getType() { return "EnhancerAffect"; } - @Override - protected void write(CommandProcess process) { - writeln(process, affect+""); - } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/ErrorResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/ErrorResult.java index 5b849410..13f47e24 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/result/ErrorResult.java +++ b/core/src/main/java/com/taobao/arthas/core/command/result/ErrorResult.java @@ -1,11 +1,12 @@ package com.taobao.arthas.core.command.result; -import com.taobao.arthas.core.shell.command.CommandProcess; - public class ErrorResult extends ExecResult { private int statusCode; private String message; + public ErrorResult() { + } + public ErrorResult(int statusCode, String message) { this.statusCode = statusCode; this.message = message; @@ -34,8 +35,4 @@ public class ErrorResult extends ExecResult { return "error"; } - @Override - protected void write(CommandProcess process) { - writeln(process, message); - } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/ExecResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/ExecResult.java index bc295be6..71cd443f 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/result/ExecResult.java +++ b/core/src/main/java/com/taobao/arthas/core/command/result/ExecResult.java @@ -1,35 +1,17 @@ package com.taobao.arthas.core.command.result; -import com.taobao.arthas.core.shell.command.CommandProcess; - /** * Command execute result + * * @author gongdewei 2020-03-26 */ public abstract class ExecResult { /** * Command type (name) + * * @return */ public abstract String getType(); - /** - * Write command execute result to tty / term - * @param process - */ - public final void writeToTty(CommandProcess process){ - this.write(process); - } - - protected abstract void write(CommandProcess process); - - /** - * write str and append a new line - * @param process - * @param str - */ - protected void writeln(CommandProcess process, String str) { - process.write(str).write("\n"); - } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/SessionResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/SessionResult.java new file mode 100644 index 00000000..6d9fe9a2 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/result/SessionResult.java @@ -0,0 +1,60 @@ +package com.taobao.arthas.core.command.result; + +/** + * Session command result model + * + * @author gongdewei 2020.03.27 + */ +public class SessionResult extends ExecResult { + + private long javaPid; + private String sessionId; + private String agentId; + private String tunnelServer; + private String statUrl; + + @Override + public String getType() { + return "session"; + } + + public long getJavaPid() { + return javaPid; + } + + public void setJavaPid(long javaPid) { + this.javaPid = javaPid; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getTunnelServer() { + return tunnelServer; + } + + public void setTunnelServer(String tunnelServer) { + this.tunnelServer = tunnelServer; + } + + public String getStatUrl() { + return statUrl; + } + + public void setStatUrl(String statUrl) { + this.statUrl = statUrl; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/VersionResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/VersionResult.java new file mode 100644 index 00000000..36bf0415 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/result/VersionResult.java @@ -0,0 +1,20 @@ +package com.taobao.arthas.core.command.result; + +public class VersionResult extends ExecResult { + + private String version; + + @Override + public String getType() { + return "version"; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/result/WatchResult.java b/core/src/main/java/com/taobao/arthas/core/command/result/WatchResult.java index 2a1ef935..0739e0fd 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/result/WatchResult.java +++ b/core/src/main/java/com/taobao/arthas/core/command/result/WatchResult.java @@ -1,13 +1,19 @@ package com.taobao.arthas.core.command.result; -import com.taobao.arthas.core.shell.command.CommandProcess; - +/** + * Watch command result model + * + * @author gongdewei 2020.03.26 + */ public class WatchResult extends ExecResult { private String ts; private double cost; private String result; + public WatchResult() { + } + public WatchResult(String ts, double cost, String result) { this.ts = ts; this.cost = cost; @@ -31,8 +37,4 @@ public class WatchResult extends ExecResult { return result; } - @Override - protected void write(CommandProcess process) { - process.write("ts=" + ts + "; [cost=" + cost + "ms] result=" + result + "\n"); - } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/EnhancerAffectView.java b/core/src/main/java/com/taobao/arthas/core/command/view/EnhancerAffectView.java new file mode 100644 index 00000000..a6e1c722 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/EnhancerAffectView.java @@ -0,0 +1,16 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.result.EnhancerAffectResult; +import com.taobao.arthas.core.shell.command.CommandProcess; + +/** + * @author gongdewei 2020/3/27 + */ +public class EnhancerAffectView extends ResultView { + + @Override + public void draw(CommandProcess process, EnhancerAffectResult result) { + writeln(process, result.affect() + ""); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ErrorView.java b/core/src/main/java/com/taobao/arthas/core/command/view/ErrorView.java new file mode 100644 index 00000000..56559c54 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ErrorView.java @@ -0,0 +1,16 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.result.ErrorResult; +import com.taobao.arthas.core.shell.command.CommandProcess; + +/** + * @author gongdewei 2020/3/27 + */ +public class ErrorView extends ResultView { + + @Override + public void draw(CommandProcess process, ErrorResult result) { + writeln(process, result.getMessage()); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ResultView.java b/core/src/main/java/com/taobao/arthas/core/command/view/ResultView.java new file mode 100644 index 00000000..19ccc7b6 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ResultView.java @@ -0,0 +1,30 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.result.ExecResult; +import com.taobao.arthas.core.shell.command.CommandProcess; + +/** + * Command result view for telnet term/tty. + * Note: Result view is reusable stateless instance + * + * @author gongdewei 2020/3/27 + */ +public abstract class ResultView { + + /** + * formatted printing data to term/tty + * + * @param process + */ + public abstract void draw(CommandProcess process, T result); + + /** + * write str and append a new line + * + * @param process + * @param str + */ + protected void writeln(CommandProcess process, String str) { + process.write(str).write("\n"); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java b/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java new file mode 100644 index 00000000..c4c6124f --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java @@ -0,0 +1,64 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.result.*; +import com.taobao.arthas.core.util.LogUtil; +import com.taobao.middleware.logger.Logger; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author gongdewei 2020/3/27 + */ +public class ResultViewResolver { + private static final Logger logger = LogUtil.getArthasLogger(); + + private Map resultViewMap = new ConcurrentHashMap(); + + private static ResultViewResolver viewResolver; + + public static ResultViewResolver getInstance() { + if (viewResolver == null) { + synchronized (ResultViewResolver.class) { + viewResolver = new ResultViewResolver(); + } + } + return viewResolver; + } + + static { + getInstance().registerResultViews(); + } + + private void registerResultViews() { + try { + registerView(new SessionResult(), new SessionView()); + registerView(new ErrorResult(), new ErrorView()); + registerView(new WatchResult(), new WatchView()); + registerView(new EnhancerAffectResult(), new EnhancerAffectView()); + registerView(new VersionResult(), new VersionView()); + } catch (Throwable e) { + logger.error("arthas", "register result view failed", e); + } + } + + private ResultViewResolver() { + } + +// public void registerView(Class resultClass, ResultView view) throws IllegalAccessException, InstantiationException { +// ExecResult instance = resultClass.newInstance(); +// this.registerView(instance.getType(), view); +// } + + public void registerView(T resultObject, ResultView view) { + this.registerView(resultObject.getType(), view); + } + + public void registerView(String resultType, ResultView view) { + resultViewMap.put(resultType, view); + } + + public ResultView getResultView(String resultType) { + return resultViewMap.get(resultType); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/SessionView.java b/core/src/main/java/com/taobao/arthas/core/command/view/SessionView.java new file mode 100644 index 00000000..c8556d19 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/SessionView.java @@ -0,0 +1,36 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.result.SessionResult; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.text.Decoration; +import com.taobao.text.ui.TableElement; +import com.taobao.text.util.RenderUtil; + +import static com.taobao.text.ui.Element.label; + +/** + * Term / Tty view for session result + * + * @author gongdewei 2020/3/27 + */ +public class SessionView extends ResultView { + + @Override + public void draw(CommandProcess process, SessionResult result) { + //会话详情 + TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1); + table.row(true, label("Name").style(Decoration.bold.bold()), label("Value").style(Decoration.bold.bold())); + table.row("JAVA_PID", "" + result.getJavaPid()).row("SESSION_ID", "" + result.getSessionId()); + if (result.getAgentId() != null) { + table.row("AGENT_ID", "" + result.getAgentId()); + } + if (result.getTunnelServer() != null) { + table.row("TUNNEL_SERVER", "" + result.getTunnelServer()); + } + if (result.getStatUrl() != null) { + table.row("STAT_URL", result.getStatUrl()); + } + process.write(RenderUtil.render(table, process.width())); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/VersionView.java b/core/src/main/java/com/taobao/arthas/core/command/view/VersionView.java new file mode 100644 index 00000000..83702dcd --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/VersionView.java @@ -0,0 +1,16 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.result.VersionResult; +import com.taobao.arthas.core.shell.command.CommandProcess; + +/** + * @author gongdewei 2020/3/27 + */ +public class VersionView extends ResultView { + + @Override + public void draw(CommandProcess process, VersionResult result) { + writeln(process, result.getVersion()); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/WatchView.java b/core/src/main/java/com/taobao/arthas/core/command/view/WatchView.java new file mode 100644 index 00000000..c5591392 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/WatchView.java @@ -0,0 +1,16 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.result.WatchResult; +import com.taobao.arthas.core.shell.command.CommandProcess; + +/** + * @author gongdewei 2020/3/27 + */ +public class WatchView extends ResultView { + + @Override + public void draw(CommandProcess process, WatchResult result) { + process.write("ts=" + result.getTs() + "; [cost=" + result.getCost() + "ms] result=" + result.getResult() + "\n"); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/impl/TermResultDistributorImpl.java b/core/src/main/java/com/taobao/arthas/core/distribution/impl/TermResultDistributorImpl.java index 91cc1e4a..eecd2c4d 100644 --- a/core/src/main/java/com/taobao/arthas/core/distribution/impl/TermResultDistributorImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/distribution/impl/TermResultDistributorImpl.java @@ -1,24 +1,30 @@ package com.taobao.arthas.core.distribution.impl; import com.taobao.arthas.core.command.result.ExecResult; +import com.taobao.arthas.core.command.view.ResultView; +import com.taobao.arthas.core.command.view.ResultViewResolver; import com.taobao.arthas.core.distribution.ResultDistributor; import com.taobao.arthas.core.shell.command.CommandProcess; /** * Term/Tty Result Distributor + * * @author gongdewei 2020-03-26 */ public class TermResultDistributorImpl implements ResultDistributor { private final CommandProcess commandProcess; + private final ResultViewResolver resultViewResolver; public TermResultDistributorImpl(CommandProcess commandProcess) { this.commandProcess = commandProcess; + this.resultViewResolver = ResultViewResolver.getInstance(); } @Override public void appendResult(ExecResult result) { - result.writeToTty(commandProcess); + ResultView resultView = resultViewResolver.getResultView(result.getType()); + resultView.draw(commandProcess, result); } } -- Gitee From 3ee6b6b7024f3a559bba40a0151b9230a56b9f83 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Fri, 27 Mar 2020 19:09:43 +0800 Subject: [PATCH 010/135] Support async exec command and pull results, join in sharing session --- .../arthas/core/command/view/ResultView.java | 2 +- .../core/distribution/ResultConsumer.java | 9 +- .../SharingResultDistributor.java | 6 + .../distribution/impl/ResultConsumerImpl.java | 115 ++++++++++++ .../impl/SharingResultDistributorImpl.java | 19 +- .../arthas/core/shell/session/Session.java | 19 ++ .../core/shell/session/impl/SessionImpl.java | 11 ++ .../session/impl/SessionManagerImpl.java | 6 + .../shell/term/impl/http/api/ApiAction.java | 31 ++++ .../shell/term/impl/http/api/ApiRequest.java | 23 +-- .../shell/term/impl/http/api/ApiState.java | 5 +- .../term/impl/http/api/HttpApiHandler.java | 168 ++++++++++++++---- 12 files changed, 359 insertions(+), 55 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/distribution/impl/ResultConsumerImpl.java diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ResultView.java b/core/src/main/java/com/taobao/arthas/core/command/view/ResultView.java index 19ccc7b6..353c03a8 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/view/ResultView.java +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ResultView.java @@ -5,7 +5,7 @@ import com.taobao.arthas.core.shell.command.CommandProcess; /** * Command result view for telnet term/tty. - * Note: Result view is reusable stateless instance + * Note: Result view is a reusable and stateless instance * * @author gongdewei 2020/3/27 */ diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumer.java b/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumer.java index a44df8b5..7c570dd6 100644 --- a/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumer.java +++ b/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumer.java @@ -3,7 +3,6 @@ package com.taobao.arthas.core.distribution; import com.taobao.arthas.core.command.result.ExecResult; import java.util.List; -import java.util.concurrent.TimeUnit; /** * Command result consumer @@ -21,7 +20,13 @@ public interface ResultConsumer { * Retrieves and removes a pack of results from the head * @return a pack of results */ - List pollResults(long timeout, TimeUnit unit); + List pollResults(); + long getLastAccessTime(); + boolean isPolling(); + + String getConsumerId(); + + void setConsumerId(String consumerId); } diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/SharingResultDistributor.java b/core/src/main/java/com/taobao/arthas/core/distribution/SharingResultDistributor.java index 225c0454..fdf2181a 100644 --- a/core/src/main/java/com/taobao/arthas/core/distribution/SharingResultDistributor.java +++ b/core/src/main/java/com/taobao/arthas/core/distribution/SharingResultDistributor.java @@ -14,4 +14,10 @@ public interface SharingResultDistributor extends ResultDistributor { */ void removeConsumer(ResultConsumer consumer); + /** + * Get consumer by id + * @param consumerId + * @return + */ + ResultConsumer getConsumer(String consumerId); } diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/impl/ResultConsumerImpl.java b/core/src/main/java/com/taobao/arthas/core/distribution/impl/ResultConsumerImpl.java new file mode 100644 index 00000000..f08ba5cc --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/distribution/impl/ResultConsumerImpl.java @@ -0,0 +1,115 @@ +package com.taobao.arthas.core.distribution.impl; + +import com.taobao.arthas.core.command.result.ExecResult; +import com.taobao.arthas.core.distribution.ResultConsumer; +import com.taobao.arthas.core.util.LogUtil; +import com.taobao.middleware.logger.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author gongdewei 2020/3/27 + */ +public class ResultConsumerImpl implements ResultConsumer { + private static final Logger logger = LogUtil.getArthasLogger(); + private BlockingQueue resultQueue = new ArrayBlockingQueue(500); + private long lastAccessTime; + private volatile boolean polling; + private ReentrantLock lock = new ReentrantLock(); + private int resultSizeLimit = 32; + private long pollTimeLimit = 10*1000; + private String consumerId; + + public ResultConsumerImpl() { + lastAccessTime = System.currentTimeMillis(); + } + + @Override + public void appendResult(ExecResult result) { + while (!resultQueue.offer(result)) { + ExecResult discardResult = resultQueue.poll(); + } + } + + @Override + public List pollResults() { + try { + long accessTime = System.currentTimeMillis(); + if (lock.tryLock(500, TimeUnit.MILLISECONDS)) { + polling = true; + long firstResultTime = 0; + // sending delay: time elapsed after firstResultTime + long sendingDelay = 0; + // waiting time: time elapsed after access + long waitingTime = 0; + List sendingResults = new ArrayList(resultSizeLimit); + + while (sendingResults.size() < resultSizeLimit + && sendingDelay < 200 + && waitingTime < pollTimeLimit) { + ExecResult aResult = resultQueue.poll(100, TimeUnit.MILLISECONDS); + if (aResult != null) { + sendingResults.add(aResult); + //是否为第一次获取到数据 + if (firstResultTime == 0){ + firstResultTime = System.currentTimeMillis(); + } + } else { + if (firstResultTime > 0){ + //获取到部分数据后,队列已经取完,计算发送延时时间 + sendingDelay = System.currentTimeMillis() - firstResultTime; + } + //计算总共等待时间,长轮询最大等待时间 + waitingTime = System.currentTimeMillis() - accessTime; + } + } + + //resultQueue.drainTo(sendingResults, resultSizeLimit-sendingResults.size()); + return sendingResults; + } + } catch (InterruptedException e) { + //e.printStackTrace(); + } finally { + if (lock.isHeldByCurrentThread()) { + polling = false; + lastAccessTime = System.currentTimeMillis(); + lock.unlock(); + } + } + return Collections.emptyList(); + } + + @Override + public long getLastAccessTime() { + return lastAccessTime; + } + + @Override + public boolean isPolling() { + return polling; + } + + public int getResultSizeLimit() { + return resultSizeLimit; + } + + public void setResultSizeLimit(int resultSizeLimit) { + this.resultSizeLimit = resultSizeLimit; + } + + @Override + public String getConsumerId() { + return consumerId; + } + + @Override + public void setConsumerId(String consumerId) { + this.consumerId = consumerId; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/impl/SharingResultDistributorImpl.java b/core/src/main/java/com/taobao/arthas/core/distribution/impl/SharingResultDistributorImpl.java index 9f7e569f..c54b7e66 100644 --- a/core/src/main/java/com/taobao/arthas/core/distribution/impl/SharingResultDistributorImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/distribution/impl/SharingResultDistributorImpl.java @@ -1,6 +1,5 @@ package com.taobao.arthas.core.distribution.impl; -import com.alibaba.fastjson.JSON; import com.taobao.arthas.core.command.result.ExecResult; import com.taobao.arthas.core.distribution.ResultConsumer; import com.taobao.arthas.core.distribution.SharingResultDistributor; @@ -9,10 +8,12 @@ import com.taobao.arthas.core.util.LogUtil; import com.taobao.middleware.logger.Logger; import java.util.List; +import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; public class SharingResultDistributorImpl implements SharingResultDistributor { private static final Logger logger = LogUtil.getArthasLogger(); @@ -22,6 +23,7 @@ public class SharingResultDistributorImpl implements SharingResultDistributor { private final Session session; private Thread distributorThread; private volatile boolean running; + private AtomicInteger consumerNumGenerator = new AtomicInteger(0); public SharingResultDistributorImpl(Session session) { this.session = session; @@ -48,13 +50,16 @@ public class SharingResultDistributorImpl implements SharingResultDistributor { } } } catch (Throwable e) { - logger.warn("arthas", "distribute result failed", e); + logger.warn("arthas", "distribute result failed: " + e.getMessage(), e); } } } @Override public void addConsumer(ResultConsumer consumer) { + int consumerNo = consumerNumGenerator.incrementAndGet(); + String consumerId = UUID.randomUUID().toString().replaceAll("-", "") + "_" + consumerNo; + consumer.setConsumerId(consumerId); consumers.add(consumer); } @@ -63,6 +68,16 @@ public class SharingResultDistributorImpl implements SharingResultDistributor { consumers.remove(consumer); } + @Override + public ResultConsumer getConsumer(String consumerId) { + for (ResultConsumer consumer : consumers) { + if (consumer.getConsumerId().equals(consumerId)) { + return consumer; + } + } + return null; + } + private class DistributorTask implements Runnable { @Override public void run() { diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java b/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java index 51ed4144..13d5fb63 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/Session.java @@ -1,5 +1,6 @@ package com.taobao.arthas.core.shell.session; +import com.taobao.arthas.core.distribution.SharingResultDistributor; import com.taobao.arthas.core.shell.command.CommandResolver; import java.lang.instrument.Instrumentation; @@ -32,6 +33,12 @@ public interface Session { */ String LAST_ACCESS_TIME = "lastAccessedTime"; + /** + * Command Result Distributor + */ + String RESULT_DISTRIBUTOR = "resultDistributor"; + + /** * Put some data in a session * @@ -128,4 +135,16 @@ public interface Session { * @return session create time */ long getCreateTime(); + + /** + * Update session's command result distributor + * @param resultDistributor + */ + void setResultDistributor(SharingResultDistributor resultDistributor); + + /** + * Get session's command result distributor + * @return + */ + SharingResultDistributor getResultDistributor(); } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java index f9a34b84..7265a05f 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionImpl.java @@ -1,5 +1,6 @@ package com.taobao.arthas.core.shell.session.impl; +import com.taobao.arthas.core.distribution.SharingResultDistributor; import com.taobao.arthas.core.shell.command.CommandResolver; import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; @@ -104,4 +105,14 @@ public class SessionImpl implements Session { public long getCreateTime() { return (Long)data.get(CREATE_TIME); } + + @Override + public void setResultDistributor(SharingResultDistributor resultDistributor) { + data.put(RESULT_DISTRIBUTOR, resultDistributor); + } + + @Override + public SharingResultDistributor getResultDistributor() { + return (SharingResultDistributor) data.get(RESULT_DISTRIBUTOR); + } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java index 0a82a647..f388a777 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java @@ -1,5 +1,7 @@ package com.taobao.arthas.core.shell.session.impl; +import com.taobao.arthas.core.distribution.SharingResultDistributor; +import com.taobao.arthas.core.distribution.impl.SharingResultDistributorImpl; import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.ShellServerOptions; import com.taobao.arthas.core.shell.session.Session; @@ -14,6 +16,7 @@ import java.util.concurrent.ConcurrentHashMap; /** * Arthas Session Manager + * * @author gongdewei 2020-03-20 */ public class SessionManagerImpl implements SessionManager { @@ -51,6 +54,9 @@ public class SessionManagerImpl implements SessionManager { String sessionId = UUID.randomUUID().toString(); session.put(Session.ID, sessionId); + //Result Distributor + session.setResultDistributor(new SharingResultDistributorImpl(session)); + sessions.put(sessionId, session); return session; } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiAction.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiAction.java index 04e02fce..71d9df5f 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiAction.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiAction.java @@ -2,11 +2,42 @@ package com.taobao.arthas.core.shell.term.impl.http.api; /** * Http api action enums + * * @author gongdewei 2020-03-25 */ public enum ApiAction { + /** + * Execute command synchronized + */ EXEC, + + /** + * Execute command async + */ + ASYNC_EXEC, + + /** + * Pull the results from result queue of the session + */ + PULL_RESULTS, + + /** + * Create a new session + */ INIT_SESSION, + + /** + * Join a exist session + */ + JOIN_SESSION, + + /** + * Terminate the session + */ CLOSE_SESSION, + + /** + * Get session info + */ SESSION_INFO } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiRequest.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiRequest.java index fa018631..55a48d0d 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiRequest.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiRequest.java @@ -3,25 +3,26 @@ package com.taobao.arthas.core.shell.term.impl.http.api; import java.util.Map; /** - * Http Api exception + * Http Api request + * * @author gongdewei 2020-03-19 */ public class ApiRequest { private String action; - private boolean sync; private String command; private String requestId; private String sessionId; + private String consumerId; private Map options; @Override public String toString() { return "ApiRequest{" + "action='" + action + '\'' + - ", sync=" + sync + ", command='" + command + '\'' + ", requestId='" + requestId + '\'' + ", sessionId='" + sessionId + '\'' + + ", consumerId='" + consumerId + '\'' + ", options=" + options + '}'; } @@ -34,14 +35,6 @@ public class ApiRequest { this.action = action; } - public boolean isSync() { - return sync; - } - - public void setSync(boolean sync) { - this.sync = sync; - } - public String getCommand() { return command; } @@ -73,4 +66,12 @@ public class ApiRequest { public void setSessionId(String sessionId) { this.sessionId = sessionId; } + + public String getConsumerId() { + return consumerId; + } + + public void setConsumerId(String consumerId) { + this.consumerId = consumerId; + } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiState.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiState.java index ec3cfce0..e4dcc5bd 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiState.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/ApiState.java @@ -2,10 +2,13 @@ package com.taobao.arthas.core.shell.term.impl.http.api; /** * Http API response state + * * @author gongdewei 2020-03-19 */ public enum ApiState { - /** accepted */ + /** + * Scheduled async exec job + */ SCHEDULED, RUNNING, SUCCEEDED, diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java index 7a6107d0..3c41ea03 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java @@ -2,9 +2,12 @@ package com.taobao.arthas.core.shell.term.impl.http.api; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.ObjectMapper; +import com.taobao.arthas.core.command.result.ExecResult; import com.taobao.arthas.core.distribution.PackingResultDistributor; +import com.taobao.arthas.core.distribution.ResultConsumer; import com.taobao.arthas.core.distribution.ResultDistributor; import com.taobao.arthas.core.distribution.impl.PackingResultDistributorImpl; +import com.taobao.arthas.core.distribution.impl.ResultConsumerImpl; import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.cli.CliTokens; @@ -34,6 +37,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Http Restful Api Handler + * * @author gongdewei 2020-03-18 */ public class HttpApiHandler { @@ -46,8 +50,8 @@ public class HttpApiHandler { private final JobControllerImpl jobController; public static HttpApiHandler getInstance() { - if (instance == null){ - synchronized (HttpApiHandler.class){ + if (instance == null) { + synchronized (HttpApiHandler.class) { instance = new HttpApiHandler(); } } @@ -69,17 +73,17 @@ public class HttpApiHandler { String requestId = "req_" + requestIdGenerator.addAndGet(1); try { HttpMethod method = request.method(); - if (HttpMethod.POST.equals(method)){ + if (HttpMethod.POST.equals(method)) { requestBody = getBody(request); ApiRequest apiRequest = parseRequest(requestBody); apiRequest.setRequestId(requestId); result = processRequest(apiRequest); } else { - result = createResponse(ApiState.REFUSED, "Unsupported http method: "+method.name()); + result = createResponse(ApiState.REFUSED, "Unsupported http method: " + method.name()); } } catch (Throwable e) { - result = createResponse(ApiState.FAILED, "Process request error: "+e.getMessage()); - logger.error("arthas", "arthas process http api request error: " + request.uri()+", request body: "+requestBody, e); + result = createResponse(ApiState.FAILED, "Process request error: " + e.getMessage()); + logger.error("arthas", "arthas process http api request error: " + request.uri() + ", request body: " + requestBody, e); } if (result == null) { result = createResponse(ApiState.FAILED, "The request was not processed"); @@ -93,7 +97,7 @@ public class HttpApiHandler { } private ApiRequest parseRequest(String requestBody) throws ApiException { - if (StringUtils.isBlank(requestBody)){ + if (StringUtils.isBlank(requestBody)) { throw new ApiException("parse request failed: request body is empty"); } try { @@ -101,7 +105,7 @@ public class HttpApiHandler { ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.readValue(requestBody, ApiRequest.class); } catch (Exception e) { - throw new ApiException("parse request failed: "+e.getMessage(), e); + throw new ApiException("parse request failed: " + e.getMessage(), e); } } @@ -109,18 +113,18 @@ public class HttpApiHandler { String actionStr = apiRequest.getAction(); try { - if (StringUtils.isBlank(actionStr)){ + if (StringUtils.isBlank(actionStr)) { throw new ApiException("'action' is required"); } ApiAction action; try { - action = ApiAction.valueOf(actionStr.toUpperCase()); + action = ApiAction.valueOf(actionStr.trim().toUpperCase()); } catch (IllegalArgumentException e) { - throw new ApiException("unknown action: "+actionStr); + throw new ApiException("unknown action: " + actionStr); } //no session required - if(ApiAction.INIT_SESSION.equals(action)){ + if (ApiAction.INIT_SESSION.equals(action)) { return processInitSessionRequest(apiRequest); } @@ -130,36 +134,61 @@ public class HttpApiHandler { throw new ApiException("'sessionId' is required"); } Session session = sessionManager.getSession(sessionId); - if (session == null){ - throw new ApiException("session not found"); + if (session == null) { + throw new ApiException("session not found: "+sessionId); } sessionManager.updateAccessTime(session); - switch (action) { - case EXEC: - return processExecRequest(apiRequest, session); - case SESSION_INFO: - return processSessionInfoRequest(apiRequest, session); - case CLOSE_SESSION: - return processCloseSessionRequest(apiRequest, session); + //dispatch requests + ApiResponse response = dispatchRequest(action, apiRequest, session); + if (response != null) { + return response; } } catch (ApiException e) { logger.info("arthas", e.getMessage(), e); return createResponse(ApiState.FAILED, e.getMessage()); } catch (Throwable e) { - logger.error("arthas", "process http api request failed", e); - return createResponse(ApiState.FAILED, "process http api request failed"); + logger.error("arthas", "process http api request failed: " + e.getMessage(), e); + return createResponse(ApiState.FAILED, "process http api request failed: " + e.getMessage()); } - return createResponse(ApiState.REFUSED, "Unsupported action: "+actionStr); + return createResponse(ApiState.REFUSED, "Unsupported action: " + actionStr); + } + + private ApiResponse dispatchRequest(ApiAction action, ApiRequest apiRequest, Session session) throws ApiException { + switch (action) { + case EXEC: + return processExecRequest(apiRequest, session); + case ASYNC_EXEC: + return processAsyncExecRequest(apiRequest, session); + case PULL_RESULTS: + return processPullResultsRequest(apiRequest, session); + case SESSION_INFO: + return processSessionInfoRequest(apiRequest, session); + case JOIN_SESSION: + return processJoinSessionRequest(apiRequest, session); + case CLOSE_SESSION: + return processCloseSessionRequest(apiRequest, session); + case INIT_SESSION: + break; + } + return null; } private ApiResponse processInitSessionRequest(ApiRequest apiRequest) throws ApiException { ApiResponse response = new ApiResponse(); + + //create session Session session = sessionManager.createSession(); if (session != null) { + + //create consumer + ResultConsumer resultConsumer = new ResultConsumerImpl(); + session.getResultDistributor().addConsumer(resultConsumer); + response.setSessionId(session.getSessionId()) + .setConsumerId(resultConsumer.getConsumerId()) .setState(ApiState.SUCCEEDED); } else { throw new ApiException("create api session failed"); @@ -167,6 +196,19 @@ public class HttpApiHandler { return response; } + private ApiResponse processJoinSessionRequest(ApiRequest apiRequest, Session session) { + + //create consumer + ResultConsumer resultConsumer = new ResultConsumerImpl(); + session.getResultDistributor().addConsumer(resultConsumer); + + ApiResponse response = new ApiResponse(); + response.setSessionId(session.getSessionId()) + .setConsumerId(resultConsumer.getConsumerId()) + .setState(ApiState.SUCCEEDED); + return response; + } + private ApiResponse processSessionInfoRequest(ApiRequest apiRequest, Session session) { ApiResponse response = new ApiResponse(); Map body = new TreeMap(); @@ -181,23 +223,25 @@ public class HttpApiHandler { private ApiResponse processCloseSessionRequest(ApiRequest apiRequest, Session session) { sessionManager.removeSession(session.getSessionId()); - - return null; + ApiResponse response = new ApiResponse(); + response.setState(ApiState.SUCCEEDED); + return response; } + /** + * Execute command sync, wait for job finish or timeout, sending results immediately + * + * @param apiRequest + * @param session + * @return + */ private ApiResponse processExecRequest(ApiRequest apiRequest, Session session) { PackingResultDistributor packingResultDistributor = new PackingResultDistributorImpl(session); String commandLine = apiRequest.getCommand(); Job job = this.createJob(commandLine, session, packingResultDistributor); - - if (apiRequest.isSync()) { - job.run(); - //get job result - } else { - //schedule job - } + job.run(); //wait for job completed or timeout boolean timeExpired = !waitForJob(job); @@ -219,15 +263,63 @@ public class HttpApiHandler { return response; } + /** + * Execute command async, create and schedule the job running, but no wait for the results. + * + * @param apiRequest + * @param session + * @return + */ + private ApiResponse processAsyncExecRequest(ApiRequest apiRequest, Session session) { + String commandLine = apiRequest.getCommand(); + Job job = this.createJob(commandLine, session, session.getResultDistributor()); + job.run(); + + //packing results + Map body = new TreeMap(); + body.put("jobId", job.id()); + body.put("jobStatus", job.status()); + body.put("sessionId", session.getSessionId()); + + ApiResponse response = new ApiResponse(); + response.setState(ApiState.SCHEDULED).setBody(body); + return response; + } + + /** + * Pull results from result queue + * + * @param apiRequest + * @param session + * @return + */ + private ApiResponse processPullResultsRequest(ApiRequest apiRequest, Session session) throws ApiException { + String consumerId = apiRequest.getConsumerId(); + if (StringUtils.isBlank(consumerId)) { + throw new ApiException("'consumerId' is required"); + } + ResultConsumer consumer = session.getResultDistributor().getConsumer(consumerId); + List results = consumer.pollResults(); + + Map body = new TreeMap(); + body.put("sessionId", session.getSessionId()); + body.put("consumerId", consumerId); + body.put("results", results); + + ApiResponse response = new ApiResponse(); + response.setState(ApiState.SUCCEEDED).setBody(body); + return response; + } + private boolean waitForJob(Job job) { long startTime = System.currentTimeMillis(); - while(true){ - switch (job.status()){ + while (true) { + switch (job.status()) { case STOPPED: case TERMINATED: - return true; + return true; } - if (System.currentTimeMillis() - startTime > 30000){ + if (System.currentTimeMillis() - startTime > 30000) { return false; } try { @@ -253,7 +345,7 @@ public class HttpApiHandler { return apiResponse; } - private String getBody(FullHttpRequest request){ + private String getBody(FullHttpRequest request) { ByteBuf buf = request.content(); return buf.toString(CharsetUtil.UTF_8); } -- Gitee From 40ee92fa001eae81334c1795b594fd0b9955a310 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Mon, 30 Mar 2020 19:08:21 +0800 Subject: [PATCH 011/135] Add WebUI files --- .../core/http/element-ui@2.13.0/lib/index.js | 1 + .../lib/theme-chalk/fonts/element-icons.woff | Bin 0 -> 28200 bytes .../lib/theme-chalk/index.css | 1 + .../com/taobao/arthas/core/http/ui/index.html | 134 + .../com/taobao/arthas/core/http/ui/ui.css | 100 + .../com/taobao/arthas/core/http/vue/vue.js | 11965 ++++++++++++++++ 6 files changed, 12201 insertions(+) create mode 100644 core/src/main/resources/com/taobao/arthas/core/http/element-ui@2.13.0/lib/index.js create mode 100644 core/src/main/resources/com/taobao/arthas/core/http/element-ui@2.13.0/lib/theme-chalk/fonts/element-icons.woff create mode 100644 core/src/main/resources/com/taobao/arthas/core/http/element-ui@2.13.0/lib/theme-chalk/index.css create mode 100644 core/src/main/resources/com/taobao/arthas/core/http/ui/index.html create mode 100644 core/src/main/resources/com/taobao/arthas/core/http/ui/ui.css create mode 100644 core/src/main/resources/com/taobao/arthas/core/http/vue/vue.js diff --git a/core/src/main/resources/com/taobao/arthas/core/http/element-ui@2.13.0/lib/index.js b/core/src/main/resources/com/taobao/arthas/core/http/element-ui@2.13.0/lib/index.js new file mode 100644 index 00000000..6c647970 --- /dev/null +++ b/core/src/main/resources/com/taobao/arthas/core/http/element-ui@2.13.0/lib/index.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("vue")):"function"==typeof define&&define.amd?define("ELEMENT",["vue"],t):"object"==typeof exports?exports.ELEMENT=t(require("vue")):e.ELEMENT=t(e.Vue)}("undefined"!=typeof self?self:this,function(e){return function(e){var t={};function i(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}return i.m=e,i.c=t,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/dist/",i(i.s=49)}([function(t,i){t.exports=e},function(e,t,i){var n=i(4);e.exports=function(e,t,i){return void 0===i?n(e,t,!1):n(e,i,!1!==t)}},function(e,t,i){var n;!function(r){"use strict";var s={},a=/d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g,o="[^\\s]+",l=/\[([^]*?)\]/gm,u=function(){};function c(e,t){for(var i=[],n=0,r=e.length;n3?0:(e-e%10!=10)*e%10]}};var g={D:function(e){return e.getDay()},DD:function(e){return d(e.getDay())},Do:function(e,t){return t.DoFn(e.getDate())},d:function(e){return e.getDate()},dd:function(e){return d(e.getDate())},ddd:function(e,t){return t.dayNamesShort[e.getDay()]},dddd:function(e,t){return t.dayNames[e.getDay()]},M:function(e){return e.getMonth()+1},MM:function(e){return d(e.getMonth()+1)},MMM:function(e,t){return t.monthNamesShort[e.getMonth()]},MMMM:function(e,t){return t.monthNames[e.getMonth()]},yy:function(e){return d(String(e.getFullYear()),4).substr(2)},yyyy:function(e){return d(e.getFullYear(),4)},h:function(e){return e.getHours()%12||12},hh:function(e){return d(e.getHours()%12||12)},H:function(e){return e.getHours()},HH:function(e){return d(e.getHours())},m:function(e){return e.getMinutes()},mm:function(e){return d(e.getMinutes())},s:function(e){return e.getSeconds()},ss:function(e){return d(e.getSeconds())},S:function(e){return Math.round(e.getMilliseconds()/100)},SS:function(e){return d(Math.round(e.getMilliseconds()/10),2)},SSS:function(e){return d(e.getMilliseconds(),3)},a:function(e,t){return e.getHours()<12?t.amPm[0]:t.amPm[1]},A:function(e,t){return e.getHours()<12?t.amPm[0].toUpperCase():t.amPm[1].toUpperCase()},ZZ:function(e){var t=e.getTimezoneOffset();return(t>0?"-":"+")+d(100*Math.floor(Math.abs(t)/60)+Math.abs(t)%60,4)}},b={d:["\\d\\d?",function(e,t){e.day=t}],Do:["\\d\\d?"+o,function(e,t){e.day=parseInt(t,10)}],M:["\\d\\d?",function(e,t){e.month=t-1}],yy:["\\d\\d?",function(e,t){var i=+(""+(new Date).getFullYear()).substr(0,2);e.year=""+(t>68?i-1:i)+t}],h:["\\d\\d?",function(e,t){e.hour=t}],m:["\\d\\d?",function(e,t){e.minute=t}],s:["\\d\\d?",function(e,t){e.second=t}],yyyy:["\\d{4}",function(e,t){e.year=t}],S:["\\d",function(e,t){e.millisecond=100*t}],SS:["\\d{2}",function(e,t){e.millisecond=10*t}],SSS:["\\d{3}",function(e,t){e.millisecond=t}],D:["\\d\\d?",u],ddd:[o,u],MMM:[o,h("monthNamesShort")],MMMM:[o,h("monthNames")],a:[o,function(e,t,i){var n=t.toLowerCase();n===i.amPm[0]?e.isPm=!1:n===i.amPm[1]&&(e.isPm=!0)}],ZZ:["[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d|[^\\s]*?Z",function(e,t){var i,n=(t+"").match(/([+-]|\d\d)/gi);n&&(i=60*n[1]+parseInt(n[2],10),e.timezoneOffset="+"===n[0]?i:-i)}]};b.dd=b.d,b.dddd=b.ddd,b.DD=b.D,b.mm=b.m,b.hh=b.H=b.HH=b.h,b.MM=b.M,b.ss=b.s,b.A=b.a,s.masks={default:"ddd MMM dd yyyy HH:mm:ss",shortDate:"M/D/yy",mediumDate:"MMM d, yyyy",longDate:"MMMM d, yyyy",fullDate:"dddd, MMMM d, yyyy",shortTime:"HH:mm",mediumTime:"HH:mm:ss",longTime:"HH:mm:ss.SSS"},s.format=function(e,t,i){var n=i||s.i18n;if("number"==typeof e&&(e=new Date(e)),"[object Date]"!==Object.prototype.toString.call(e)||isNaN(e.getTime()))throw new Error("Invalid Date in fecha.format");t=s.masks[t]||t||s.masks.default;var r=[];return(t=(t=t.replace(l,function(e,t){return r.push(t),"@@@"})).replace(a,function(t){return t in g?g[t](e,n):t.slice(1,t.length-1)})).replace(/@@@/g,function(){return r.shift()})},s.parse=function(e,t,i){var n=i||s.i18n;if("string"!=typeof t)throw new Error("Invalid format in fecha.parse");if(t=s.masks[t]||t,e.length>1e3)return null;var r={},o=[],u=[];t=t.replace(l,function(e,t){return u.push(t),"@@@"});var c,h=(c=t,c.replace(/[|\\{()[^$+*?.-]/g,"\\$&")).replace(a,function(e){if(b[e]){var t=b[e];return o.push(t[1]),"("+t[0]+")"}return e});h=h.replace(/@@@/g,function(){return u.shift()});var d=e.match(new RegExp(h,"i"));if(!d)return null;for(var p=1;pe?u():!0!==t&&(r=setTimeout(n?function(){r=void 0}:u,void 0===n?e-o:e))}}},function(e,t){var i=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=i)},function(e,t){var i=/^(attrs|props|on|nativeOn|class|style|hook)$/;function n(e,t){return function(){e&&e.apply(this,arguments),t&&t.apply(this,arguments)}}e.exports=function(e){return e.reduce(function(e,t){var r,s,a,o,l;for(a in t)if(r=e[a],s=t[a],r&&i.test(a))if("class"===a&&("string"==typeof r&&(l=r,e[a]=r={},r[l]=!0),"string"==typeof s&&(l=s,t[a]=s={},s[l]=!0)),"on"===a||"nativeOn"===a||"hook"===a)for(o in s)r[o]=n(r[o],s[o]);else if(Array.isArray(r))e[a]=r.concat(s);else if(Array.isArray(s))e[a]=[r].concat(s);else for(o in s)r[o]=s[o];else e[a]=t[a];return e},{})}},function(e,t){var i={}.hasOwnProperty;e.exports=function(e,t){return i.call(e,t)}},function(e,t,i){"use strict";t.__esModule=!0;var n,r=i(56),s=(n=r)&&n.__esModule?n:{default:n};t.default=s.default||function(e){for(var t=1;t0?n:i)(e)}},function(e,t,i){var n=i(28)("keys"),r=i(21);e.exports=function(e){return n[e]||(n[e]=r(e))}},function(e,t,i){var n=i(14),r=i(5),s=r["__core-js_shared__"]||(r["__core-js_shared__"]={});(e.exports=function(e,t){return s[e]||(s[e]=void 0!==t?t:{})})("versions",[]).push({version:n.version,mode:i(20)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t){e.exports={}},function(e,t,i){var n=i(10).f,r=i(7),s=i(13)("toStringTag");e.exports=function(e,t,i){e&&!r(e=i?e:e.prototype,s)&&n(e,s,{configurable:!0,value:t})}},function(e,t,i){t.f=i(13)},function(e,t,i){var n=i(5),r=i(14),s=i(20),a=i(33),o=i(10).f;e.exports=function(e){var t=r.Symbol||(r.Symbol=s?{}:n.Symbol||{});"_"==e.charAt(0)||e in t||o(t,e,{value:a.f(e)})}},function(e,t,i){var n=i(4),r=i(1);e.exports={throttle:n,debounce:r}},function(e,t,i){e.exports=!i(11)&&!i(16)(function(){return 7!=Object.defineProperty(i(37)("div"),"a",{get:function(){return 7}}).a})},function(e,t,i){var n=i(15),r=i(5).document,s=n(r)&&n(r.createElement);e.exports=function(e){return s?r.createElement(e):{}}},function(e,t,i){var n=i(7),r=i(12),s=i(62)(!1),a=i(27)("IE_PROTO");e.exports=function(e,t){var i,o=r(e),l=0,u=[];for(i in o)i!=a&&n(o,i)&&u.push(i);for(;t.length>l;)n(o,i=t[l++])&&(~s(u,i)||u.push(i));return u}},function(e,t,i){var n=i(40);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==n(e)?e.split(""):Object(e)}},function(e,t){var i={}.toString;e.exports=function(e){return i.call(e).slice(8,-1)}},function(e,t,i){var n=i(25);e.exports=function(e){return Object(n(e))}},function(e,t,i){"use strict";var n=i(20),r=i(23),s=i(43),a=i(9),o=i(31),l=i(69),u=i(32),c=i(72),h=i(13)("iterator"),d=!([].keys&&"next"in[].keys()),p=function(){return this};e.exports=function(e,t,i,f,m,v,g){l(i,t,f);var b,y,w,_=function(e){if(!d&&e in S)return S[e];switch(e){case"keys":case"values":return function(){return new i(this,e)}}return function(){return new i(this,e)}},x=t+" Iterator",C="values"==m,k=!1,S=e.prototype,D=S[h]||S["@@iterator"]||m&&S[m],$=D||_(m),E=m?C?_("entries"):$:void 0,T="Array"==t&&S.entries||D;if(T&&(w=c(T.call(new e)))!==Object.prototype&&w.next&&(u(w,x,!0),n||"function"==typeof w[h]||a(w,h,p)),C&&D&&"values"!==D.name&&(k=!0,$=function(){return D.call(this)}),n&&!g||!d&&!k&&S[h]||a(S,h,$),o[t]=$,o[x]=p,m)if(b={values:C?$:_("values"),keys:v?$:_("keys"),entries:E},g)for(y in b)y in S||s(S,y,b[y]);else r(r.P+r.F*(d||k),t,b);return b}},function(e,t,i){e.exports=i(9)},function(e,t,i){var n=i(17),r=i(70),s=i(29),a=i(27)("IE_PROTO"),o=function(){},l=function(){var e,t=i(37)("iframe"),n=s.length;for(t.style.display="none",i(71).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" + + + + +
+ + + + + + + + + New + Share + Close + Shutdown + + + + + + +
+  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
+ /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
+|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
+|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
+`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'
+
+ +wiki https://alibaba.github.io/arthas +tutorials https://alibaba.github.io/arthas/arthas-tutorials +version 3.1.8-SNAPSHOT +pid 1914 +time 2020-03-30 11:21:04 +
+
+ +
[arthas@1914]$ version
+3.1.8-SNAPSHOT
+
+
+ +
[arthas@1914]$ sm *Game
+demo.MathGame ()V
+demo.MathGame main([Ljava/lang/String;)V
+demo.MathGame run()V
+demo.MathGame print(ILjava/util/List;)V
+demo.MathGame primeFactors(I)Ljava/util/List;
+Affect(row-cnt:5) cost in 19 ms.
+
+
+ +
[arthas@1914]$ watch *Game primeFactors 'params' -n 3
+Press Q or Ctrl+C to abort.
+Affect(class-cnt:1 , method-cnt:1) cost in 60 ms.
+ts=2020-03-30 17:12:43; [cost=2.669414ms] result=@Object[][
+    @Integer[85275],
+]
+ts=2020-03-30 17:12:44; [cost=0.212597ms] result=@Object[][
+    @Integer[-209963],
+]
+ts=2020-03-30 17:12:45; [cost=0.175717ms] result=@Object[][
+    @Integer[212916],
+]
+Command execution times exceed limit: 3, so command will exit. You can set it with -n option.
+
+
+ +

更新 Github 模板

+

王小虎 提交于 2018/4/12 20:46

+
+ +

更新 Github 模板

+

王小虎 提交于 2018/4/12 20:46

+
+
+ +

更新 Github 模板

+

王小虎 提交于 2018/4/12 20:46

+
+
+ + + + + + + +
+
+ + + \ No newline at end of file diff --git a/core/src/main/resources/com/taobao/arthas/core/http/ui/ui.css b/core/src/main/resources/com/taobao/arthas/core/http/ui/ui.css new file mode 100644 index 00000000..f863d85b --- /dev/null +++ b/core/src/main/resources/com/taobao/arthas/core/http/ui/ui.css @@ -0,0 +1,100 @@ + +body { + margin: 0px; + overflow: hidden; +} +h4 { + margin: 0 5px 5px 5px; +} +p { + margin: 0px; +} + +.min-gap { + height: 6px; +} + +.logo { + vertical-align: middle; + display: inline-block; +} + +.user-info { + margin-left: 10px; + vertical-align: middle; + display: inline-block; +} +.user-icon { + font-size: 24px; +} + +a.nav-link:active, a.nav-link:hover, a.nav-link:visited { + color: rgba(0,0,0,.9); + text-decoration: none; +} + +.nav-link { + color: rgba(0,0,0,.5); + display: inline-block; + padding: .5rem 0.3rem; + text-decoration: none; +} + +.container { + display: flex; + flex-direction:column; + height: 100vh; +} +.header { + flex: 0 0 auto; + padding: 8px; + border-bottom: #cccccc 1px solid; +} +.header-right { + text-align: right; +} +.main-body { + /*height: 80vh;*/ + flex: 1 1 auto; + overflow-y: auto; +} +.footer-row { + flex: 0 0 auto; + display: flex; + padding-top: 10px; + vertical-align: middle; + border-top: #cccccc 1px solid; +} +.footer-left { + /*width: 100px;*/ + vertical-align: middle; + padding: 0.5em; +} +.footer-input { + flex: 1; + margin: 0 10px; +} +.footer-button { + /*width: 100px;*/ +} + +.main-body pre { + margin: 0; +} +/*****************************/ +/* override element-ui class*/ +/*****************************/ + +.el-card__body { + /*margin: 2px;*/ + padding: 8px; + /*background-color: #cccccc;*/ +} +.el-main { + padding: 10px; + /*background-color: #f7f8f9;*/ +} +.el-header { + padding: 10px; + background-color: #f7f8f9; +} diff --git a/core/src/main/resources/com/taobao/arthas/core/http/vue/vue.js b/core/src/main/resources/com/taobao/arthas/core/http/vue/vue.js new file mode 100644 index 00000000..e22cf130 --- /dev/null +++ b/core/src/main/resources/com/taobao/arthas/core/http/vue/vue.js @@ -0,0 +1,11965 @@ +/*! + * Vue.js v2.6.11 + * (c) 2014-2019 Evan You + * Released under the MIT License. + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Vue = factory()); +}(this, function () { 'use strict'; + + /* */ + + var emptyObject = Object.freeze({}); + + // These helpers produce better VM code in JS engines due to their + // explicitness and function inlining. + function isUndef (v) { + return v === undefined || v === null + } + + function isDef (v) { + return v !== undefined && v !== null + } + + function isTrue (v) { + return v === true + } + + function isFalse (v) { + return v === false + } + + /** + * Check if value is primitive. + */ + function isPrimitive (value) { + return ( + typeof value === 'string' || + typeof value === 'number' || + // $flow-disable-line + typeof value === 'symbol' || + typeof value === 'boolean' + ) + } + + /** + * Quick object check - this is primarily used to tell + * Objects from primitive values when we know the value + * is a JSON-compliant type. + */ + function isObject (obj) { + return obj !== null && typeof obj === 'object' + } + + /** + * Get the raw type string of a value, e.g., [object Object]. + */ + var _toString = Object.prototype.toString; + + function toRawType (value) { + return _toString.call(value).slice(8, -1) + } + + /** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ + function isPlainObject (obj) { + return _toString.call(obj) === '[object Object]' + } + + function isRegExp (v) { + return _toString.call(v) === '[object RegExp]' + } + + /** + * Check if val is a valid array index. + */ + function isValidArrayIndex (val) { + var n = parseFloat(String(val)); + return n >= 0 && Math.floor(n) === n && isFinite(val) + } + + function isPromise (val) { + return ( + isDef(val) && + typeof val.then === 'function' && + typeof val.catch === 'function' + ) + } + + /** + * Convert a value to a string that is actually rendered. + */ + function toString (val) { + return val == null + ? '' + : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) + ? JSON.stringify(val, null, 2) + : String(val) + } + + /** + * Convert an input value to a number for persistence. + * If the conversion fails, return original string. + */ + function toNumber (val) { + var n = parseFloat(val); + return isNaN(n) ? val : n + } + + /** + * Make a map and return a function for checking if a key + * is in that map. + */ + function makeMap ( + str, + expectsLowerCase + ) { + var map = Object.create(null); + var list = str.split(','); + for (var i = 0; i < list.length; i++) { + map[list[i]] = true; + } + return expectsLowerCase + ? function (val) { return map[val.toLowerCase()]; } + : function (val) { return map[val]; } + } + + /** + * Check if a tag is a built-in tag. + */ + var isBuiltInTag = makeMap('slot,component', true); + + /** + * Check if an attribute is a reserved attribute. + */ + var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); + + /** + * Remove an item from an array. + */ + function remove (arr, item) { + if (arr.length) { + var index = arr.indexOf(item); + if (index > -1) { + return arr.splice(index, 1) + } + } + } + + /** + * Check whether an object has the property. + */ + var hasOwnProperty = Object.prototype.hasOwnProperty; + function hasOwn (obj, key) { + return hasOwnProperty.call(obj, key) + } + + /** + * Create a cached version of a pure function. + */ + function cached (fn) { + var cache = Object.create(null); + return (function cachedFn (str) { + var hit = cache[str]; + return hit || (cache[str] = fn(str)) + }) + } + + /** + * Camelize a hyphen-delimited string. + */ + var camelizeRE = /-(\w)/g; + var camelize = cached(function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) + }); + + /** + * Capitalize a string. + */ + var capitalize = cached(function (str) { + return str.charAt(0).toUpperCase() + str.slice(1) + }); + + /** + * Hyphenate a camelCase string. + */ + var hyphenateRE = /\B([A-Z])/g; + var hyphenate = cached(function (str) { + return str.replace(hyphenateRE, '-$1').toLowerCase() + }); + + /** + * Simple bind polyfill for environments that do not support it, + * e.g., PhantomJS 1.x. Technically, we don't need this anymore + * since native bind is now performant enough in most browsers. + * But removing it would mean breaking code that was able to run in + * PhantomJS 1.x, so this must be kept for backward compatibility. + */ + + /* istanbul ignore next */ + function polyfillBind (fn, ctx) { + function boundFn (a) { + var l = arguments.length; + return l + ? l > 1 + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) + : fn.call(ctx) + } + + boundFn._length = fn.length; + return boundFn + } + + function nativeBind (fn, ctx) { + return fn.bind(ctx) + } + + var bind = Function.prototype.bind + ? nativeBind + : polyfillBind; + + /** + * Convert an Array-like object to a real Array. + */ + function toArray (list, start) { + start = start || 0; + var i = list.length - start; + var ret = new Array(i); + while (i--) { + ret[i] = list[i + start]; + } + return ret + } + + /** + * Mix properties into target object. + */ + function extend (to, _from) { + for (var key in _from) { + to[key] = _from[key]; + } + return to + } + + /** + * Merge an Array of Objects into a single Object. + */ + function toObject (arr) { + var res = {}; + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); + } + } + return res + } + + /* eslint-disable no-unused-vars */ + + /** + * Perform no operation. + * Stubbing args to make Flow happy without leaving useless transpiled code + * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/). + */ + function noop (a, b, c) {} + + /** + * Always return false. + */ + var no = function (a, b, c) { return false; }; + + /* eslint-enable no-unused-vars */ + + /** + * Return the same value. + */ + var identity = function (_) { return _; }; + + /** + * Generate a string containing static keys from compiler modules. + */ + function genStaticKeys (modules) { + return modules.reduce(function (keys, m) { + return keys.concat(m.staticKeys || []) + }, []).join(',') + } + + /** + * Check if two values are loosely equal - that is, + * if they are plain objects, do they have the same shape? + */ + function looseEqual (a, b) { + if (a === b) { return true } + var isObjectA = isObject(a); + var isObjectB = isObject(b); + if (isObjectA && isObjectB) { + try { + var isArrayA = Array.isArray(a); + var isArrayB = Array.isArray(b); + if (isArrayA && isArrayB) { + return a.length === b.length && a.every(function (e, i) { + return looseEqual(e, b[i]) + }) + } else if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime() + } else if (!isArrayA && !isArrayB) { + var keysA = Object.keys(a); + var keysB = Object.keys(b); + return keysA.length === keysB.length && keysA.every(function (key) { + return looseEqual(a[key], b[key]) + }) + } else { + /* istanbul ignore next */ + return false + } + } catch (e) { + /* istanbul ignore next */ + return false + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b) + } else { + return false + } + } + + /** + * Return the first index at which a loosely equal value can be + * found in the array (if value is a plain object, the array must + * contain an object of the same shape), or -1 if it is not present. + */ + function looseIndexOf (arr, val) { + for (var i = 0; i < arr.length; i++) { + if (looseEqual(arr[i], val)) { return i } + } + return -1 + } + + /** + * Ensure a function is called only once. + */ + function once (fn) { + var called = false; + return function () { + if (!called) { + called = true; + fn.apply(this, arguments); + } + } + } + + var SSR_ATTR = 'data-server-rendered'; + + var ASSET_TYPES = [ + 'component', + 'directive', + 'filter' + ]; + + var LIFECYCLE_HOOKS = [ + 'beforeCreate', + 'created', + 'beforeMount', + 'mounted', + 'beforeUpdate', + 'updated', + 'beforeDestroy', + 'destroyed', + 'activated', + 'deactivated', + 'errorCaptured', + 'serverPrefetch' + ]; + + /* */ + + + + var config = ({ + /** + * Option merge strategies (used in core/util/options) + */ + // $flow-disable-line + optionMergeStrategies: Object.create(null), + + /** + * Whether to suppress warnings. + */ + silent: false, + + /** + * Show production mode tip message on boot? + */ + productionTip: "development" !== 'production', + + /** + * Whether to enable devtools + */ + devtools: "development" !== 'production', + + /** + * Whether to record perf + */ + performance: false, + + /** + * Error handler for watcher errors + */ + errorHandler: null, + + /** + * Warn handler for watcher warns + */ + warnHandler: null, + + /** + * Ignore certain custom elements + */ + ignoredElements: [], + + /** + * Custom user key aliases for v-on + */ + // $flow-disable-line + keyCodes: Object.create(null), + + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + + /** + * Check if an attribute is reserved so that it cannot be used as a component + * prop. This is platform-dependent and may be overwritten. + */ + isReservedAttr: no, + + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + + /** + * Parse the real tag name for the specific platform. + */ + parsePlatformTagName: identity, + + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + + /** + * Perform updates asynchronously. Intended to be used by Vue Test Utils + * This will significantly reduce performance if set to false. + */ + async: true, + + /** + * Exposed for legacy reasons + */ + _lifecycleHooks: LIFECYCLE_HOOKS + }); + + /* */ + + /** + * unicode letters used for parsing html tags, component names and property paths. + * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname + * skipping \u10000-\uEFFFF due to it freezing up PhantomJS + */ + var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; + + /** + * Check if a string starts with $ or _ + */ + function isReserved (str) { + var c = (str + '').charCodeAt(0); + return c === 0x24 || c === 0x5F + } + + /** + * Define a property. + */ + function def (obj, key, val, enumerable) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }); + } + + /** + * Parse simple path. + */ + var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]")); + function parsePath (path) { + if (bailRE.test(path)) { + return + } + var segments = path.split('.'); + return function (obj) { + for (var i = 0; i < segments.length; i++) { + if (!obj) { return } + obj = obj[segments[i]]; + } + return obj + } + } + + /* */ + + // can we use __proto__? + var hasProto = '__proto__' in {}; + + // Browser environment sniffing + var inBrowser = typeof window !== 'undefined'; + var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; + var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); + var UA = inBrowser && window.navigator.userAgent.toLowerCase(); + var isIE = UA && /msie|trident/.test(UA); + var isIE9 = UA && UA.indexOf('msie 9.0') > 0; + var isEdge = UA && UA.indexOf('edge/') > 0; + var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); + var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); + var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; + var isPhantomJS = UA && /phantomjs/.test(UA); + var isFF = UA && UA.match(/firefox\/(\d+)/); + + // Firefox has a "watch" function on Object.prototype... + var nativeWatch = ({}).watch; + + var supportsPassive = false; + if (inBrowser) { + try { + var opts = {}; + Object.defineProperty(opts, 'passive', ({ + get: function get () { + /* istanbul ignore next */ + supportsPassive = true; + } + })); // https://github.com/facebook/flow/issues/285 + window.addEventListener('test-passive', null, opts); + } catch (e) {} + } + + // this needs to be lazy-evaled because vue may be required before + // vue-server-renderer can set VUE_ENV + var _isServer; + var isServerRendering = function () { + if (_isServer === undefined) { + /* istanbul ignore if */ + if (!inBrowser && !inWeex && typeof global !== 'undefined') { + // detect presence of vue-server-renderer and avoid + // Webpack shimming the process + _isServer = global['process'] && global['process'].env.VUE_ENV === 'server'; + } else { + _isServer = false; + } + } + return _isServer + }; + + // detect devtools + var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + + /* istanbul ignore next */ + function isNative (Ctor) { + return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) + } + + var hasSymbol = + typeof Symbol !== 'undefined' && isNative(Symbol) && + typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); + + var _Set; + /* istanbul ignore if */ // $flow-disable-line + if (typeof Set !== 'undefined' && isNative(Set)) { + // use native Set when available. + _Set = Set; + } else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = /*@__PURE__*/(function () { + function Set () { + this.set = Object.create(null); + } + Set.prototype.has = function has (key) { + return this.set[key] === true + }; + Set.prototype.add = function add (key) { + this.set[key] = true; + }; + Set.prototype.clear = function clear () { + this.set = Object.create(null); + }; + + return Set; + }()); + } + + /* */ + + var warn = noop; + var tip = noop; + var generateComponentTrace = (noop); // work around flow check + var formatComponentName = (noop); + + { + var hasConsole = typeof console !== 'undefined'; + var classifyRE = /(?:^|[-_])(\w)/g; + var classify = function (str) { return str + .replace(classifyRE, function (c) { return c.toUpperCase(); }) + .replace(/[-_]/g, ''); }; + + warn = function (msg, vm) { + var trace = vm ? generateComponentTrace(vm) : ''; + + if (config.warnHandler) { + config.warnHandler.call(null, msg, vm, trace); + } else if (hasConsole && (!config.silent)) { + console.error(("[Vue warn]: " + msg + trace)); + } + }; + + tip = function (msg, vm) { + if (hasConsole && (!config.silent)) { + console.warn("[Vue tip]: " + msg + ( + vm ? generateComponentTrace(vm) : '' + )); + } + }; + + formatComponentName = function (vm, includeFile) { + if (vm.$root === vm) { + return '' + } + var options = typeof vm === 'function' && vm.cid != null + ? vm.options + : vm._isVue + ? vm.$options || vm.constructor.options + : vm; + var name = options.name || options._componentTag; + var file = options.__file; + if (!name && file) { + var match = file.match(/([^/\\]+)\.vue$/); + name = match && match[1]; + } + + return ( + (name ? ("<" + (classify(name)) + ">") : "") + + (file && includeFile !== false ? (" at " + file) : '') + ) + }; + + var repeat = function (str, n) { + var res = ''; + while (n) { + if (n % 2 === 1) { res += str; } + if (n > 1) { str += str; } + n >>= 1; + } + return res + }; + + generateComponentTrace = function (vm) { + if (vm._isVue && vm.$parent) { + var tree = []; + var currentRecursiveSequence = 0; + while (vm) { + if (tree.length > 0) { + var last = tree[tree.length - 1]; + if (last.constructor === vm.constructor) { + currentRecursiveSequence++; + vm = vm.$parent; + continue + } else if (currentRecursiveSequence > 0) { + tree[tree.length - 1] = [last, currentRecursiveSequence]; + currentRecursiveSequence = 0; + } + } + tree.push(vm); + vm = vm.$parent; + } + return '\n\nfound in\n\n' + tree + .map(function (vm, i) { return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm) + ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") + : formatComponentName(vm))); }) + .join('\n') + } else { + return ("\n\n(found in " + (formatComponentName(vm)) + ")") + } + }; + } + + /* */ + + var uid = 0; + + /** + * A dep is an observable that can have multiple + * directives subscribing to it. + */ + var Dep = function Dep () { + this.id = uid++; + this.subs = []; + }; + + Dep.prototype.addSub = function addSub (sub) { + this.subs.push(sub); + }; + + Dep.prototype.removeSub = function removeSub (sub) { + remove(this.subs, sub); + }; + + Dep.prototype.depend = function depend () { + if (Dep.target) { + Dep.target.addDep(this); + } + }; + + Dep.prototype.notify = function notify () { + // stabilize the subscriber list first + var subs = this.subs.slice(); + if (!config.async) { + // subs aren't sorted in scheduler if not running async + // we need to sort them now to make sure they fire in correct + // order + subs.sort(function (a, b) { return a.id - b.id; }); + } + for (var i = 0, l = subs.length; i < l; i++) { + subs[i].update(); + } + }; + + // The current target watcher being evaluated. + // This is globally unique because only one watcher + // can be evaluated at a time. + Dep.target = null; + var targetStack = []; + + function pushTarget (target) { + targetStack.push(target); + Dep.target = target; + } + + function popTarget () { + targetStack.pop(); + Dep.target = targetStack[targetStack.length - 1]; + } + + /* */ + + var VNode = function VNode ( + tag, + data, + children, + text, + elm, + context, + componentOptions, + asyncFactory + ) { + this.tag = tag; + this.data = data; + this.children = children; + this.text = text; + this.elm = elm; + this.ns = undefined; + this.context = context; + this.fnContext = undefined; + this.fnOptions = undefined; + this.fnScopeId = undefined; + this.key = data && data.key; + this.componentOptions = componentOptions; + this.componentInstance = undefined; + this.parent = undefined; + this.raw = false; + this.isStatic = false; + this.isRootInsert = true; + this.isComment = false; + this.isCloned = false; + this.isOnce = false; + this.asyncFactory = asyncFactory; + this.asyncMeta = undefined; + this.isAsyncPlaceholder = false; + }; + + var prototypeAccessors = { child: { configurable: true } }; + + // DEPRECATED: alias for componentInstance for backwards compat. + /* istanbul ignore next */ + prototypeAccessors.child.get = function () { + return this.componentInstance + }; + + Object.defineProperties( VNode.prototype, prototypeAccessors ); + + var createEmptyVNode = function (text) { + if ( text === void 0 ) text = ''; + + var node = new VNode(); + node.text = text; + node.isComment = true; + return node + }; + + function createTextVNode (val) { + return new VNode(undefined, undefined, undefined, String(val)) + } + + // optimized shallow clone + // used for static nodes and slot nodes because they may be reused across + // multiple renders, cloning them avoids errors when DOM manipulations rely + // on their elm reference. + function cloneVNode (vnode) { + var cloned = new VNode( + vnode.tag, + vnode.data, + // #7975 + // clone children array to avoid mutating original in case of cloning + // a child. + vnode.children && vnode.children.slice(), + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions, + vnode.asyncFactory + ); + cloned.ns = vnode.ns; + cloned.isStatic = vnode.isStatic; + cloned.key = vnode.key; + cloned.isComment = vnode.isComment; + cloned.fnContext = vnode.fnContext; + cloned.fnOptions = vnode.fnOptions; + cloned.fnScopeId = vnode.fnScopeId; + cloned.asyncMeta = vnode.asyncMeta; + cloned.isCloned = true; + return cloned + } + + /* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + + var arrayProto = Array.prototype; + var arrayMethods = Object.create(arrayProto); + + var methodsToPatch = [ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' + ]; + + /** + * Intercept mutating methods and emit events + */ + methodsToPatch.forEach(function (method) { + // cache original method + var original = arrayProto[method]; + def(arrayMethods, method, function mutator () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var result = original.apply(this, args); + var ob = this.__ob__; + var inserted; + switch (method) { + case 'push': + case 'unshift': + inserted = args; + break + case 'splice': + inserted = args.slice(2); + break + } + if (inserted) { ob.observeArray(inserted); } + // notify change + ob.dep.notify(); + return result + }); + }); + + /* */ + + var arrayKeys = Object.getOwnPropertyNames(arrayMethods); + + /** + * In some cases we may want to disable observation inside a component's + * update computation. + */ + var shouldObserve = true; + + function toggleObserving (value) { + shouldObserve = value; + } + + /** + * Observer class that is attached to each observed + * object. Once attached, the observer converts the target + * object's property keys into getter/setters that + * collect dependencies and dispatch updates. + */ + var Observer = function Observer (value) { + this.value = value; + this.dep = new Dep(); + this.vmCount = 0; + def(value, '__ob__', this); + if (Array.isArray(value)) { + if (hasProto) { + protoAugment(value, arrayMethods); + } else { + copyAugment(value, arrayMethods, arrayKeys); + } + this.observeArray(value); + } else { + this.walk(value); + } + }; + + /** + * Walk through all properties and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ + Observer.prototype.walk = function walk (obj) { + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + defineReactive$$1(obj, keys[i]); + } + }; + + /** + * Observe a list of Array items. + */ + Observer.prototype.observeArray = function observeArray (items) { + for (var i = 0, l = items.length; i < l; i++) { + observe(items[i]); + } + }; + + // helpers + + /** + * Augment a target Object or Array by intercepting + * the prototype chain using __proto__ + */ + function protoAugment (target, src) { + /* eslint-disable no-proto */ + target.__proto__ = src; + /* eslint-enable no-proto */ + } + + /** + * Augment a target Object or Array by defining + * hidden properties. + */ + /* istanbul ignore next */ + function copyAugment (target, src, keys) { + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + def(target, key, src[key]); + } + } + + /** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ + function observe (value, asRootData) { + if (!isObject(value) || value instanceof VNode) { + return + } + var ob; + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__; + } else if ( + shouldObserve && + !isServerRendering() && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value); + } + if (asRootData && ob) { + ob.vmCount++; + } + return ob + } + + /** + * Define a reactive property on an Object. + */ + function defineReactive$$1 ( + obj, + key, + val, + customSetter, + shallow + ) { + var dep = new Dep(); + + var property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + return + } + + // cater for pre-defined getter/setters + var getter = property && property.get; + var setter = property && property.set; + if ((!getter || setter) && arguments.length === 2) { + val = obj[key]; + } + + var childOb = !shallow && observe(val); + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + var value = getter ? getter.call(obj) : val; + if (Dep.target) { + dep.depend(); + if (childOb) { + childOb.dep.depend(); + if (Array.isArray(value)) { + dependArray(value); + } + } + } + return value + }, + set: function reactiveSetter (newVal) { + var value = getter ? getter.call(obj) : val; + /* eslint-disable no-self-compare */ + if (newVal === value || (newVal !== newVal && value !== value)) { + return + } + /* eslint-enable no-self-compare */ + if (customSetter) { + customSetter(); + } + // #7981: for accessor properties without setter + if (getter && !setter) { return } + if (setter) { + setter.call(obj, newVal); + } else { + val = newVal; + } + childOb = !shallow && observe(newVal); + dep.notify(); + } + }); + } + + /** + * Set a property on an object. Adds the new property and + * triggers change notification if the property doesn't + * already exist. + */ + function set (target, key, val) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.length = Math.max(target.length, key); + target.splice(key, 1, val); + return val + } + if (key in target && !(key in Object.prototype)) { + target[key] = val; + return val + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid adding reactive properties to a Vue instance or its root $data ' + + 'at runtime - declare it upfront in the data option.' + ); + return val + } + if (!ob) { + target[key] = val; + return val + } + defineReactive$$1(ob.value, key, val); + ob.dep.notify(); + return val + } + + /** + * Delete a property and trigger change if necessary. + */ + function del (target, key) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.splice(key, 1); + return + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid deleting properties on a Vue instance or its root $data ' + + '- just set it to null.' + ); + return + } + if (!hasOwn(target, key)) { + return + } + delete target[key]; + if (!ob) { + return + } + ob.dep.notify(); + } + + /** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ + function dependArray (value) { + for (var e = (void 0), i = 0, l = value.length; i < l; i++) { + e = value[i]; + e && e.__ob__ && e.__ob__.dep.depend(); + if (Array.isArray(e)) { + dependArray(e); + } + } + } + + /* */ + + /** + * Option overwriting strategies are functions that handle + * how to merge a parent option value and a child option + * value into the final value. + */ + var strats = config.optionMergeStrategies; + + /** + * Options with restrictions + */ + { + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + warn( + "option \"" + key + "\" can only be used during instance " + + 'creation with the `new` keyword.' + ); + } + return defaultStrat(parent, child) + }; + } + + /** + * Helper that recursively merges two data objects together. + */ + function mergeData (to, from) { + if (!from) { return to } + var key, toVal, fromVal; + + var keys = hasSymbol + ? Reflect.ownKeys(from) + : Object.keys(from); + + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + // in case the object is already observed... + if (key === '__ob__') { continue } + toVal = to[key]; + fromVal = from[key]; + if (!hasOwn(to, key)) { + set(to, key, fromVal); + } else if ( + toVal !== fromVal && + isPlainObject(toVal) && + isPlainObject(fromVal) + ) { + mergeData(toVal, fromVal); + } + } + return to + } + + /** + * Data + */ + function mergeDataOrFn ( + parentVal, + childVal, + vm + ) { + if (!vm) { + // in a Vue.extend merge, both should be functions + if (!childVal) { + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + return function mergedDataFn () { + return mergeData( + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal + ) + } + } else { + return function mergedInstanceDataFn () { + // instance merge + var instanceData = typeof childVal === 'function' + ? childVal.call(vm, vm) + : childVal; + var defaultData = typeof parentVal === 'function' + ? parentVal.call(vm, vm) + : parentVal; + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } + } + + strats.data = function ( + parentVal, + childVal, + vm + ) { + if (!vm) { + if (childVal && typeof childVal !== 'function') { + warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ); + + return parentVal + } + return mergeDataOrFn(parentVal, childVal) + } + + return mergeDataOrFn(parentVal, childVal, vm) + }; + + /** + * Hooks and props are merged as arrays. + */ + function mergeHook ( + parentVal, + childVal + ) { + var res = childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal; + return res + ? dedupeHooks(res) + : res + } + + function dedupeHooks (hooks) { + var res = []; + for (var i = 0; i < hooks.length; i++) { + if (res.indexOf(hooks[i]) === -1) { + res.push(hooks[i]); + } + } + return res + } + + LIFECYCLE_HOOKS.forEach(function (hook) { + strats[hook] = mergeHook; + }); + + /** + * Assets + * + * When a vm is present (instance creation), we need to do + * a three-way merge between constructor options, instance + * options and parent options. + */ + function mergeAssets ( + parentVal, + childVal, + vm, + key + ) { + var res = Object.create(parentVal || null); + if (childVal) { + assertObjectType(key, childVal, vm); + return extend(res, childVal) + } else { + return res + } + } + + ASSET_TYPES.forEach(function (type) { + strats[type + 's'] = mergeAssets; + }); + + /** + * Watchers. + * + * Watchers hashes should not overwrite one + * another, so we merge them as arrays. + */ + strats.watch = function ( + parentVal, + childVal, + vm, + key + ) { + // work around Firefox's Object.prototype.watch... + if (parentVal === nativeWatch) { parentVal = undefined; } + if (childVal === nativeWatch) { childVal = undefined; } + /* istanbul ignore if */ + if (!childVal) { return Object.create(parentVal || null) } + { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = {}; + extend(ret, parentVal); + for (var key$1 in childVal) { + var parent = ret[key$1]; + var child = childVal[key$1]; + if (parent && !Array.isArray(parent)) { + parent = [parent]; + } + ret[key$1] = parent + ? parent.concat(child) + : Array.isArray(child) ? child : [child]; + } + return ret + }; + + /** + * Other object hashes. + */ + strats.props = + strats.methods = + strats.inject = + strats.computed = function ( + parentVal, + childVal, + vm, + key + ) { + if (childVal && "development" !== 'production') { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = Object.create(null); + extend(ret, parentVal); + if (childVal) { extend(ret, childVal); } + return ret + }; + strats.provide = mergeDataOrFn; + + /** + * Default strategy. + */ + var defaultStrat = function (parentVal, childVal) { + return childVal === undefined + ? parentVal + : childVal + }; + + /** + * Validate component names + */ + function checkComponents (options) { + for (var key in options.components) { + validateComponentName(key); + } + } + + function validateComponentName (name) { + if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'should conform to valid custom element name in html5 specification.' + ); + } + if (isBuiltInTag(name) || config.isReservedTag(name)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + name + ); + } + } + + /** + * Ensure all props option syntax are normalized into the + * Object-based format. + */ + function normalizeProps (options, vm) { + var props = options.props; + if (!props) { return } + var res = {}; + var i, val, name; + if (Array.isArray(props)) { + i = props.length; + while (i--) { + val = props[i]; + if (typeof val === 'string') { + name = camelize(val); + res[name] = { type: null }; + } else { + warn('props must be strings when using array syntax.'); + } + } + } else if (isPlainObject(props)) { + for (var key in props) { + val = props[key]; + name = camelize(key); + res[name] = isPlainObject(val) + ? val + : { type: val }; + } + } else { + warn( + "Invalid value for option \"props\": expected an Array or an Object, " + + "but got " + (toRawType(props)) + ".", + vm + ); + } + options.props = res; + } + + /** + * Normalize all injections into Object-based format + */ + function normalizeInject (options, vm) { + var inject = options.inject; + if (!inject) { return } + var normalized = options.inject = {}; + if (Array.isArray(inject)) { + for (var i = 0; i < inject.length; i++) { + normalized[inject[i]] = { from: inject[i] }; + } + } else if (isPlainObject(inject)) { + for (var key in inject) { + var val = inject[key]; + normalized[key] = isPlainObject(val) + ? extend({ from: key }, val) + : { from: val }; + } + } else { + warn( + "Invalid value for option \"inject\": expected an Array or an Object, " + + "but got " + (toRawType(inject)) + ".", + vm + ); + } + } + + /** + * Normalize raw function directives into object format. + */ + function normalizeDirectives (options) { + var dirs = options.directives; + if (dirs) { + for (var key in dirs) { + var def$$1 = dirs[key]; + if (typeof def$$1 === 'function') { + dirs[key] = { bind: def$$1, update: def$$1 }; + } + } + } + } + + function assertObjectType (name, value, vm) { + if (!isPlainObject(value)) { + warn( + "Invalid value for option \"" + name + "\": expected an Object, " + + "but got " + (toRawType(value)) + ".", + vm + ); + } + } + + /** + * Merge two option objects into a new one. + * Core utility used in both instantiation and inheritance. + */ + function mergeOptions ( + parent, + child, + vm + ) { + { + checkComponents(child); + } + + if (typeof child === 'function') { + child = child.options; + } + + normalizeProps(child, vm); + normalizeInject(child, vm); + normalizeDirectives(child); + + // Apply extends and mixins on the child options, + // but only if it is a raw options object that isn't + // the result of another mergeOptions call. + // Only merged options has the _base property. + if (!child._base) { + if (child.extends) { + parent = mergeOptions(parent, child.extends, vm); + } + if (child.mixins) { + for (var i = 0, l = child.mixins.length; i < l; i++) { + parent = mergeOptions(parent, child.mixins[i], vm); + } + } + } + + var options = {}; + var key; + for (key in parent) { + mergeField(key); + } + for (key in child) { + if (!hasOwn(parent, key)) { + mergeField(key); + } + } + function mergeField (key) { + var strat = strats[key] || defaultStrat; + options[key] = strat(parent[key], child[key], vm, key); + } + return options + } + + /** + * Resolve an asset. + * This function is used because child instances need access + * to assets defined in its ancestor chain. + */ + function resolveAsset ( + options, + type, + id, + warnMissing + ) { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return + } + var assets = options[type]; + // check local registration variations first + if (hasOwn(assets, id)) { return assets[id] } + var camelizedId = camelize(id); + if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } + var PascalCaseId = capitalize(camelizedId); + if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } + // fallback to prototype chain + var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; + if (warnMissing && !res) { + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ); + } + return res + } + + /* */ + + + + function validateProp ( + key, + propOptions, + propsData, + vm + ) { + var prop = propOptions[key]; + var absent = !hasOwn(propsData, key); + var value = propsData[key]; + // boolean casting + var booleanIndex = getTypeIndex(Boolean, prop.type); + if (booleanIndex > -1) { + if (absent && !hasOwn(prop, 'default')) { + value = false; + } else if (value === '' || value === hyphenate(key)) { + // only cast empty string / same name to boolean if + // boolean has higher priority + var stringIndex = getTypeIndex(String, prop.type); + if (stringIndex < 0 || booleanIndex < stringIndex) { + value = true; + } + } + } + // check default value + if (value === undefined) { + value = getPropDefaultValue(vm, prop, key); + // since the default value is a fresh copy, + // make sure to observe it. + var prevShouldObserve = shouldObserve; + toggleObserving(true); + observe(value); + toggleObserving(prevShouldObserve); + } + { + assertProp(prop, key, value, vm, absent); + } + return value + } + + /** + * Get the default value of a prop. + */ + function getPropDefaultValue (vm, prop, key) { + // no default, return undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + var def = prop.default; + // warn against non-factory defaults for Object & Array + if (isObject(def)) { + warn( + 'Invalid default value for prop "' + key + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ); + } + // the raw prop value was also undefined from previous render, + // return previous default value to avoid unnecessary watcher trigger + if (vm && vm.$options.propsData && + vm.$options.propsData[key] === undefined && + vm._props[key] !== undefined + ) { + return vm._props[key] + } + // call factory function for non-Function types + // a value is Function if its prototype is function even across different execution context + return typeof def === 'function' && getType(prop.type) !== 'Function' + ? def.call(vm) + : def + } + + /** + * Assert whether a prop is valid. + */ + function assertProp ( + prop, + name, + value, + vm, + absent + ) { + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ); + return + } + if (value == null && !prop.required) { + return + } + var type = prop.type; + var valid = !type || type === true; + var expectedTypes = []; + if (type) { + if (!Array.isArray(type)) { + type = [type]; + } + for (var i = 0; i < type.length && !valid; i++) { + var assertedType = assertType(value, type[i]); + expectedTypes.push(assertedType.expectedType || ''); + valid = assertedType.valid; + } + } + + if (!valid) { + warn( + getInvalidTypeMessage(name, value, expectedTypes), + vm + ); + return + } + var validator = prop.validator; + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ); + } + } + } + + var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; + + function assertType (value, type) { + var valid; + var expectedType = getType(type); + if (simpleCheckRE.test(expectedType)) { + var t = typeof value; + valid = t === expectedType.toLowerCase(); + // for primitive wrapper objects + if (!valid && t === 'object') { + valid = value instanceof type; + } + } else if (expectedType === 'Object') { + valid = isPlainObject(value); + } else if (expectedType === 'Array') { + valid = Array.isArray(value); + } else { + valid = value instanceof type; + } + return { + valid: valid, + expectedType: expectedType + } + } + + /** + * Use function string name to check built-in types, + * because a simple equality check will fail when running + * across different vms / iframes. + */ + function getType (fn) { + var match = fn && fn.toString().match(/^\s*function (\w+)/); + return match ? match[1] : '' + } + + function isSameType (a, b) { + return getType(a) === getType(b) + } + + function getTypeIndex (type, expectedTypes) { + if (!Array.isArray(expectedTypes)) { + return isSameType(expectedTypes, type) ? 0 : -1 + } + for (var i = 0, len = expectedTypes.length; i < len; i++) { + if (isSameType(expectedTypes[i], type)) { + return i + } + } + return -1 + } + + function getInvalidTypeMessage (name, value, expectedTypes) { + var message = "Invalid prop: type check failed for prop \"" + name + "\"." + + " Expected " + (expectedTypes.map(capitalize).join(', ')); + var expectedType = expectedTypes[0]; + var receivedType = toRawType(value); + var expectedValue = styleValue(value, expectedType); + var receivedValue = styleValue(value, receivedType); + // check if we need to specify expected value + if (expectedTypes.length === 1 && + isExplicable(expectedType) && + !isBoolean(expectedType, receivedType)) { + message += " with value " + expectedValue; + } + message += ", got " + receivedType + " "; + // check if we need to specify received value + if (isExplicable(receivedType)) { + message += "with value " + receivedValue + "."; + } + return message + } + + function styleValue (value, type) { + if (type === 'String') { + return ("\"" + value + "\"") + } else if (type === 'Number') { + return ("" + (Number(value))) + } else { + return ("" + value) + } + } + + function isExplicable (value) { + var explicitTypes = ['string', 'number', 'boolean']; + return explicitTypes.some(function (elem) { return value.toLowerCase() === elem; }) + } + + function isBoolean () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return args.some(function (elem) { return elem.toLowerCase() === 'boolean'; }) + } + + /* */ + + function handleError (err, vm, info) { + // Deactivate deps tracking while processing error handler to avoid possible infinite rendering. + // See: https://github.com/vuejs/vuex/issues/1505 + pushTarget(); + try { + if (vm) { + var cur = vm; + while ((cur = cur.$parent)) { + var hooks = cur.$options.errorCaptured; + if (hooks) { + for (var i = 0; i < hooks.length; i++) { + try { + var capture = hooks[i].call(cur, err, vm, info) === false; + if (capture) { return } + } catch (e) { + globalHandleError(e, cur, 'errorCaptured hook'); + } + } + } + } + } + globalHandleError(err, vm, info); + } finally { + popTarget(); + } + } + + function invokeWithErrorHandling ( + handler, + context, + args, + vm, + info + ) { + var res; + try { + res = args ? handler.apply(context, args) : handler.call(context); + if (res && !res._isVue && isPromise(res) && !res._handled) { + res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); }); + // issue #9511 + // avoid catch triggering multiple times when nested calls + res._handled = true; + } + } catch (e) { + handleError(e, vm, info); + } + return res + } + + function globalHandleError (err, vm, info) { + if (config.errorHandler) { + try { + return config.errorHandler.call(null, err, vm, info) + } catch (e) { + // if the user intentionally throws the original error in the handler, + // do not log it twice + if (e !== err) { + logError(e, null, 'config.errorHandler'); + } + } + } + logError(err, vm, info); + } + + function logError (err, vm, info) { + { + warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); + } + /* istanbul ignore else */ + if ((inBrowser || inWeex) && typeof console !== 'undefined') { + console.error(err); + } else { + throw err + } + } + + /* */ + + var isUsingMicroTask = false; + + var callbacks = []; + var pending = false; + + function flushCallbacks () { + pending = false; + var copies = callbacks.slice(0); + callbacks.length = 0; + for (var i = 0; i < copies.length; i++) { + copies[i](); + } + } + + // Here we have async deferring wrappers using microtasks. + // In 2.5 we used (macro) tasks (in combination with microtasks). + // However, it has subtle problems when state is changed right before repaint + // (e.g. #6813, out-in transitions). + // Also, using (macro) tasks in event handler would cause some weird behaviors + // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). + // So we now use microtasks everywhere, again. + // A major drawback of this tradeoff is that there are some scenarios + // where microtasks have too high a priority and fire in between supposedly + // sequential events (e.g. #4521, #6690, which have workarounds) + // or even between bubbling of the same event (#6566). + var timerFunc; + + // The nextTick behavior leverages the microtask queue, which can be accessed + // via either native Promise.then or MutationObserver. + // MutationObserver has wider support, however it is seriously bugged in + // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It + // completely stops working after triggering a few times... so, if native + // Promise is available, we will use it: + /* istanbul ignore next, $flow-disable-line */ + if (typeof Promise !== 'undefined' && isNative(Promise)) { + var p = Promise.resolve(); + timerFunc = function () { + p.then(flushCallbacks); + // In problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) { setTimeout(noop); } + }; + isUsingMicroTask = true; + } else if (!isIE && typeof MutationObserver !== 'undefined' && ( + isNative(MutationObserver) || + // PhantomJS and iOS 7.x + MutationObserver.toString() === '[object MutationObserverConstructor]' + )) { + // Use MutationObserver where native Promise is not available, + // e.g. PhantomJS, iOS7, Android 4.4 + // (#6466 MutationObserver is unreliable in IE11) + var counter = 1; + var observer = new MutationObserver(flushCallbacks); + var textNode = document.createTextNode(String(counter)); + observer.observe(textNode, { + characterData: true + }); + timerFunc = function () { + counter = (counter + 1) % 2; + textNode.data = String(counter); + }; + isUsingMicroTask = true; + } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + // Fallback to setImmediate. + // Technically it leverages the (macro) task queue, + // but it is still a better choice than setTimeout. + timerFunc = function () { + setImmediate(flushCallbacks); + }; + } else { + // Fallback to setTimeout. + timerFunc = function () { + setTimeout(flushCallbacks, 0); + }; + } + + function nextTick (cb, ctx) { + var _resolve; + callbacks.push(function () { + if (cb) { + try { + cb.call(ctx); + } catch (e) { + handleError(e, ctx, 'nextTick'); + } + } else if (_resolve) { + _resolve(ctx); + } + }); + if (!pending) { + pending = true; + timerFunc(); + } + // $flow-disable-line + if (!cb && typeof Promise !== 'undefined') { + return new Promise(function (resolve) { + _resolve = resolve; + }) + } + } + + /* */ + + var mark; + var measure; + + { + var perf = inBrowser && window.performance; + /* istanbul ignore if */ + if ( + perf && + perf.mark && + perf.measure && + perf.clearMarks && + perf.clearMeasures + ) { + mark = function (tag) { return perf.mark(tag); }; + measure = function (name, startTag, endTag) { + perf.measure(name, startTag, endTag); + perf.clearMarks(startTag); + perf.clearMarks(endTag); + // perf.clearMeasures(name) + }; + } + } + + /* not type checking this file because flow doesn't play well with Proxy */ + + var initProxy; + + { + var allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ); + + var warnNonPresent = function (target, key) { + warn( + "Property or method \"" + key + "\" is not defined on the instance but " + + 'referenced during render. Make sure that this property is reactive, ' + + 'either in the data option, or for class-based components, by ' + + 'initializing the property. ' + + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', + target + ); + }; + + var warnReservedPrefix = function (target, key) { + warn( + "Property \"" + key + "\" must be accessed with \"$data." + key + "\" because " + + 'properties starting with "$" or "_" are not proxied in the Vue instance to ' + + 'prevent conflicts with Vue internals. ' + + 'See: https://vuejs.org/v2/api/#data', + target + ); + }; + + var hasProxy = + typeof Proxy !== 'undefined' && isNative(Proxy); + + if (hasProxy) { + var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact'); + config.keyCodes = new Proxy(config.keyCodes, { + set: function set (target, key, value) { + if (isBuiltInModifier(key)) { + warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); + return false + } else { + target[key] = value; + return true + } + } + }); + } + + var hasHandler = { + has: function has (target, key) { + var has = key in target; + var isAllowed = allowedGlobals(key) || + (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); + if (!has && !isAllowed) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return has || !isAllowed + } + }; + + var getHandler = { + get: function get (target, key) { + if (typeof key === 'string' && !(key in target)) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return target[key] + } + }; + + initProxy = function initProxy (vm) { + if (hasProxy) { + // determine which proxy handler to use + var options = vm.$options; + var handlers = options.render && options.render._withStripped + ? getHandler + : hasHandler; + vm._renderProxy = new Proxy(vm, handlers); + } else { + vm._renderProxy = vm; + } + }; + } + + /* */ + + var seenObjects = new _Set(); + + /** + * Recursively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. + */ + function traverse (val) { + _traverse(val, seenObjects); + seenObjects.clear(); + } + + function _traverse (val, seen) { + var i, keys; + var isA = Array.isArray(val); + if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { + return + } + if (val.__ob__) { + var depId = val.__ob__.dep.id; + if (seen.has(depId)) { + return + } + seen.add(depId); + } + if (isA) { + i = val.length; + while (i--) { _traverse(val[i], seen); } + } else { + keys = Object.keys(val); + i = keys.length; + while (i--) { _traverse(val[keys[i]], seen); } + } + } + + /* */ + + var normalizeEvent = cached(function (name) { + var passive = name.charAt(0) === '&'; + name = passive ? name.slice(1) : name; + var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first + name = once$$1 ? name.slice(1) : name; + var capture = name.charAt(0) === '!'; + name = capture ? name.slice(1) : name; + return { + name: name, + once: once$$1, + capture: capture, + passive: passive + } + }); + + function createFnInvoker (fns, vm) { + function invoker () { + var arguments$1 = arguments; + + var fns = invoker.fns; + if (Array.isArray(fns)) { + var cloned = fns.slice(); + for (var i = 0; i < cloned.length; i++) { + invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler"); + } + } else { + // return handler return value for single handlers + return invokeWithErrorHandling(fns, null, arguments, vm, "v-on handler") + } + } + invoker.fns = fns; + return invoker + } + + function updateListeners ( + on, + oldOn, + add, + remove$$1, + createOnceHandler, + vm + ) { + var name, def$$1, cur, old, event; + for (name in on) { + def$$1 = cur = on[name]; + old = oldOn[name]; + event = normalizeEvent(name); + if (isUndef(cur)) { + warn( + "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), + vm + ); + } else if (isUndef(old)) { + if (isUndef(cur.fns)) { + cur = on[name] = createFnInvoker(cur, vm); + } + if (isTrue(event.once)) { + cur = on[name] = createOnceHandler(event.name, cur, event.capture); + } + add(event.name, cur, event.capture, event.passive, event.params); + } else if (cur !== old) { + old.fns = cur; + on[name] = old; + } + } + for (name in oldOn) { + if (isUndef(on[name])) { + event = normalizeEvent(name); + remove$$1(event.name, oldOn[name], event.capture); + } + } + } + + /* */ + + function mergeVNodeHook (def, hookKey, hook) { + if (def instanceof VNode) { + def = def.data.hook || (def.data.hook = {}); + } + var invoker; + var oldHook = def[hookKey]; + + function wrappedHook () { + hook.apply(this, arguments); + // important: remove merged hook to ensure it's called only once + // and prevent memory leak + remove(invoker.fns, wrappedHook); + } + + if (isUndef(oldHook)) { + // no existing hook + invoker = createFnInvoker([wrappedHook]); + } else { + /* istanbul ignore if */ + if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { + // already a merged invoker + invoker = oldHook; + invoker.fns.push(wrappedHook); + } else { + // existing plain hook + invoker = createFnInvoker([oldHook, wrappedHook]); + } + } + + invoker.merged = true; + def[hookKey] = invoker; + } + + /* */ + + function extractPropsFromVNodeData ( + data, + Ctor, + tag + ) { + // we are only extracting raw values here. + // validation and default values are handled in the child + // component itself. + var propOptions = Ctor.options.props; + if (isUndef(propOptions)) { + return + } + var res = {}; + var attrs = data.attrs; + var props = data.props; + if (isDef(attrs) || isDef(props)) { + for (var key in propOptions) { + var altKey = hyphenate(key); + { + var keyInLowerCase = key.toLowerCase(); + if ( + key !== keyInLowerCase && + attrs && hasOwn(attrs, keyInLowerCase) + ) { + tip( + "Prop \"" + keyInLowerCase + "\" is passed to component " + + (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + + " \"" + key + "\". " + + "Note that HTML attributes are case-insensitive and camelCased " + + "props need to use their kebab-case equivalents when using in-DOM " + + "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." + ); + } + } + checkProp(res, props, key, altKey, true) || + checkProp(res, attrs, key, altKey, false); + } + } + return res + } + + function checkProp ( + res, + hash, + key, + altKey, + preserve + ) { + if (isDef(hash)) { + if (hasOwn(hash, key)) { + res[key] = hash[key]; + if (!preserve) { + delete hash[key]; + } + return true + } else if (hasOwn(hash, altKey)) { + res[key] = hash[altKey]; + if (!preserve) { + delete hash[altKey]; + } + return true + } + } + return false + } + + /* */ + + // The template compiler attempts to minimize the need for normalization by + // statically analyzing the template at compile time. + // + // For plain HTML markup, normalization can be completely skipped because the + // generated render function is guaranteed to return Array. There are + // two cases where extra normalization is needed: + + // 1. When the children contains components - because a functional component + // may return an Array instead of a single root. In this case, just a simple + // normalization is needed - if any child is an Array, we flatten the whole + // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep + // because functional components already normalize their own children. + function simpleNormalizeChildren (children) { + for (var i = 0; i < children.length; i++) { + if (Array.isArray(children[i])) { + return Array.prototype.concat.apply([], children) + } + } + return children + } + + // 2. When the children contains constructs that always generated nested Arrays, + // e.g.