@@ -477,7 +477,33 @@ | |||
</plugins> | |||
</build> | |||
</profile> | |||
<profile> | |||
<!--TODO make those tests compatible with Java 10--> | |||
<id>java10</id> | |||
<activation> | |||
<jdk>10</jdk> | |||
</activation> | |||
<build> | |||
<plugins> | |||
<plugin> | |||
<artifactId>maven-compiler-plugin</artifactId> | |||
<configuration> | |||
<excludes> | |||
<exclude>com/vaadin/tests/performance/AbstractBeansMemoryTest.java</exclude> | |||
<exclude>com/vaadin/tests/performance/CompatibilityGridMemory.java</exclude> | |||
<exclude>com/vaadin/tests/performance/GridMemory.java</exclude> | |||
<exclude>com/vaadin/tests/performance/ThreadMemoryLeaksTest.java</exclude> | |||
<exclude>com/vaadin/tests/performance/TreeGridMemory.java</exclude> | |||
<exclude>com/vaadin/tests/performance/TreeTableMemory.java</exclude> | |||
</excludes> | |||
<testExcludes> | |||
<exclude>com/vaadin/tests/performance/MemoryIT.java</exclude> | |||
</testExcludes> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</profile> | |||
</profiles> | |||
</project> |
@@ -0,0 +1,201 @@ | |||
package com.vaadin.tests.performance; | |||
import java.lang.reflect.Field; | |||
import java.math.BigDecimal; | |||
import java.util.Collections; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.Random; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.IntStream; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.data.bean.Address; | |||
import com.vaadin.tests.data.bean.Country; | |||
import com.vaadin.tests.data.bean.Person; | |||
import com.vaadin.tests.data.bean.Sex; | |||
import com.vaadin.ui.AbstractComponent; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Component; | |||
import com.vaadin.ui.HasComponents; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.MenuBar; | |||
import com.vaadin.ui.MenuBar.MenuItem; | |||
import com.vaadin.ui.UI; | |||
import com.vaadin.ui.VerticalLayout; | |||
import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator; | |||
/** | |||
* @author Vaadin Ltd | |||
* | |||
*/ | |||
public abstract class AbstractBeansMemoryTest<T extends AbstractComponent> | |||
extends UI { | |||
private int dataSize; | |||
private boolean isInMemory; | |||
private boolean isDataOnly; | |||
private Label logLabel; | |||
private Label memoryLabel; | |||
@Override | |||
protected void init(VaadinRequest request) { | |||
String items = request.getParameter("items"); | |||
int itemsCount = 1; | |||
if (items != null) { | |||
itemsCount = Integer.parseInt(items); | |||
} | |||
VerticalLayout layout = new VerticalLayout(); | |||
setContent(layout); | |||
layout.addComponent(new Label(getClass().getSimpleName())); | |||
memoryLabel = new Label(); | |||
memoryLabel.setId("memory"); | |||
layout.addComponent(memoryLabel); | |||
logLabel = new Label(); | |||
logLabel.setId("log"); | |||
layout.addComponent(logLabel); | |||
T component = createComponent(); | |||
setData(null, itemsCount, component, true); | |||
layout.addComponent(createMenu(component)); | |||
layout.addComponent(component); | |||
Button close = new Button("Close UI", event -> close()); | |||
close.setId("close"); | |||
layout.addComponent(close); | |||
} | |||
protected abstract T createComponent(); | |||
private Random random = new Random(); | |||
protected List<Person> createBeans(int size) { | |||
return IntStream.range(0, size).mapToObj(this::createPerson) | |||
.collect(Collectors.toList()); | |||
} | |||
protected Person createPerson(int index) { | |||
random.setSeed(index); | |||
Person person = new Person(); | |||
person.setFirstName("First Name " + random.nextInt()); | |||
person.setLastName("Last Name " + random.nextInt()); | |||
person.setAge(random.nextInt()); | |||
person.setBirthDate(new Date(random.nextLong())); | |||
person.setDeceased(random.nextBoolean()); | |||
person.setEmail(random.nextInt() + "user@example.com"); | |||
person.setRent(new BigDecimal(random.nextLong())); | |||
person.setSalary(random.nextInt()); | |||
person.setSalaryDouble(random.nextDouble()); | |||
person.setSex(Sex.values()[random.nextInt(Sex.values().length)]); | |||
Address address = new Address(); | |||
person.setAddress(address); | |||
address.setCity("city " + random.nextInt()); | |||
address.setPostalCode(random.nextInt()); | |||
address.setStreetAddress("street address " + random.nextInt()); | |||
address.setCountry( | |||
Country.values()[random.nextInt(Country.values().length)]); | |||
return person; | |||
} | |||
protected abstract void setInMemoryContainer(T component, | |||
List<Person> data); | |||
protected abstract void setBackendContainer(T component, List<Person> data); | |||
@Override | |||
public VerticalLayout getContent() { | |||
return (VerticalLayout) super.getContent(); | |||
} | |||
@SuppressWarnings("restriction") | |||
private void setData(MenuItem item, int size, T component, | |||
boolean memoryContainer) { | |||
if (item != null) { | |||
MenuItem parent = item.getParent(); | |||
parent.getChildren().stream().filter(itm -> !itm.equals(item)) | |||
.forEach(itm -> itm.setChecked(false)); | |||
logLabel.setValue(item.getText()); | |||
} | |||
dataSize = size; | |||
isInMemory = memoryContainer; | |||
List<Person> persons = createBeans(size); | |||
if (isDataOnly) { | |||
component.setData(persons); | |||
persons = Collections.emptyList(); | |||
} else { | |||
component.setData(null); | |||
} | |||
if (isInMemory) { | |||
setInMemoryContainer(component, persons); | |||
} else { | |||
setBackendContainer(component, persons); | |||
} | |||
HasComponents container = component.getParent(); | |||
setParent(component, null); | |||
memoryLabel.setValue( | |||
String.valueOf(ObjectSizeCalculator.getObjectSize(component))); | |||
setParent(component, container); | |||
} | |||
private void setParent(Component component, Component parent) { | |||
try { | |||
Field field = AbstractComponent.class.getDeclaredField("parent"); | |||
field.setAccessible(true); | |||
field.set(component, parent); | |||
} catch (NoSuchFieldException | SecurityException | |||
| IllegalArgumentException | IllegalAccessException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
private Component createMenu(T component) { | |||
MenuBar menu = new MenuBar(); | |||
createContainerSizeMenu(menu.addItem("Size", null), component); | |||
createContainerMenu(menu.addItem("Data provider", null), component); | |||
menu.addItem("Create only data", | |||
item -> toggleDataOnly(item, component)).setCheckable(true); | |||
return menu; | |||
} | |||
private void toggleDataOnly(MenuItem item, T component) { | |||
isDataOnly = item.isChecked(); | |||
setData(null, dataSize, component, isInMemory); | |||
} | |||
private void createContainerMenu(MenuItem menu, T component) { | |||
MenuItem menuItem = menu.addItem("Use in-memory container", | |||
item -> setData(item, dataSize, component, true)); | |||
menuItem.setCheckable(true); | |||
menuItem.setChecked(true); | |||
menuItem = menu.addItem("Use backend container", | |||
item -> setData(item, dataSize, component, false)); | |||
menuItem.setCheckable(true); | |||
} | |||
private void createContainerSizeMenu(MenuItem menu, T component) { | |||
List<MenuItem> items = IntStream.of(1, 10000, 100000, 500000, 1000000) | |||
.mapToObj(size -> addContainerSizeMenu(size, menu, component)) | |||
.collect(Collectors.toList()); | |||
if (dataSize == 1) { | |||
items.get(0).setChecked(true); | |||
} | |||
} | |||
private MenuItem addContainerSizeMenu(int size, MenuItem menu, | |||
T component) { | |||
MenuItem item = menu.addItem("Set data provider size to " + size, | |||
itm -> setData(itm, size, component, isInMemory)); | |||
item.setCheckable(true); | |||
return item; | |||
} | |||
} |
@@ -0,0 +1,88 @@ | |||
package com.vaadin.tests.performance; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import java.util.function.Function; | |||
import javax.servlet.annotation.WebServlet; | |||
import com.vaadin.annotations.VaadinServletConfiguration; | |||
import com.vaadin.server.VaadinServlet; | |||
import com.vaadin.tests.data.bean.Address; | |||
import com.vaadin.tests.data.bean.Person; | |||
import com.vaadin.v7.data.Item; | |||
import com.vaadin.v7.data.util.BeanItemContainer; | |||
import com.vaadin.v7.data.util.GeneratedPropertyContainer; | |||
import com.vaadin.v7.data.util.PropertyValueGenerator; | |||
import com.vaadin.v7.ui.Grid; | |||
/** | |||
* @author Vaadin Ltd | |||
* | |||
*/ | |||
public class CompatibilityGridMemory extends AbstractBeansMemoryTest<Grid> { | |||
public static final String PATH = "/grid-compatibility-memory/"; | |||
/** | |||
* The main servlet for the application. | |||
*/ | |||
@WebServlet(urlPatterns = PATH | |||
+ "*", name = "CompatibilityGridServlet", asyncSupported = true) | |||
@VaadinServletConfiguration(ui = CompatibilityGridMemory.class, productionMode = false, widgetset = "com.vaadin.v7.Vaadin7WidgetSet") | |||
public static class Servlet extends VaadinServlet { | |||
} | |||
private static class ColumnGenerator | |||
extends PropertyValueGenerator<String> { | |||
private final Function<Address, Object> getter; | |||
ColumnGenerator(Function<Address, Object> getter) { | |||
this.getter = getter; | |||
} | |||
@Override | |||
public String getValue(Item item, Object itemId, Object propertyId) { | |||
Address address = ((Person) itemId).getAddress(); | |||
return Optional.ofNullable(address).map(getter) | |||
.map(Object::toString).orElse(null); | |||
} | |||
@Override | |||
public Class<String> getType() { | |||
return String.class; | |||
} | |||
} | |||
@Override | |||
protected Grid createComponent() { | |||
return new Grid(); | |||
} | |||
@Override | |||
protected void setInMemoryContainer(Grid grid, List<Person> persons) { | |||
BeanItemContainer<Person> container = new BeanItemContainer<>( | |||
Person.class); | |||
container.addAll(persons); | |||
GeneratedPropertyContainer generated = new GeneratedPropertyContainer( | |||
container); | |||
generated.addGeneratedProperty("street", | |||
new ColumnGenerator(Address::getStreetAddress)); | |||
generated.addGeneratedProperty("zip", | |||
new ColumnGenerator(Address::getPostalCode)); | |||
generated.addGeneratedProperty("city", | |||
new ColumnGenerator(Address::getCity)); | |||
grid.setContainerDataSource(generated); | |||
configureColumns(grid); | |||
} | |||
@Override | |||
protected void setBackendContainer(Grid component, List<Person> data) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
private void configureColumns(Grid grid) { | |||
grid.setColumns("firstName", "lastName", "street", "zip", "city"); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
package com.vaadin.tests.performance; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import javax.servlet.annotation.WebServlet; | |||
import com.vaadin.annotations.VaadinServletConfiguration; | |||
import com.vaadin.server.VaadinServlet; | |||
import com.vaadin.tests.data.bean.Address; | |||
import com.vaadin.tests.data.bean.Person; | |||
import com.vaadin.ui.Grid; | |||
/** | |||
* @author Vaadin Ltd | |||
* | |||
*/ | |||
public class GridMemory extends AbstractBeansMemoryTest<Grid<Person>> { | |||
public static final String PATH = "/grid-memory/"; | |||
/** | |||
* The main servlet for the application. | |||
*/ | |||
@WebServlet(urlPatterns = PATH | |||
+ "*", name = "GridServlet", asyncSupported = true) | |||
@VaadinServletConfiguration(ui = GridMemory.class, productionMode = false) | |||
public static class Servlet extends VaadinServlet { | |||
} | |||
@Override | |||
protected Grid<Person> createComponent() { | |||
Grid<Person> grid = new Grid<>(); | |||
grid.addColumn(Person::getFirstName).setCaption("First Name"); | |||
grid.addColumn(Person::getLastName).setCaption("Last Name"); | |||
grid.addColumn(person -> Optional.ofNullable(person.getAddress()) | |||
.map(Address::getStreetAddress).orElse(null)) | |||
.setCaption("Street"); | |||
grid.addColumn(person -> Optional.ofNullable(person.getAddress()) | |||
.map(Address::getPostalCode).map(Object::toString).orElse("")) | |||
.setCaption("Zip"); | |||
grid.addColumn(person -> Optional.ofNullable(person.getAddress()) | |||
.map(Address::getCity).orElse(null)).setCaption("City"); | |||
return grid; | |||
} | |||
@Override | |||
protected void setInMemoryContainer(Grid<Person> grid, | |||
List<Person> persons) { | |||
grid.setItems(persons); | |||
} | |||
@Override | |||
protected void setBackendContainer(Grid<Person> component, | |||
List<Person> data) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
} |
@@ -0,0 +1,54 @@ | |||
package com.vaadin.tests.performance; | |||
import java.util.Date; | |||
import java.util.Timer; | |||
import java.util.TimerTask; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractReindeerTestUI; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Label; | |||
public class ThreadMemoryLeaksTest extends AbstractReindeerTestUI { | |||
public static class Worker { | |||
long value = 0; | |||
private TimerTask task = new TimerTask() { | |||
@Override | |||
public void run() { | |||
value++; | |||
} | |||
}; | |||
private final Timer timer = new Timer(true); | |||
public Worker() { | |||
timer.scheduleAtFixedRate(task, new Date(), 1000); | |||
} | |||
} | |||
int workers = 0; | |||
Label label; | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
label = new Label(String.format("%d workers", workers)); | |||
addComponent(label); | |||
addComponent(new Button("Add worker", event -> { | |||
new Worker(); | |||
workers++; | |||
label.setValue(String.format("%d workers", workers)); | |||
})); | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return "Inherited ThreadLocals should not leak memory. Clicking the " | |||
+ "button starts a new thread, after which memory consumption " | |||
+ "can be checked with visualvm"; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 12401; | |||
} | |||
} |
@@ -0,0 +1,83 @@ | |||
package com.vaadin.tests.performance; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import javax.servlet.annotation.WebServlet; | |||
import com.vaadin.annotations.VaadinServletConfiguration; | |||
import com.vaadin.data.TreeData; | |||
import com.vaadin.data.provider.TreeDataProvider; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.server.VaadinServlet; | |||
import com.vaadin.tests.data.bean.Address; | |||
import com.vaadin.tests.data.bean.Person; | |||
import com.vaadin.ui.TreeGrid; | |||
public class TreeGridMemory extends AbstractBeansMemoryTest<TreeGrid<Person>> { | |||
public static final String PATH = "/tree-grid-memory/"; | |||
@WebServlet(urlPatterns = PATH | |||
+ "*", name = "TreeGridServlet", asyncSupported = true) | |||
@VaadinServletConfiguration(ui = TreeGridMemory.class, productionMode = false) | |||
public static class Servlet extends VaadinServlet { | |||
} | |||
private boolean initiallyExpanded = false; | |||
@Override | |||
protected void init(VaadinRequest request) { | |||
if (request.getParameter("initiallyExpanded") != null) { | |||
initiallyExpanded = true; | |||
} | |||
super.init(request); | |||
} | |||
@Override | |||
protected TreeGrid<Person> createComponent() { | |||
TreeGrid<Person> treeGrid = new TreeGrid<>(); | |||
treeGrid.addColumn(Person::getFirstName).setCaption("First Name"); | |||
treeGrid.addColumn(Person::getLastName).setCaption("Last Name"); | |||
treeGrid.addColumn(person -> Optional.ofNullable(person.getAddress()) | |||
.map(Address::getStreetAddress).orElse(null)) | |||
.setCaption("Street"); | |||
treeGrid.addColumn(person -> Optional.ofNullable(person.getAddress()) | |||
.map(Address::getPostalCode).map(Object::toString).orElse("")) | |||
.setCaption("Zip"); | |||
treeGrid.addColumn(person -> Optional.ofNullable(person.getAddress()) | |||
.map(Address::getCity).orElse(null)).setCaption("City"); | |||
return treeGrid; | |||
} | |||
@Override | |||
protected void setInMemoryContainer(TreeGrid<Person> treeGrid, | |||
List<Person> data) { | |||
TreeData<Person> treeData = new TreeData<>(); | |||
treeGrid.setDataProvider(new TreeDataProvider<>(treeData)); | |||
List<Person> toExpand = new ArrayList<>(); | |||
if (data.size() != 0 && data.size() % 2 == 0) { | |||
// treat list as if it were a balanced binary tree | |||
treeData.addItem(null, data.get(0)); | |||
int n = 0; | |||
while (2 * n + 2 < data.size()) { | |||
treeData.addItems(data.get(n), | |||
data.subList(2 * n + 1, 2 * n + 3)); | |||
toExpand.add(data.get(n)); | |||
n++; | |||
} | |||
} else { | |||
treeData.addItems(null, data); | |||
} | |||
if (initiallyExpanded) { | |||
treeGrid.expand(toExpand); | |||
} | |||
} | |||
@Override | |||
protected void setBackendContainer(TreeGrid<Person> component, | |||
List<Person> data) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
} |
@@ -0,0 +1,98 @@ | |||
package com.vaadin.tests.performance; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import javax.servlet.annotation.WebServlet; | |||
import com.vaadin.annotations.VaadinServletConfiguration; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.server.VaadinServlet; | |||
import com.vaadin.tests.data.bean.Address; | |||
import com.vaadin.tests.data.bean.Person; | |||
import com.vaadin.v7.data.Item; | |||
import com.vaadin.v7.data.util.HierarchicalContainer; | |||
import com.vaadin.v7.ui.TreeTable; | |||
public class TreeTableMemory extends AbstractBeansMemoryTest<TreeTable> { | |||
public static final String PATH = "/tree-table-memory/"; | |||
@WebServlet(urlPatterns = PATH | |||
+ "*", name = "TreeTableServlet", asyncSupported = true) | |||
@VaadinServletConfiguration(ui = TreeTableMemory.class, productionMode = false, widgetset = "com.vaadin.v7.Vaadin7WidgetSet") | |||
public static class Servlet extends VaadinServlet { | |||
} | |||
private boolean initiallyExpanded = false; | |||
@Override | |||
protected void init(VaadinRequest request) { | |||
if (request.getParameter("initiallyExpanded") != null) { | |||
initiallyExpanded = true; | |||
} | |||
super.init(request); | |||
} | |||
@Override | |||
protected TreeTable createComponent() { | |||
TreeTable treeTable = new TreeTable(); | |||
return treeTable; | |||
} | |||
@Override | |||
protected void setInMemoryContainer(TreeTable treeTable, | |||
List<Person> data) { | |||
HierarchicalContainer container = new HierarchicalContainer(); | |||
container.addContainerProperty("firstName", String.class, null); | |||
container.addContainerProperty("lastName", String.class, null); | |||
container.addContainerProperty("street", String.class, null); | |||
container.addContainerProperty("zip", String.class, null); | |||
container.addContainerProperty("city", String.class, null); | |||
treeTable.setContainerDataSource(container); | |||
if (data.size() != 0 && data.size() % 2 == 0) { | |||
createItem(0, container, data); | |||
treeTable.setCollapsed(0, false); | |||
int n = 0; | |||
while (2 * n + 2 < data.size()) { | |||
for (int i : new Integer[] { 1, 2 }) { | |||
createItem(2 * n + i, container, data); | |||
container.setParent(2 * n + i, n); | |||
if (initiallyExpanded) { | |||
treeTable.setCollapsed(2 * n + i, false); | |||
} | |||
} | |||
n++; | |||
} | |||
} else { | |||
for (int i = 0; i < data.size(); i++) { | |||
createItem(i, container, data); | |||
} | |||
} | |||
} | |||
private void createItem(int index, HierarchicalContainer container, | |||
List<Person> data) { | |||
Item item = container.addItem(index); | |||
item.getItemProperty("firstName") | |||
.setValue(data.get(index).getFirstName()); | |||
item.getItemProperty("lastName") | |||
.setValue(data.get(index).getLastName()); | |||
item.getItemProperty("street") | |||
.setValue(Optional.ofNullable(data.get(index).getAddress()) | |||
.map(Address::getStreetAddress).orElse(null)); | |||
item.getItemProperty("zip") | |||
.setValue(Optional.ofNullable(data.get(index).getAddress()) | |||
.map(Address::getPostalCode).map(Object::toString) | |||
.orElse("")); | |||
item.getItemProperty("city") | |||
.setValue(Optional.ofNullable(data.get(index).getAddress()) | |||
.map(Address::getCity).orElse(null)); | |||
} | |||
@Override | |||
protected void setBackendContainer(TreeTable component, List<Person> data) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
} |
@@ -2,6 +2,10 @@ package com.vaadin.tests.components.treegrid; | |||
import static org.junit.Assert.assertEquals; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.hamcrest.core.StringStartsWith; | |||
import org.junit.Assume; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
@@ -63,6 +67,26 @@ public class TreeGridHugeTreeTest extends SingleBrowserTest { | |||
new String[] { "Granddad 0", "Dad 0/0", "Son 0/0/0" }); | |||
} | |||
@Test | |||
public void collapsed_subtrees_outside_of_cache_stay_expanded() { | |||
Assume.assumeThat(System.getProperty("java.specification.version"), | |||
new StringStartsWith("1.8")); | |||
getDriver().get(StringUtils.strip(getBaseURL(), "/") | |||
+ "/tree-grid-memory/?items=200&initiallyExpanded"); | |||
grid = $(TreeGridElement.class).first(); | |||
String[] cellTexts = new String[100]; | |||
for (int i = 0; i < 100; i++) { | |||
cellTexts[i] = grid.getRow(i).getCell(0).getText(); | |||
} | |||
grid.scrollToRow(0); | |||
grid.collapseWithClick(1); | |||
grid.expandWithClick(1); | |||
assertCellTexts(0, 0, cellTexts); | |||
} | |||
private void assertCellTexts(int startRowIndex, int cellIndex, | |||
String[] cellTexts) { | |||
int index = startRowIndex; |
@@ -0,0 +1,117 @@ | |||
package com.vaadin.tests.performance; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.junit.Test; | |||
import org.junit.experimental.categories.Category; | |||
import com.vaadin.testbench.By; | |||
import com.vaadin.testcategory.MeasurementTest; | |||
import com.vaadin.tests.tb3.SingleBrowserTest; | |||
@Category(MeasurementTest.class) | |||
public class MemoryIT extends SingleBrowserTest { | |||
private static final int MAX_ITERATIONS = 20; | |||
@Test | |||
public void measureMemory() { | |||
performTest(GridMemory.PATH + "?items=1", "grid-v8-one-item-"); | |||
performTest(CompatibilityGridMemory.PATH + "?items=1", | |||
"grid-v7-one-item-"); | |||
performTest(GridMemory.PATH + "?items=1", "grid-v8-100thousand-items-"); | |||
performTest(CompatibilityGridMemory.PATH + "?items=100000", | |||
"grid-v7-100thousand-items-"); | |||
performTest(TreeGridMemory.PATH + "?items=1", "tree-grid-one-item-"); | |||
performTest(TreeTableMemory.PATH + "?items=1", "tree-table-one-item-"); | |||
performTest(TreeGridMemory.PATH + "?items=100&initiallyExpanded", | |||
"tree-grid-100-items-initially-expanded-"); | |||
performTest(TreeTableMemory.PATH + "?items=100&initiallyExpanded", | |||
"tree-table-100-items-initially-expanded-"); | |||
performTest(TreeGridMemory.PATH + "?items=100000", | |||
"tree-grid-100thousand-items-"); | |||
performTest(TreeTableMemory.PATH + "?items=100000", | |||
"tree-table-100thousand-items-"); | |||
} | |||
@Override | |||
protected void closeApplication() { | |||
} | |||
private void performTest(String path, String teamcityStatPrefix) { | |||
double lastResult = 0; | |||
int stableNumber = 0; | |||
List<Long> renderingTimes = new ArrayList<>(); | |||
List<Long> requestTimes = new ArrayList<>(); | |||
for (int i = 0; i < MAX_ITERATIONS; i++) { | |||
openUI(path); | |||
renderingTimes.add(testBench().totalTimeSpentRendering()); | |||
requestTimes.add(testBench().totalTimeSpentServicingRequests()); | |||
long currentResult = Long | |||
.parseLong(findElement(By.id("memory")).getText()); | |||
close(); | |||
if (approx(lastResult, currentResult, 0.001)) { | |||
stableNumber++; | |||
} | |||
lastResult = currentResult; | |||
if (stableNumber == 5) { | |||
System.out.println( | |||
"Memory usage stabilized after " + i + " iterations"); | |||
printTeamcityStats(teamcityStatPrefix + "size", currentResult); | |||
printTeamcityStats(teamcityStatPrefix + "rendering-time", | |||
median(renderingTimes)); | |||
printTeamcityStats(teamcityStatPrefix + "request-time", | |||
median(requestTimes)); | |||
return; | |||
} | |||
if (i == MAX_ITERATIONS) { | |||
fail("Memory size does not stabilize"); | |||
} | |||
} | |||
} | |||
private long median(List<Long> values) { | |||
values.sort(Long::compareTo); | |||
int middle = values.size() / 2; | |||
if (values.size() % 2 == 1) { | |||
return values.get(middle); | |||
} else { | |||
return (values.get(middle - 1) + values.get(middle)) / 2; | |||
} | |||
} | |||
private boolean approx(double num1, double num2, double epsilon) { | |||
double delta = Math.abs(num1 - num2); | |||
double deltaLimit = num2 * epsilon; | |||
return delta < deltaLimit; | |||
} | |||
private void openUI(String path) { | |||
getDriver().get(StringUtils.strip(getBaseURL(), "/") + path); | |||
assertTrue(isElementPresent(By.className("v-grid")) | |||
|| isElementPresent(By.className("v-treegrid")) | |||
|| isElementPresent(By.className("v-table"))); | |||
} | |||
private void close() { | |||
findElement(By.id("close")).click(); | |||
} | |||
private void printTeamcityStats(String key, long value) { | |||
// ##teamcity[buildStatisticValue key='<valueTypeKey>' | |||
// value='<value>'] | |||
System.out.println("##teamcity[buildStatisticValue key='" + key | |||
+ "' value='" + value + "']"); | |||
} | |||
} |