Skip to content

Commit

Permalink
when sorting nodes for consideration, also take into account last tim…
Browse files Browse the repository at this point in the history
…e accessed for a more even distribution across nodes over the lifetime of the grid.

Also fix using the RemoteProxy's getResourceUsageInPercent instead of calculating it in the sort.

Fixes #1673
  • Loading branch information
lukeis committed Feb 24, 2016
1 parent d2a55fd commit a74cfe8
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 6 deletions.
8 changes: 8 additions & 0 deletions java/server/src/org/openqa/grid/internal/BaseRemoteProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -563,4 +563,12 @@ private JsonObject extractObject(HttpResponse resp) throws IOException {
public float getResourceUsageInPercent() {
return 100 * (float)getTotalUsed() / (float)getMaxNumberOfConcurrentTestSessions();
}

public long getLastSessionStart() {
long last = -1;
for (TestSlot slot : testSlots) {
last = Math.max(last, slot.getLastSessionStart());
}
return last;
}
}
13 changes: 9 additions & 4 deletions java/server/src/org/openqa/grid/internal/ProxySet.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,15 @@ public List<RemoteProxy> getSorted() {
private Comparator<RemoteProxy> proxyComparator = new Comparator<RemoteProxy>() {
@Override
public int compare(RemoteProxy o1, RemoteProxy o2) {
double p1used = (o1.getTotalUsed() * 1.0) / o1.getTestSlots().size();
double p2used = (o2.getTotalUsed() * 1.0) / o2.getTestSlots().size();

if (p1used == p2used) return 0;
double p1used = o1.getResourceUsageInPercent();
double p2used = o2.getResourceUsageInPercent();

if (p1used == p2used) {
long time1lastUsed = o1.getLastSessionStart();
long time2lastUsed = o2.getLastSessionStart();
if (time1lastUsed == time2lastUsed) return 0;
return time1lastUsed < time2lastUsed? -1 : 1;
}
return p1used < p2used? -1 : 1;
}
};
Expand Down
5 changes: 5 additions & 0 deletions java/server/src/org/openqa/grid/internal/RemoteProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,9 @@ public interface RemoteProxy extends Comparable<RemoteProxy> {
* @return the percentage of the available resource used. Can be greater than 100 if the grid is under heavy load.
*/
float getResourceUsageInPercent();

/**
* @return the time the latest session was started on a TestSlot, -1 if no sessions were started.
*/
long getLastSessionStart();
}
10 changes: 10 additions & 0 deletions java/server/src/org/openqa/grid/internal/TestSlot.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.openqa.grid.internal.listeners.TestSessionListener;
import org.openqa.grid.internal.utils.CapabilityMatcher;
import org.openqa.grid.internal.utils.GridHubConfiguration;
import org.openqa.selenium.remote.server.SystemClock;

import java.net.MalformedURLException;
import java.net.URL;
Expand Down Expand Up @@ -61,6 +62,7 @@ public class TestSlot {
private volatile TestSession currentSession;
volatile boolean beingReleased = false;
private boolean showWarning = false;
private long lastSessionStart = -1;


public TestSlot(RemoteProxy proxy, SeleniumProtocol protocol, String path,
Expand Down Expand Up @@ -110,6 +112,7 @@ public TestSession getNewSession(Map<String, Object> desiredCapabilities) {
log.info("Trying to create a new session on test slot " + this.capabilities);
TestSession session = new TestSession(this, desiredCapabilities, new DefaultTimeSource());
currentSession = session;
lastSessionStart = System.currentTimeMillis();
return session;
} else {
return null;
Expand Down Expand Up @@ -247,4 +250,11 @@ public URL getRemoteURL() {
throw new GridException("Configuration error for the node." + u + " isn't a valid URL");
}
}

/**
* @return System.currentTimeMillis() of when the session was started, otherwise -1
*/
public long getLastSessionStart() {
return lastSessionStart;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
import org.openqa.selenium.server.SeleniumServer;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class GridDistributionTest {

Expand Down Expand Up @@ -114,15 +116,83 @@ public void testLoadIsDistributedEvenly() throws Throwable {
}
}

@AfterClass
public static void stop() throws Exception {
@Test
public void testLeastRecentlyUsedNodesPickedFirst() throws Throwable {
ProxySet ps = hub.getRegistry().getAllProxies();

for (int i=0; i < 4; i++) {
drivers.add(GridTestHelper.getRemoteWebDriver(DesiredCapabilities.chrome(), hub));
}

Set<String> chosenNodes = new HashSet<>();

for (RemoteProxy p : ps) {
for (TestSlot ts : p.getTestSlots()) {
if (ts.getSession() != null) {
chosenNodes.add(p.getRemoteHost().toString());
break;
}
}
}

stopDrivers(drivers);

for (int i=0; i < 4; i++) {
drivers.add(GridTestHelper.getRemoteWebDriver(DesiredCapabilities.chrome(), hub));
}

for (RemoteProxy p : ps) {
for (TestSlot ts : p.getTestSlots()) {
if (ts.getSession() != null) {
Assert.assertFalse("Should not be immediately reused: " + p.getRemoteHost().toString() + " previously used nodes: " + chosenNodes,
chosenNodes.contains(p.getRemoteHost().toString()));
break;
}
}
}

chosenNodes.clear();

for (RemoteProxy p : ps) {
for (TestSlot ts : p.getTestSlots()) {
if (ts.getSession() != null) {
chosenNodes.add(p.getRemoteHost().toString());
break;
}
}
}

stopDrivers(drivers);

for (int i=0; i < 4; i++) {
drivers.add(GridTestHelper.getRemoteWebDriver(DesiredCapabilities.chrome(), hub));
}

for (RemoteProxy p : ps) {
for (TestSlot ts : p.getTestSlots()) {
if (ts.getSession() != null) {
Assert.assertFalse("Should not be immediately reused: " + p.getRemoteHost().toString() + " previously used nodes: " + chosenNodes,
chosenNodes.contains(p.getRemoteHost().toString()));
break;
}
}
}
}

private static void stopDrivers(List<WebDriver> drivers) {
for (WebDriver driver : drivers) {
try {
driver.quit();
} catch (Exception e) {
System.out.println(e.toString());
}
}
drivers.clear();
}

@AfterClass
public static void stop() throws Exception {
stopDrivers(drivers);
hub.stop();
}
}

6 comments on commit a74cfe8

@alexkogon
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good 👍

@freynaud34
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@mach6
Copy link
Member

@mach6 mach6 commented on a74cfe8 Feb 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @lukeis

@alexkogon
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, a lot of this seems to be to support something which in my experience is an anti-pattern, which is running multiple browsers on a single node. We found this caused problems with sessions between multiple instances of the same browser, and since we like to record our sessions found that having one session per node (VM) was the best way to go (also when they become unstable to restart them with no affect on other tests running on the same node).

The Docker Selenium implementations seems to back up my experience--each container has a single browser. If you have a VM allocated with more resources, you run multiple instances of the Docker containers, but each is still its own node.

In this case using a Queue as I implemented is equivalent to this solution (and perhaps a lot easier to understand and maintain and computationally less active).

Does anyone run multiple browsers in a single Node? @freynaud what is your experience?

@freynaud34
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use multiple (30ish) browsers per node. 1 browser per node is normally what happens with the default implementation. If you create a proxy specifically for your architecture, that 1 per node "rule" goes away.

@alexkogon
Copy link

@alexkogon alexkogon commented on a74cfe8 Mar 7, 2016 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.