/* * Copyright 2015 the original author or authors. * @https://github.com/scouter-project/scouter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package scouter.client.xlog.views; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.draw2d.IFigure; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.viewers.*; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.part.ViewPart; import org.eclipse.zest.core.viewers.*; import org.eclipse.zest.core.widgets.ZestStyles; import org.eclipse.zest.layouts.LayoutStyles; import org.eclipse.zest.layouts.algorithms.HorizontalTreeLayoutAlgorithm; import scouter.client.Images; import scouter.client.model.TextProxy; import scouter.client.model.XLogData; import scouter.client.model.XLogProxy; import scouter.client.net.INetReader; import scouter.client.net.TcpProxy; import scouter.client.popup.EditableMessageDialog; import scouter.client.popup.SQLFormatDialog; import scouter.client.server.ServerManager; import scouter.client.util.*; import scouter.client.xlog.actions.OpenXLogProfileJob; import scouter.io.DataInputX; import scouter.lang.CountryCode; import scouter.lang.pack.MapPack; import scouter.lang.pack.XLogPack; import scouter.lang.pack.XLogTypes; import scouter.lang.step.*; import scouter.lang.value.DecimalValue; import scouter.lang.value.MapValue; import scouter.net.RequestCmd; import scouter.util.*; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; public class XLogFlowView extends ViewPart { public final static String ID = XLogFlowView.class.getName(); private GraphViewer viewer = null; private String date; boolean showSql = true, showApicall = true; public void createPartControl(Composite parent) { parent.setLayout(new FillLayout()); parent.addControlListener(new ControlListener() { public void controlResized(ControlEvent e) { viewer.applyLayout(); } public void controlMoved(ControlEvent e) { } }); viewer = new GraphViewer(parent, SWT.NONE); viewer.setContentProvider(new GraphContentProvider()); viewer.setLabelProvider(new GraphLabelProvider()); viewer.setLayoutAlgorithm(new HorizontalTreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING)); viewer.addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { StructuredSelection sel = (StructuredSelection) event.getSelection(); Object o = sel.getFirstElement(); if (o instanceof DependencyElement) { DependencyElement de = (DependencyElement) o; switch (de.type) { case SERVICE: new OpenXLogProfileJob(XLogFlowView.this.getViewSite().getShell().getDisplay(), date, de.id, de.tags.getInt("serverId")).schedule(); break; case SQL: List<StyleRange> srList = new ArrayList<StyleRange>(); StringBuffer sb = new StringBuffer(); String sql = de.tags.getText("sql"); sb.append(sql); String error = null; if (de.error != 0) { error = TextProxy.error.getLoadText(date, de.error, de.tags.getInt("serverId")); } new SQLFormatDialog().show(sb.toString(), error); break; case API_CALL: srList = new ArrayList<StyleRange>(); sb = new StringBuffer(); String apicall = de.name; sb.append(apicall); if (de.error != 0) { error = TextProxy.error.getLoadText(date, de.error, de.tags.getInt("serverId")); if (StringUtil.isNotEmpty(error)) { sb.append("\n"); srList.add(new StyleRange(sb.length(), error.length(), ColorUtil.getInstance().getColor("red"), null, SWT.BOLD)); sb.append(error); } } new EditableMessageDialog().show("API Call", sb.toString(), srList); break; case DISPATCH: case THREAD: new OpenXLogProfileJob(XLogFlowView.this.getViewSite().getShell().getDisplay(), date, de.id, de.tags.getInt("serverId")).schedule(); break; // case DISPATCH: // srList = new ArrayList<StyleRange>(); // sb = new StringBuffer(); // String dispatch = de.name; // sb.append(dispatch); // if (de.error != 0) { // error = TextProxy.error.getLoadText(date, de.error, de.tags.getInt("serverId")); // if (StringUtil.isNotEmpty(error)) { // sb.append("\n"); // srList.add(new StyleRange(sb.length(), error.length(), ColorUtil.getInstance().getColor("red"), null, SWT.BOLD)); // sb.append(error); // } // } // new EditableMessageDialog().show("Dispatch", sb.toString(), srList); // break; } } } }); IToolBarManager man = getViewSite().getActionBars().getToolBarManager(); Action sqlFilterAct = new Action("Show SQL", IAction.AS_CHECK_BOX) { public void run() { showSql = isChecked(); viewer.refresh(); viewer.applyLayout(); } }; sqlFilterAct.setImageDescriptor(ImageUtil.getImageDescriptor(Images.database)); man.add(sqlFilterAct); sqlFilterAct.setChecked(true); Action apiFilterAct = new Action("Show API Call", IAction.AS_CHECK_BOX) { public void run() { showApicall = isChecked(); viewer.refresh(); viewer.applyLayout(); } }; apiFilterAct.setImageDescriptor(ImageUtil.getImageDescriptor(Images.link)); man.add(apiFilterAct); apiFilterAct.setChecked(true); viewer.setFilters(new ViewerFilter[] {filter}); } public void loadByTxId(String date, long txid) { this.date = date; this.setPartName("Service Flow - " + Hexa32.toString32(txid)); MapPack param = new MapPack(); param.put("date", date); param.put("txid", txid); new ProcessDependencyTask(RequestCmd.XLOG_READ_BY_TXID, param).schedule(); } public void loadByGxId(String date, long gxId) { this.date = date; this.setPartName("Service Flow - " + Hexa32.toString32(gxId)); MapPack param = new MapPack(); param.put("date", date); param.put("gxid", gxId); new ProcessDependencyTask(RequestCmd.XLOG_READ_BY_GXID, param).schedule(); } class ProcessDependencyTask extends Job { final String requestCmd; final MapPack param; LongKeyLinkedMap<DependencyElement> rootMap = new LongKeyLinkedMap<DependencyElement>(); LongKeyLinkedMap<DependencyElement> serviceMap = new LongKeyLinkedMap<DependencyElement>(); public ProcessDependencyTask(String requestCmd, MapPack param) { super("Processing Dependencies Task"); this.requestCmd = requestCmd; this.param = param; } protected IStatus run(IProgressMonitor monitor) { monitor.beginTask("Collecting XLog/Profile....", IProgressMonitor.UNKNOWN); Iterator<Integer> itr = ServerManager.getInstance().getOpenServerList().iterator(); while (itr.hasNext()) { final int serverId = itr.next(); TcpProxy tcp = TcpProxy.getTcpProxy(serverId); try { tcp.process(requestCmd, param, new INetReader() { public void process(DataInputX in) throws IOException { XLogPack xlog = (XLogPack) in.readPack(); ElementType eType = ElementType.SERVICE; boolean excludeObjName = false; switch (xlog.xType) { case XLogTypes.ASYNCSERVLET_DISPATCHED_SERVICE: eType = ElementType.DISPATCH; excludeObjName = true; break; case XLogTypes.BACK_THREAD2: eType = ElementType.THREAD; excludeObjName = true; break; } DependencyElement serviceElement = new DependencyElement(eType, xlog.txid); String objName = TextProxy.object.getLoadText(date, xlog.objHash, serverId); String serviceName = TextProxy.service.getLoadText(date, xlog.service, serverId); serviceElement.elapsed = xlog.elapsed; if(excludeObjName) { serviceElement.name = serviceName; } else { serviceElement.name = serviceName + "\n(" + objName + ")"; } serviceElement.error = xlog.error; serviceElement.xtype = xlog.xType; serviceElement.threadName = TextProxy.hashMessage.getLoadText(date, xlog.threadNameHash, serverId); serviceElement.tags.put("caller", xlog.caller); serviceElement.tags.put("ip", IPUtil.toString(xlog.ipaddr)); serviceElement.tags.put("serverId", serverId); if (StringUtil.isNotEmpty(xlog.countryCode)) { serviceElement.tags.put("country", CountryCode.getCountryName(xlog.countryCode)); serviceElement.tags.put("city", TextProxy.city.getLoadText(date, xlog.city, serverId)); } serviceMap.put(xlog.txid, serviceElement); } }); } catch (Throwable th) { ConsoleProxy.errorSafe(th.toString()); } finally { TcpProxy.putTcpProxy(tcp); } } LongEnumer txidEnumer = serviceMap.keys(); while (txidEnumer.hasMoreElements()) { long txid = txidEnumer.nextLong(); final DependencyElement serviceElement = serviceMap.get(txid); final int serverId = (int) serviceElement.tags.getLong("serverId"); if (serverId == 0) continue; TcpProxy tcp = TcpProxy.getTcpProxy(serverId); try { MapPack param = new MapPack(); param.put("date", date); param.put("txid", new DecimalValue(txid)); tcp.process(RequestCmd.TRANX_PROFILE_FULL, param, new INetReader() { public void process(DataInputX in) throws IOException { byte[] buff = in.readBlob(); Step[] steps = Step.toObjects(buff); if (steps == null) return; for (Step step : steps) { stepToElement(serviceElement, step, serverId); } } }); } catch (Exception e) { ConsoleProxy.errorSafe(e.toString()); } finally { TcpProxy.putTcpProxy(tcp); } if (serviceMap.size() == 1 || serviceElement.tags.getLong("caller") == 0) { String ip = serviceElement.tags.getText("ip"); if (StringUtil.isNotEmpty(ip)) { int id = HashUtil.hash(ip); DependencyElement ipElement = rootMap.get(id); if (ipElement == null) { ipElement = new DependencyElement(ElementType.USER, id); if (StringUtil.isNotEmpty(serviceElement.tags.getText("country"))) { ipElement.name = ip + "\n(" + serviceElement.tags.getText("city") + ", " + serviceElement.tags.getText("country") + ")"; } else { ipElement.name = ip; } rootMap.put(id, ipElement); } ipElement.addChild(serviceElement); } else { long id = TimeUtil.getCurrentTime(); DependencyElement dummyElement = new DependencyElement(ElementType.USER, id); dummyElement.name = "???.???.???.???"; rootMap.put(id, dummyElement); } } } ExUtil.exec(viewer.getGraphControl(), new Runnable() { public void run() { viewer.setInput(rootMap); } }); return Status.OK_STATUS; } private void stepToElement(final DependencyElement serviceElement, Step step, final int serverId) { switch (step.getStepType()) { case StepEnum.APICALL: case StepEnum.APICALL2: ApiCallStep apicallstep = (ApiCallStep) step; DependencyElement apiElement = new DependencyElement(ElementType.API_CALL, apicallstep.txid + apicallstep.hash); apiElement.elapsed = apicallstep.elapsed; apiElement.error = apicallstep.error; apiElement.name = TextProxy.apicall.getLoadText(date, apicallstep.hash, serverId); apiElement.tags.put("serverId", serverId); apiElement.address = apicallstep.address; if (apicallstep.txid != 0) { DependencyElement calledService = serviceMap.get(apicallstep.txid); if (calledService != null) { calledService.address = apicallstep.address; serviceElement.addChild(calledService); } else { serviceElement.addChild(apiElement); } } else { serviceElement.addChild(apiElement); } break; case StepEnum.DISPATCH: DispatchStep dispatchStep = (DispatchStep) step; DependencyElement dispatchElement = new DependencyElement(ElementType.DISPATCH, dispatchStep.txid + dispatchStep.hash); dispatchElement.elapsed = dispatchStep.elapsed; dispatchElement.error = dispatchStep.error; dispatchElement.name = TextProxy.apicall.getLoadText(date, dispatchStep.hash, serverId); dispatchElement.tags.put("serverId", serverId); if (dispatchStep.txid != 0) { DependencyElement calledService = serviceMap.get(dispatchStep.txid); if (calledService != null) { serviceElement.addChild(calledService); } else { serviceElement.addChild(dispatchElement); } } else { serviceElement.addChild(dispatchElement); } break; case StepEnum.THREAD_CALL_POSSIBLE: ThreadCallPossibleStep tcStep = (ThreadCallPossibleStep) step; XLogData threadStepXlog = null; if(tcStep.txid != 0L) { threadStepXlog = XLogProxy.getXLogData(serverId, date, tcStep.txid); } if(threadStepXlog != null) { tcStep.threaded = 1; tcStep.elapsed = threadStepXlog.p.elapsed; } if(tcStep.threaded == 0) break; DependencyElement tcElement = new DependencyElement(ElementType.DISPATCH, tcStep.txid + tcStep.hash); tcElement.elapsed = tcStep.elapsed; tcElement.name = TextProxy.apicall.getLoadText(date, tcStep.hash, serverId); tcElement.tags.put("serverId", serverId); if (tcStep.txid != 0) { DependencyElement calledService = serviceMap.get(tcStep.txid); if (calledService != null) { serviceElement.addChild(calledService); } else { serviceElement.addChild(tcElement); } } else { serviceElement.addChild(tcElement); } break; case StepEnum.APICALL_SUM: ApiCallSum apicallsum = (ApiCallSum) step; DependencyElement apiSumElement = new DependencyElement(ElementType.API_CALL, apicallsum.hash); apiSumElement.dupleCnt = apicallsum.count; apiSumElement.elapsed = (int) apicallsum.elapsed; apiSumElement.error = apicallsum.error; apiSumElement.name = TextProxy.apicall.getLoadText(date, apicallsum.hash, serverId); apiSumElement.tags.put("serverId", serverId); serviceElement.addChild(apiSumElement); break; case StepEnum.SQL: case StepEnum.SQL2: case StepEnum.SQL3: SqlStep sqlstep = (SqlStep) step; DependencyElement sqlElement = new DependencyElement(ElementType.SQL, sqlstep.hash); sqlElement.elapsed = sqlstep.elapsed; String table = TextProxy.sql_tables.getLoadText(date, sqlstep.hash, serverId); String sql = TextProxy.sql.getLoadText(date, sqlstep.hash, serverId).trim(); sqlElement.name = StringUtil.isNotEmpty(table) ? table : StringUtil.truncate(sql, 20) + "..."; //sqlElement.name = table; sqlElement.error = sqlstep.error; sqlElement.tags.put("serverId", serverId); sqlElement.tags.put("sql", sql); serviceElement.addChild(sqlElement); break; case StepEnum.SQL_SUM: SqlSum sqlsum = (SqlSum) step; DependencyElement sqlSumElement = new DependencyElement(ElementType.SQL, sqlsum.hash); sqlSumElement.dupleCnt = sqlsum.count; sqlSumElement.elapsed = (int) sqlsum.elapsed; sqlSumElement.error = sqlsum.error; table = TextProxy.sql_tables.getLoadText(date, sqlsum.hash, serverId); sql = TextProxy.sql.getLoadText(date, sqlsum.hash, serverId).trim(); sqlSumElement.name = StringUtil.isNotEmpty(table) ? table : StringUtil.truncate(sql, 20) + "..."; sqlSumElement.tags.put("serverId", serverId); sqlSumElement.tags.put("sql", sql); serviceElement.addChild(sqlSumElement); break; case StepEnum.THREAD_SUBMIT: ThreadSubmitStep tsStep = (ThreadSubmitStep) step; TcpProxy tcp = TcpProxy.getTcpProxy(serverId); try { MapPack param = new MapPack(); param.put("date", date); param.put("txid", tsStep.txid); tcp.process(RequestCmd.TRANX_PROFILE_FULL, param, new INetReader() { public void process(DataInputX in) throws IOException { byte[] buff = in.readBlob(); Step[] steps = Step.toObjects(buff); if (steps == null) return; for (Step step : steps) { stepToElement(serviceElement, step, serverId); } } }); } catch (Throwable th) { ConsoleProxy.errorSafe(th.toString()); } finally { TcpProxy.putTcpProxy(tcp); } break; } } } public void setFocus() { } ViewerFilter filter = new ViewerFilter() { public boolean select(Viewer viewer, Object parentElement, Object element) { if (element instanceof DependencyElement) { DependencyElement de = (DependencyElement) element; switch (de.type) { case SQL: return showSql; case API_CALL: case DISPATCH: return showApicall; } } return true; } }; enum ElementType { USER, SERVICE, API_CALL, SQL, DISPATCH, THREAD } public static class DependencyElement { ElementType type; long id; int dupleCnt = 1; LinkedMap<Long, DependencyElement> childMap = new LinkedMap<Long, DependencyElement>(); public String name; public int elapsed; public int error; public byte xtype; public String address; public String threadName; MapValue tags = new MapValue(); DependencyElement(ElementType type, long id) { this.type = type; this.id = id; } public void addChild(DependencyElement child) { DependencyElement obj = childMap.get(child.id); if (obj == null) { childMap.put(child.id, child); } else { obj.dupleCnt += child.dupleCnt; obj.elapsed += child.elapsed; if (child.error != 0) { obj.error = child.error; } } } public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (id ^ (id >>> 32)); return result; } public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DependencyElement other = (DependencyElement) obj; if (id != other.id) return false; return true; } } class GraphContentProvider implements IGraphEntityContentProvider { LongKeyLinkedMap<DependencyElement> root; public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { if (newInput != null) { root = (LongKeyLinkedMap<DependencyElement>) newInput; } } public Object[] getElements(Object inputElement) { ArrayList<DependencyElement> list = new ArrayList<DependencyElement>(); LongEnumer ipEnumer = root.keys(); while (ipEnumer.hasMoreElements()) { long id = ipEnumer.nextLong(); DependencyElement ipElement = root.get(id); list.add(ipElement); collectElement(ipElement.childMap, list); } return list.toArray(new DependencyElement[list.size()]); } public Object[] getConnectedTo(Object entity) { if (entity instanceof DependencyElement) { ArrayList<DependencyElement> list = new ArrayList<DependencyElement>(); DependencyElement element = (DependencyElement) entity; Enumeration<DependencyElement> values = element.childMap.values(); while (values.hasMoreElements()) { DependencyElement de = values.nextElement(); list.add(de); } return list.toArray(new DependencyElement[list.size()]); } return null; } } private void collectElement(LinkedMap<Long, DependencyElement> childMap, ArrayList<DependencyElement> list) { Enumeration<DependencyElement> values = childMap.values(); while (values.hasMoreElements()) { DependencyElement element = values.nextElement(); list.add(element); collectElement(element.childMap, list); } } class GraphLabelProvider extends LabelProvider implements IEntityStyleProvider, IEntityConnectionStyleProvider { public String getText(Object element) { if (element instanceof DependencyElement) { DependencyElement de = (DependencyElement) element; if (de.name == null) de.name = ""; switch(de.type) { case SQL: case API_CALL: String name1 = de.name.trim().replaceAll("[\r\n]+", " ").replaceAll("\\s+", " "); if (name1.length() > 40 && name1.length() <= 65) { name1 = name1.substring(0, 30) + "\n " + name1.substring(30); } else if(name1.length() > 65) { name1 = name1.substring(0, 30) + "\n " + name1.substring(30, 60) + "..."; } return name1; case DISPATCH: case THREAD: String name2 = de.name.trim().replaceAll("[\r\n]+", " ").replaceAll("\\s+", " "); if (name2.length() > 40 && name2.length() <= 65) { name2 = name2.substring(0, 30) + "\n " + name2.substring(30); } else if(name2.length() > 65) { name2 = name2.substring(0, 30) + "\n " + name2.substring(30, 60) + "..."; } if(de.threadName != null) { name2 = name2 + "\n <" + de.threadName + ">"; } return name2; } String elementText; if(de.address != null) { elementText = de.name + "\n : " + de.address; } else { elementText = de.name; } return elementText; } else if (element instanceof EntityConnectionData) { if (((EntityConnectionData) element).dest instanceof DependencyElement) { DependencyElement de = (DependencyElement) ((EntityConnectionData) element).dest; String elapsed = FormatUtil.print(de.elapsed, "#,###") + " ms"; if (de.dupleCnt > 1) { return "(" + de.dupleCnt + ") " + elapsed; } else { return elapsed; } } } return null; } public Image getImage(Object element) { if (element instanceof DependencyElement) { DependencyElement de = (DependencyElement) element ; switch (de.type) { case USER: return Images.CONFIG_USER; case SERVICE: return Images.server; case API_CALL: return Images.link; case SQL : return Images.database; case DISPATCH: return Images.arrow_right; case THREAD: return Images.timer; } } return null; } public Color getNodeHighlightColor(Object entity) { return ColorUtil.getInstance().getColor(SWT.COLOR_WHITE); } public Color getBorderColor(Object entity) { if (entity instanceof DependencyElement) { DependencyElement de = (DependencyElement) entity; if (de.type == ElementType.SERVICE) { if(de.xtype == XLogTypes.ASYNCSERVLET_DISPATCHED_SERVICE || de.xtype == XLogTypes.BACK_THREAD2) { return ColorUtil.getInstance().getColor(SWT.COLOR_GRAY); } else { return ColorUtil.getInstance().getColor(SWT.COLOR_DARK_BLUE); } } else { return ColorUtil.getInstance().getColor(SWT.COLOR_WHITE); } } return null; } public Color getBorderHighlightColor(Object entity) { return null; } public int getBorderWidth(Object entity) { if (entity instanceof DependencyElement) { DependencyElement de = (DependencyElement) entity; if (de.type == ElementType.SERVICE) { return 1; } } return 0; } public Color getBackgroundColour(Object entity) { if (entity instanceof DependencyElement) { return ColorUtil.getInstance().getColor(SWT.COLOR_WHITE); } return null; } public Color getForegroundColour(Object entity) { if (entity instanceof DependencyElement) { DependencyElement de = (DependencyElement) entity; if (de.xtype == XLogTypes.ASYNCSERVLET_DISPATCHED_SERVICE || de.xtype == XLogTypes.BACK_THREAD2) { return ColorUtil.getInstance().getColor(SWT.COLOR_DARK_GRAY); } } return null; } public IFigure getTooltip(Object entity) { return null; } public boolean fisheyeNode(Object entity) { return false; } public int getConnectionStyle(Object src, Object dest) { return ZestStyles.CONNECTIONS_DIRECTED; } public Color getColor(Object src, Object dest) { if (dest instanceof DependencyElement) { DependencyElement de = (DependencyElement) dest; if (de.error != 0) { return ColorUtil.getInstance().getColor(SWT.COLOR_RED); } } return null; } public Color getHighlightColor(Object src, Object dest) { return null; } public int getLineWidth(Object src, Object dest) { return 0; } } public static void main(String[] args) { String str = "SELECT * FROM ( SELECT CEIL(ROWNUM '@' ) pg , cc.* FROM ( SELECT a.news_no"; System.out.println(str.replaceAll("\\s+", " ")); } }