package rocks.inspectit.ui.rcp.editor.text.input;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.mutable.MutableDouble;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.HyperlinkSettings;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.FormToolkit;
import rocks.inspectit.shared.all.communication.data.InvocationSequenceData;
import rocks.inspectit.shared.all.communication.data.SqlStatementData;
import rocks.inspectit.shared.all.util.ObjectUtils;
import rocks.inspectit.shared.cs.communication.data.InvocationSequenceDataHelper;
import rocks.inspectit.ui.rcp.InspectIT;
import rocks.inspectit.ui.rcp.InspectITImages;
import rocks.inspectit.ui.rcp.editor.root.IRootEditor;
import rocks.inspectit.ui.rcp.formatter.ColorFormatter;
import rocks.inspectit.ui.rcp.formatter.NumberFormatter;
/**
* The small summary below the SQL invocation overview.
*
* @author Ivan Senic
*
*/
public class SqlInvocSummaryTextInputController extends AbstractTextInputController {
/**
* Constant for the slowest80/20 color id.
*/
private static final String SLOWEST8020_COLOR = "slowest8020Color";
/**
* Link for slowest 80% sqls.
*/
private static final String SLOWEST80_LINK = "slowest80Link";
/**
* Link for slowest 20% sqls.
*/
private static final String SLOWEST20_LINK = "slowest20Link";
/**
* Slowest 80/20 string.
*/
private static final String SLOWEST_80_20 = "Slowest 80%/20%:";
/**
* SQLs duration in invocation string.
*/
private static final String SQLS_DURATION_IN_INVOCATION = "SQLs duration in invocation:";
/**
* Total duration string.
*/
private static final String TOTAL_DURATION = "Total duration:";
/**
* Total SQLs string.
*/
private static final String TOTAL_SQLS = "Total SQLs:";
/**
* Reset link to be added to the slowest 80/20 count when selection is active.
*/
private static final String RESET_LINK = "<a href=\"reset\">[RESET]</a>";
/**
* System {@link SWT#COLOR_DARK_GREEN} color.
*/
private static final RGB GREEN_RGB;
/**
* System {@link SWT#COLOR_DARK_YELLOW} color.
*/
private static final RGB YELLOW_RGB;
/**
* System {@link SWT#COLOR_RED} color.
*/
private static final RGB RED_RGB;
/**
* Local resource manager for color creation.
*/
private ResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources());
/**
* Main composite.
*/
private Composite main;
/**
* Total count SQL field.
*/
private FormText totalSql;
/**
* Total SQL duration field.
*/
private FormText totalDuration;
/**
* Percentage in invocation.
*/
private FormText percentageOfDuration;
/**
* Slowest 80%/20% count.
*/
private FormText slowestCount;
/**
* HYperlink settings so that we can change the link color.
*/
private HyperlinkSettings slowestHyperlinkSettings;
/**
* List that will be passed to display slowest 80%.
*/
private Collection<SqlStatementData> slowest80List = new ArrayList<>();
/**
* List that will be passed to display slowest 20%.
*/
private Collection<SqlStatementData> slowest20List = new ArrayList<>();
/**
* Keep the source invocations.
*/
private List<InvocationSequenceData> sourceInvocations;
/**
* Content displayed in slowest 80/20 without the link.
*/
private String slowestContent;
/**
* If reset link is displayed.
*/
private boolean resetDisplayed;
static {
Display display = Display.getDefault();
GREEN_RGB = display.getSystemColor(SWT.COLOR_DARK_GREEN).getRGB();
YELLOW_RGB = display.getSystemColor(SWT.COLOR_DARK_YELLOW).getRGB();
RED_RGB = display.getSystemColor(SWT.COLOR_RED).getRGB();
}
/**
* {@inheritDoc}
*/
@Override
public void createPartControl(Composite parent, FormToolkit toolkit) {
main = toolkit.createComposite(parent, SWT.BORDER);
main.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
GridLayout gl = new GridLayout(8, false);
main.setLayout(gl);
toolkit.createLabel(main, null).setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE));
totalSql = toolkit.createFormText(main, false);
totalSql.setToolTipText("Total amount of SQL Statements executed in the invocation");
totalSql.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
toolkit.createLabel(main, null).setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_TIME));
totalDuration = toolkit.createFormText(main, false);
totalDuration.setToolTipText("Duration sum of all SQL Statements executed in the invocation");
totalDuration.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
toolkit.createLabel(main, null).setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INVOCATION));
percentageOfDuration = toolkit.createFormText(main, false);
percentageOfDuration.setToolTipText("Percentage of the time spent in the invocation on SQL Statements execution");
percentageOfDuration.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
toolkit.createLabel(main, null).setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_HELP));
slowestCount = toolkit.createFormText(main, false);
slowestCount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
slowestCount.setToolTipText("Amount of slowest SQL Statements that take 80%/20% time of total SQL execution duration");
// remove left and right margins from the parent
Layout parentLayout = parent.getLayout();
if (parentLayout instanceof GridLayout) {
((GridLayout) parentLayout).marginWidth = 0;
((GridLayout) parentLayout).marginHeight = 0;
}
setDefaultText();
slowestHyperlinkSettings = new HyperlinkSettings(parent.getDisplay());
slowestHyperlinkSettings.setHyperlinkUnderlineMode(HyperlinkSettings.UNDERLINE_HOVER);
slowestCount.setHyperlinkSettings(slowestHyperlinkSettings);
slowestCount.addHyperlinkListener(getHyperlinkAdapter());
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public void setDataInput(List<? extends Object> data) {
if (CollectionUtils.isNotEmpty(data)) {
Object object = data.get(0);
if (object instanceof InvocationSequenceData) {
updateRepresentation((List<InvocationSequenceData>) data);
}
} else {
setDefaultText();
}
}
/**
* Updates the representation of the text form.
*
* @param invocations
* Invocations to display.
*/
@SuppressWarnings("unchecked")
private void updateRepresentation(List<InvocationSequenceData> invocations) {
sourceInvocations = invocations;
resetDisplayed = false;
MutableDouble duration = new MutableDouble(0d);
List<SqlStatementData> sqlList = new ArrayList<>();
InvocationSequenceDataHelper.collectSqlsInInvocations(invocations, sqlList, duration);
double totalInvocationsDuration = 0d;
for (InvocationSequenceData inv : invocations) {
totalInvocationsDuration += inv.getDuration();
}
double percentage = (duration.toDouble() / totalInvocationsDuration) * 100;
slowest80List.clear();
int slowest80 = getSlowestSqlCount(duration.toDouble(), sqlList, 0.8d, slowest80List);
int slowest20 = sqlList.size() - slowest80;
slowest20List = CollectionUtils.subtract(sqlList, slowest80List);
totalSql.setText("<form><p><b>" + TOTAL_SQLS + "</b> " + sqlList.size() + "</p></form>", true, false);
totalDuration.setText("<form><p><b>" + TOTAL_DURATION + "</b> " + NumberFormatter.formatDouble(duration.doubleValue()) + " ms</p></form>", true, false);
String formatedPercentage = NumberFormatter.formatDouble(percentage, 1);
if (CollectionUtils.isNotEmpty(sqlList)) {
Color durationInInvocationColor = ColorFormatter.getPerformanceColor(GREEN_RGB, YELLOW_RGB, RED_RGB, percentage, 20d, 80d, resourceManager);
percentageOfDuration.setColor("durationInInvocationColor", durationInInvocationColor);
percentageOfDuration.setText("<form><p><b>" + SQLS_DURATION_IN_INVOCATION + "</b> <span color=\"durationInInvocationColor\">" + formatedPercentage + "%</span></p></form>", true, false);
} else {
percentageOfDuration.setText("<form><p><b>" + SQLS_DURATION_IN_INVOCATION + "</b> " + formatedPercentage + "%</p></form>", true, false);
}
String slowest80String = getCountAndPercentage(slowest80, sqlList.size());
String slowest20String = getCountAndPercentage(slowest20, sqlList.size());
if (CollectionUtils.isNotEmpty(sqlList)) {
double slowest80Percentage = ((double) slowest80 / sqlList.size()) * 100;
if (Double.isNaN(slowest80Percentage)) {
slowest80Percentage = 0;
}
Color color8020 = ColorFormatter.getPerformanceColor(GREEN_RGB, YELLOW_RGB, RED_RGB, slowest80Percentage, 70d, 10d, resourceManager);
slowestCount.setColor(SLOWEST8020_COLOR, color8020);
slowestHyperlinkSettings.setForeground(color8020);
StringBuilder text = new StringBuilder("<b>" + SLOWEST_80_20 + "</b> ");
if (slowest80 > 0) {
text.append("<a href=\"" + SLOWEST80_LINK + "\">" + slowest80String + "</a>");
} else {
text.append("<span color=\"" + SLOWEST8020_COLOR + "\">" + slowest80String + "</span>");
}
text.append(" / ");
if (slowest20 > 0) {
text.append("<a href=\"" + SLOWEST20_LINK + "\">" + slowest20String + "</a>");
} else {
text.append("<span color=\"" + SLOWEST8020_COLOR + "\">" + slowest20String + "</span>");
}
slowestContent = text.toString();
} else {
slowestContent = "<b>" + SLOWEST_80_20 + "</b> " + slowest80String + " / " + slowest20String;
}
slowestCount.setText("<form><p>" + slowestContent + "</p></form>", true, false);
main.layout();
}
/**
* Returns the {@link HyperlinkAdapter} to handle the Hyperlink clicks.
*
* @return Returns the {@link HyperlinkAdapter} to handle the Hyperlink clicks.
*/
private HyperlinkAdapter getHyperlinkAdapter() {
return new HyperlinkAdapter() {
@Override
public void linkActivated(final HyperlinkEvent e) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
IRootEditor rootEditor = (IRootEditor) page.getActiveEditor();
if (SLOWEST80_LINK.equals(e.getHref())) {
rootEditor.setDataInput(new ArrayList<>(slowest80List));
showResetFor8020(true);
} else if (SLOWEST20_LINK.equals(e.getHref())) {
rootEditor.setDataInput(new ArrayList<>(slowest20List));
showResetFor8020(true);
} else {
rootEditor.setDataInput(sourceInvocations);
showResetFor8020(false);
}
}
});
}
};
}
/**
* Define if reset button should be displayed in the 80/20 test.
*
* @param show
* If <code>true</code> reset link will be show, otherwise hidden.
*/
private void showResetFor8020(boolean show) {
if (show && !resetDisplayed) {
resetDisplayed = true;
slowestCount.setText("<form><p>" + slowestContent + " " + RESET_LINK + "</p></form>", true, false);
} else if (!show && resetDisplayed) {
resetDisplayed = false;
slowestCount.setText("<form><p>" + slowestContent + "</p></form>", true, false);
}
}
/**
* Sets default text that has no informations displayed.
*/
private void setDefaultText() {
resetDisplayed = false;
totalSql.setText("<form><p><b>" + TOTAL_SQLS + "</b></p></form>", true, false);
totalDuration.setText("<form><p><b>" + TOTAL_DURATION + "</b></p></form>", true, false);
percentageOfDuration.setText("<form><p><b>" + SQLS_DURATION_IN_INVOCATION + "</b></p></form>", true, false);
slowestCount.setText("<form><p><b>" + SLOWEST_80_20 + "</b></p></form>", true, false);
}
/**
* Returns string representation of count and percentage.
*
* @param count
* Count.
* @param totalCount
* Total count.
* @return {@link String} representation.
*/
private String getCountAndPercentage(int count, int totalCount) {
if (0 == totalCount) {
return "0(0%)";
}
return count + "(" + NumberFormatter.formatDouble(((double) count / totalCount) * 100, 0) + "%)";
}
/**
* Calculates how much slowest SQL can fit into the given percentage of total duration.
*
* @param totalDuration
* Total duration of all SQLs.
* @param sqlStatementDataList
* List of SQL. Note that there is a side effect of list sorting.
* @param percentage
* Wanted percentages to be calculated.
* @param resultList
* List to add the resulting statements to.
* @return Return the count of SQL.
*/
private int getSlowestSqlCount(double totalDuration, List<SqlStatementData> sqlStatementDataList, double percentage, Collection<SqlStatementData> resultList) {
// sort first
Collections.sort(sqlStatementDataList, new Comparator<SqlStatementData>() {
@Override
public int compare(SqlStatementData o1, SqlStatementData o2) {
return ObjectUtils.compare(o2.getDuration(), o1.getDuration());
}
});
int result = 0;
double currentDurationSum = 0;
for (SqlStatementData sqlStatementData : sqlStatementDataList) {
if ((currentDurationSum / totalDuration) < percentage) {
result++;
resultList.add(sqlStatementData);
} else {
break;
}
currentDurationSum += sqlStatementData.getDuration();
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
resourceManager.dispose();
super.dispose();
}
}