/*
* 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.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import scouter.client.Images;
import scouter.client.model.TextProxy;
import scouter.client.model.XLogData;
import scouter.client.popup.XLogYValueMaxDialog;
import scouter.client.preferences.PManager;
import scouter.client.preferences.PreferenceConstants;
import scouter.client.threads.ObjectSelectManager;
import scouter.client.util.ChartUtil;
import scouter.client.util.ColorUtil;
import scouter.client.util.TimeUtil;
import scouter.client.xlog.ImageCache;
import scouter.client.xlog.XLogFilterStatus;
import scouter.client.xlog.XLogYAxisEnum;
import scouter.lang.pack.XLogPack;
import scouter.util.*;
import java.util.Date;
import java.util.Enumeration;
public class XLogViewPainter {
public static Color color_black = new Color(null, 0, 0, 0);
public static Color color_white = new Color(null, 255, 255, 255);
public static Color color_grid_narrow = new Color(null, 220, 228, 255);
public static Color color_grid_wide = new Color(null, 200, 208, 255);
public static Color color_blue = new Color(null, 0, 0, 255);
public static Color color_red = new Color(null, 255, 0, 0);
public static Color ignore_area = new Color(null, 234, 234, 234);
public long xTimeRange = DateUtil.MILLIS_PER_MINUTE * 5;
public long originalRange = xTimeRange;
private double yValueMax;
private double yValueMin = 0;
private boolean viewIsInAdditionalDataLoading = false;
private XLogViewMouse mouse;
private PointMap pointMap = new PointMap();
private final LongKeyLinkedMap<XLogData> xLogPerfData;
private ImageCache dotImage = ImageCache.getInstance();
public int selectedUrlHash = 0;
XLogFilterStatus filterStatus;
public XLogYAxisEnum yAxisMode = XLogYAxisEnum.ELAPSED;
private int filter_hash = 0;
public StrMatch objNameMat;
public StrMatch serviceMat;
public StrMatch ipMat;
public StrMatch loginMat;
public StrMatch descMat;
public StrMatch text1Mat;
public StrMatch text2Mat;
public StrMatch userAgentMat;
public String yyyymmdd;
ITimeChange callback;
int serverId;
public XLogViewPainter(LongKeyLinkedMap<XLogData> xLogPerfData, XLogViewMouse mouse, ITimeChange callback) {
this.mouse = mouse;
this.xLogPerfData = xLogPerfData;
this.callback = callback;
}
private long endTime;
public void setEndTime(long etime) {
this.endTime = etime;
if (this.endTime <= 0)
this.endTime = 0;
}
public void setTimeRange(long range) {
// if (range < DateUtil.MILLIS_PER_MINUTE) {
// range = DateUtil.MILLIS_PER_MINUTE;
// }
this.originalRange = this.xTimeRange = range;
}
public void setViewIsInAdditionalDataLoading(boolean b) {
this.viewIsInAdditionalDataLoading = b;
}
public long getTimeRange() {
return this.xTimeRange;
}
public void setValueRange(double minValue, double maxValue) {
this.yValueMin = minValue;
this.yValueMax = maxValue;
}
public void dispose() {
if (this.ibuffer != null)
this.ibuffer.dispose();
}
public void set(Rectangle area) {
this.area = area;
}
private Rectangle area;
private Image ibuffer;
boolean onGoing = false;
private Object lock = new Object();
public void build() {
if (area == null)
return;
synchronized (lock) {
if(onGoing)
return;
onGoing = true;
}
int work_w = area.width < 200 ? 200 : area.width;
int work_h = area.height < 200 ? 200 : area.height;
Image img = new Image(null, work_w, work_h);
GC gc = new GC(img);
draw(gc, work_w, work_h);
gc.dispose();
Image old = ibuffer;
ibuffer = img;
if (old != null) {
old.dispose();
}
synchronized (lock) {
onGoing = false;
}
}
int chart_x;
long paintedEndTime;
public long getLastTime() {
return paintedEndTime;
}
private void draw(GC gc, int work_w, int work_h) {
if (area == null)
return;
String maxLabel = FormatUtil.print(new Double(yValueMax), "#,##0.00");
chart_x = maxLabel.length() * 6 + 30;
int chart_y = 30;
int chart_w = work_w - chart_x - 30;
int chart_h = work_h - chart_y - 30;
pointMap.reset(chart_w, chart_h);
long time_end = ((this.endTime > 0) ? this.endTime : TimeUtil.getCurrentTime(serverId)) + moveWidth;
long time_start = time_end - xTimeRange;
if (zoomMode) {
time_end = zoomEndtime + moveWidth;
time_start = time_end - xTimeRange;
}
paintedEndTime = time_end;
gc.setForeground(color_black);
if (filter_hash != new XLogFilterStatus().hashCode()) {
gc.setBackground(ColorUtil.getInstance().getColor("azure"));
} else {
gc.setBackground(color_white);
}
gc.fillRectangle(0, 0, work_w, work_h);
if (yAxisMode == XLogYAxisEnum.ELAPSED) {
int ignoreMs = PManager.getInstance().getInt(PreferenceConstants.P_XLOG_IGNORE_TIME);
if (yValueMin == 0 && ignoreMs > 0) {
gc.setBackground(ignore_area);
int chart_igreno_h = (int) ((ignoreMs / (yValueMax * 1000)) * chart_h);
if (chart_igreno_h > chart_h) {
chart_igreno_h = chart_h;
}
gc.fillRoundRectangle(chart_x, 30 + chart_h - chart_igreno_h, chart_w + 5, chart_igreno_h + 5, 1, 1);
gc.setBackground(color_white);
}
}
gc.setLineWidth(1);
gc.setLineStyle(SWT.LINE_DOT);
double valueRange = yValueMax - yValueMin;
double yUnit = ChartUtil.getYaxisUnit(valueRange, chart_h);
for (double yValue = 0; yValue <= valueRange; yValue += yUnit) {
int y = (int) (chart_y + chart_h - chart_h * yValue / valueRange);
gc.setForeground(color_grid_narrow);
gc.drawLine(chart_x, y, (int) (chart_x + chart_w), y);
String s = FormatUtil.print(new Double(yValue + yValueMin), "#,##0.00");
gc.setForeground(color_black);
gc.drawString(s, chart_x - (15 + s.length() * 6), y - 5);
}
double xUnit = ChartUtil.getSplitTimeUnit(xTimeRange, chart_w);
String xLabelFormat = ChartUtil.getTimeFormat((long) xUnit);
long spareTime = time_end % (long) xUnit;
int labelOnNum = (int) (time_end % (xUnit * 5) / xUnit);
int xi = 0;
for (double timeDelta = xTimeRange - spareTime; timeDelta > 0; timeDelta -= xUnit) {
boolean labelOn = (xi++ % 5 == labelOnNum);
if (labelOn) {
gc.setForeground(color_grid_wide);
gc.setLineStyle(SWT.LINE_SOLID);
} else {
gc.setForeground(color_grid_narrow);
gc.setLineStyle(SWT.LINE_DOT);
}
int x = (int) (chart_x + (chart_w * timeDelta / xTimeRange));
gc.drawLine(x, chart_y, x, chart_y + chart_h);
if (labelOn) {
gc.setForeground(color_black);
String s = FormatUtil.print(new Date(time_start + (long) timeDelta), xLabelFormat);
gc.drawString(s, x - 25, chart_y + chart_h + 5 + 5);
}
}
drawXPerfData(gc, time_start, time_end, chart_x, chart_y, chart_w, chart_h);
drawChartBorder(gc, chart_x, chart_y, chart_w, chart_h);
drawYaxisDescription(gc, chart_x, chart_y);
drawTxCount(gc, chart_x, chart_w, chart_y);
if (zoomMode) {
drawZoomMode(gc, chart_x, chart_y, chart_w, time_start, time_end);
drawZoomOut(gc, chart_x, chart_y, chart_w, chart_h);
} else {
if (moveWidth != 0 || originalRange != xTimeRange) {
drawCircle(gc, chart_x, chart_y, chart_w, chart_h);
}
}
}
private void drawZoomMode(GC gc, int chart_x, int chart_y, int chart_w, long stime, long etime) {
String cntText = "Zoom Mode(" + DateUtil.format(stime, "HH:mm") + "~" + DateUtil.format(etime, "HH:mm") + ")";
gc.drawText(cntText, chart_x + chart_w - 140, chart_y - 20);
}
private void drawTxCount(GC gc, int chart_x, int chart_w, int chart_y) {
gc.setFont(null);
String cntText = " Count : " + FormatUtil.print(new Long(count), "#,##0");
int strLen = gc.stringExtent(cntText).x;
gc.drawText(cntText, chart_x + chart_w - strLen - 10, chart_y - 20);
}
private void drawYaxisDescription(GC gc, int chart_x, int chart_y) {
gc.setFont(null);
String desc = " " + yAxisMode.getDesc();
gc.drawText(desc, chart_x, chart_y - 20);
}
private void drawChartBorder(GC gc, int chart_sx, int chart_sy, int chart_w, int chart_h) {
// gc.setLineStyle(SWT.LINE_SOLID);
// gc.setLineWidth(5);
// gc.setForeground(color_red);
// gc.drawRoundRectangle(chart_sx - 2, chart_sy - 2, chart_w + 4, chart_h + 4, 1, 1);
gc.setLineStyle(SWT.LINE_SOLID);
gc.setLineWidth(1);
gc.setForeground(color_black);
gc.drawRoundRectangle(chart_sx, chart_sy, chart_w + 5, chart_h + 5, 1, 1);
}
public void drawImageBuffer(GC gc) {
if (ibuffer != null)
gc.drawImage(ibuffer, 0, 0);
}
public void drawSelectArea(GC gc) {
if (mouse.x1 >= 0 && mouse.y1 >= 0) {
if (mouse.x2 + mouse.y2 > 0) {
Color color = null;
switch (mouse.mode) {
case LIST_XLOG:
color = XLogViewPainter.color_blue;
break;
case ZOOM_AREA:
color = XLogViewPainter.color_red;
break;
default:
break;
}
if (color != null) {
gc.setBackground(color);
gc.setAlpha(15);
gc.setLineStyle(SWT.LINE_SOLID);
gc.fillRectangle(mouse.x1, mouse.y1, mouse.x2 - mouse.x1, mouse.y2 - mouse.y1);
gc.setAlpha(150);
gc.setLineWidth(1);
gc.setForeground(color);
gc.drawRectangle(mouse.x1, mouse.y1, mouse.x2 - mouse.x1, mouse.y2 - mouse.y1);
}
}
}
}
int mouseX;
int mouseY;
public void drawCircle(GC gc, int chart_x, int chart_y, int chart_w, int chart_h){
mouseX = chart_x + chart_w+ 5;
mouseY = (chart_h / 2) + chart_y - 16 ;
gc.drawImage(Images.circle, mouseX, mouseY);
}
public void drawZoomOut(GC gc, int chart_x, int chart_y, int chart_w, int chart_h){
mouseX = chart_x + chart_w+ 10;
mouseY = (chart_h / 2) + chart_y - 16 ;
gc.drawImage(Images.zoomout, mouseX, mouseY);
}
public boolean onMouseDoubleClick(int x, int y) {
boolean isChange = false;
if (x < chart_x) {
Display display = Display.getCurrent();
if (display == null) {
display = Display.getDefault();
}
XLogYValueMaxDialog dialog = new XLogYValueMaxDialog(display, this);
dialog.show();
isChange = true;
}
return isChange;
}
private void initializeChart() {
moveWidth = 0;
xTimeRange = originalRange;
load();
zoomMode = false;
build();
}
public boolean onMouseClick(int x, int y) {
boolean isChange = false;
if (moveWidth != 0 || xTimeRange != originalRange) {
if (x > mouseX && x < mouseX + 32
&& y > mouseY && y < mouseY + 32) {
initializeChart();
isChange = true;
}
}
return isChange;
}
private void removeOutsidePerfData(long time_start, long time_end) {
if (zoomMode) {
return;
}
while (xLogPerfData.size() > 0 && xLogPerfData.getFirstValue().p.endTime < time_start) {
xLogPerfData.removeFirst();
}
if (this.endTime > 0) {
while (xLogPerfData.size() > 0 && xLogPerfData.getLastValue().p.endTime > time_end) {
xLogPerfData.removeLast();
}
}
}
ObjectSelectManager objSelMgr = ObjectSelectManager.getInstance();
int count;
private void drawXPerfData(GC gc, long time_start, long time_end, int chart_x,
int chart_y, int chart_w, int chart_h) {
count = 0;
if (xLogPerfData.size() == 0) {
return;
}
if (viewIsInAdditionalDataLoading == false) {
removeOutsidePerfData(time_start - (DateUtil.MILLIS_PER_SECOND * 10), time_end);
}
Enumeration<XLogData> en = xLogPerfData.values();
while (en.hasMoreElements()) {
XLogData d = en.nextElement();
if (d == null || d.p.endTime < time_start || d.p.endTime > time_end) {
continue;
}
if (d.filter_hash != filter_hash) {
if (isFilterOk(d)) {
d.filter_ok = true;
} else {
d.filter_ok = false;
}
d.filter_hash = filter_hash;
}
if (d.filter_ok) {
if (objSelMgr.isUnselectedObject(d.p.objHash)) {
d.x = d.y = -1;
continue;
}
int x = (int) (chart_w * (d.p.endTime - time_start) / xTimeRange);
d.x = chart_x + x;
int y = 0;
double value = 0;
switch(yAxisMode) {
case ELAPSED:
if ((double) d.p.elapsed / 1000 >= yValueMax) {
value = -1;
} else {
value = (double) d.p.elapsed / 1000;
}
break;
case CPU:
if (d.p.cpu >= yValueMax) {
value = -1;
} else {
value = (double) d.p.cpu;
}
break;
case SQL_TIME:
if ((double) d.p.sqlTime / 1000 >= yValueMax) {
value = -1;
} else {
value = (double) d.p.sqlTime / 1000;
}
break;
case SQL_COUNT:
if ((double) d.p.sqlCount >= yValueMax) {
value = -1;
} else {
value = (double) d.p.sqlCount;
}
break;
case APICALL_TIME:
if ((double) d.p.apicallTime / 1000 >= yValueMax) {
value = -1;
} else {
value = (double) d.p.apicallTime / 1000;
}
break;
case APICALL_COUNT:
if ((double) d.p.apicallCount >= yValueMax) {
value = -1;
} else {
value = (double) d.p.apicallCount;
}
break;
case HEAP_USED:
if (d.p.kbytes >= yValueMax) {
value = -1;
} else {
value = d.p.kbytes;
}
break;
default:
if ((double) d.p.elapsed / 1000 >= yValueMax) {
value = -1;
} else {
value = (double) d.p.elapsed / 1000;
}
break;
}
if (value < 0) {
y = chart_h - 1;
} else {
y = (int) (chart_h * (value - yValueMin) / (yValueMax - yValueMin));
}
d.y = chart_h + chart_y - y;
if (pointMap.check(x, y)) {
try {
if (d.p.error != 0) {
// gc.setForeground(ColorUtil.getInstance().getColor("red"));
// gc.drawString(MARK, d.x, d.y, true);
gc.drawImage(dotImage.getXPErrorImage(d.p.xType), d.x, d.y);
} else {
// gc.setForeground(AgentColorManager.getInstance().getColor(d.p.objHash));
// gc.drawString(MARK, d.x, d.y, true);
gc.drawImage(dotImage.getXPImage(d.p.objHash, d.p.xType), d.x, d.y);
}
} catch (Throwable t) {
}
}
count++;
}
}
}
public double getYValue() {
return yValueMax;
}
public void setYValueMaxValue(double max) {
yValueMax = max;
build();
mouse.setNewYValue();
}
public int moveWidth = 0;
public void keyPressed(int keyCode) {
switch (keyCode) {
case 16777217:// UP Key
yValueMax += yValueMax >= 2 ? 1 : 0.1;
break;
case 16777218: // DOWN Key
yValueMax -= yValueMax >= 2 ? 1 : (yValueMax < 0.5 ? 0.05 : 0.1);
if (yValueMax < 0.05)
yValueMax = 0.05;
break;
case 16777261:
case 45: // -
xTimeRange += DateUtil.MILLIS_PER_MINUTE;
load();
break;
case 16777259:
case 61: // +
xTimeRange -= DateUtil.MILLIS_PER_MINUTE;
if (xTimeRange < DateUtil.MILLIS_PER_MINUTE)
xTimeRange = DateUtil.MILLIS_PER_MINUTE;
break;
case 16777219: // left arrow
moveWidth -= DateUtil.MILLIS_PER_SECOND * 10;
load();
break;
case 16777220: // right arrow
moveWidth += DateUtil.MILLIS_PER_SECOND * 10;
load();
break;
}
}
private boolean zoomMode = false;
private long zoomEndtime;
public boolean zoomIn(long stime, long etime) {
if (this.endTime < etime
|| this.endTime - xTimeRange > stime
|| etime < stime) {
MessageDialog.openWarning(Display.getCurrent().getActiveShell(), "Warning", "MaxRange is between " + DateUtil.format(this.endTime - xTimeRange, "HH:mm") + " and " + DateUtil.format(this.endTime, "HH:mm"));
return false;
}
zoomEndtime = etime;
xTimeRange = etime - stime;
moveWidth = 0;
zoomMode = true;
build();
return true;
}
public void endZoom() {
initializeChart();
}
public boolean isZoomMode() {
return zoomMode;
}
public void setServerId(int serverId) {
this.serverId = serverId;
}
private void load() {
if (zoomMode) {
return;
}
long time_current = TimeUtil.getCurrentTime(serverId);
long time_end = (this.endTime > 0 ? this.endTime : time_current) + moveWidth ;
long time_start = time_end - xTimeRange;
if (time_end > time_current) {
moveWidth -= DateUtil.MILLIS_PER_SECOND * 10;
return;
}
callback.timeRangeChanged(time_start, time_end);
}
public boolean isFilterOk(XLogData d) {
return isObjNameFilterOk(d)
&& isServiceFilterOk(d)
&& isIpFilterOk(d.p)
&& isLoginFilterOk(d)
&& isDescFilterOk(d)
&& isText1FilterOk(d)
&& isText2FilterOk(d)
&& isUserAgentFilterOk(d)
&& isErrorFilterOk(d.p)
&& isApicallFilterOk(d.p)
&& isSqlFilterOk(d.p);
}
public boolean isObjNameFilterOk(XLogData d) {
if (StringUtil.isEmpty(filterStatus.objName)) {
return true;
}
if (objNameMat.getComp() == StrMatch.COMP.EQU) {
return d.p.objHash == HashUtil.hash(objNameMat.getPattern());
} else {
String objName = TextProxy.object.getLoadText(yyyymmdd, d.p.objHash, d.serverId);
return objNameMat.include(objName);
}
}
public boolean isServiceFilterOk(XLogData d) {
if (StringUtil.isEmpty(filterStatus.service)) {
return true;
}
if (serviceMat.getComp() == StrMatch.COMP.EQU) {
return d.p.service == HashUtil.hash(serviceMat.getPattern());
} else {
String serviceName = TextProxy.service.getLoadText(yyyymmdd, d.p.service, d.serverId);
return serviceMat.include(serviceName);
}
}
public boolean isIpFilterOk(XLogPack p) {
if (StringUtil.isEmpty(filterStatus.ip)) {
return true;
}
String value = IPUtil.toString(p.ipaddr);
return ipMat.include(value);
}
public boolean isLoginFilterOk(XLogData d) {
if (StringUtil.isEmpty(filterStatus.login)) {
return true;
}
if (loginMat.getComp() == StrMatch.COMP.EQU) {
return d.p.login == HashUtil.hash(loginMat.getPattern());
} else {
String login = TextProxy.login.getLoadText(yyyymmdd, d.p.login, d.serverId);
return loginMat.include(login);
}
}
public boolean isDescFilterOk(XLogData d) {
if (StringUtil.isEmpty(filterStatus.desc)) {
return true;
}
if (descMat.getComp() == StrMatch.COMP.EQU) {
return d.p.desc == HashUtil.hash(descMat.getPattern());
} else {
String desc = TextProxy.desc.getLoadText(yyyymmdd, d.p.desc, d.serverId);
return descMat.include(desc);
}
}
public boolean isText1FilterOk(XLogData d) {
if (StringUtil.isEmpty(filterStatus.text1)) {
return true;
}
return text1Mat.include(d.p.text1);
}
public boolean isText2FilterOk(XLogData d) {
if (StringUtil.isEmpty(filterStatus.text2)) {
return true;
}
return text2Mat.include(d.p.text2);
}
public boolean isUserAgentFilterOk(XLogData d) {
if (StringUtil.isEmpty(filterStatus.userAgent)) {
return true;
}
if (userAgentMat.getComp() == StrMatch.COMP.EQU) {
return d.p.userAgent == HashUtil.hash(userAgentMat.getPattern());
} else {
String userAgent = TextProxy.userAgent.getLoadText(yyyymmdd, d.p.userAgent, d.serverId);
return userAgentMat.include(userAgent);
}
}
public boolean isErrorFilterOk(XLogPack p) {
if (filterStatus.onlyError) {
return p.error != 0;
}
return true;
}
public boolean isSqlFilterOk(XLogPack p) {
if (filterStatus.onlySql) {
return p.sqlCount > 0;
}
return true;
}
public boolean isApicallFilterOk(XLogPack p) {
if (filterStatus.onlyApicall) {
return p.apicallCount > 0;
}
return true;
}
public interface ITimeChange {
public void timeRangeChanged(long stime, long etime);
}
public void setYAxisMode(XLogYAxisEnum yAxis) {
this.yAxisMode = yAxis;
this.yValueMax = yAxis.getDefaultMax();
this.yValueMin = 0;
}
public void setFilterStatus(XLogFilterStatus status) {
this.filterStatus = status;
filter_hash = filterStatus.hashCode();
objNameMat = new StrMatch(status.objName);
serviceMat = new StrMatch(status.service);
ipMat = new StrMatch(status.ip);
loginMat = new StrMatch(status.login);
text1Mat = new StrMatch(status.text1);
text2Mat = new StrMatch(status.text2);
descMat = new StrMatch(status.desc);
userAgentMat = new StrMatch(status.userAgent);
}
}