/*******************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi, Red Hat, Inc.
*
*
*******************************************************************************/
package hudson.node_monitors;
import hudson.Util;
import hudson.Extension;
import hudson.model.Descriptor.FormException;
import hudson.slaves.OfflineCause;
import hudson.model.Computer;
import hudson.remoting.Callable;
import hudson.remoting.Future;
import hudson.util.TimeUnit2;
import hudson.util.IOException2;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
/**
* Monitors the round-trip response time to this slave.
*
* @author Kohsuke Kawaguchi
*/
public class ResponseTimeMonitor extends NodeMonitor {
@Extension
public static final AbstractNodeMonitorDescriptor<Data> DESCRIPTOR = new AbstractNodeMonitorDescriptor<Data>() {
protected Data monitor(Computer c) throws IOException, InterruptedException {
Data old = get(c);
Data d;
long start = System.nanoTime();
Future<String> f = c.getChannel().callAsync(new NoopTask());
try {
f.get(TIMEOUT, TimeUnit.MILLISECONDS);
long end = System.nanoTime();
d = new Data(old, TimeUnit2.NANOSECONDS.toMillis(end - start));
} catch (ExecutionException e) {
throw new IOException2(e.getCause()); // I don't think this is possible
} catch (TimeoutException e) {
// special constant to indicate that the processing timed out.
d = new Data(old, -1L);
}
if (d.hasTooManyTimeouts() && !isIgnored()) {
// unlike other monitors whose failure still allow us to communicate with the slave,
// the failure in this monitor indicates that we are just unable to make any requests
// to this slave. So we should severe the connection, as opposed to marking it temporarily
// off line, which still keeps the underlying channel open.
c.disconnect(d);
LOGGER.warning(Messages.ResponseTimeMonitor_MarkedOffline(c.getName()));
}
return d;
}
public String getDisplayName() {
return Messages.ResponseTimeMonitor_DisplayName();
}
@Override
public NodeMonitor newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return new ResponseTimeMonitor();
}
};
/**
* Immutable representation of the monitoring data.
*/
@ExportedBean
public static final class Data extends OfflineCause {
/**
* Record of the past 5 times. -1 if time out. Otherwise in
* milliseconds. Old ones first.
*/
private final long[] past5;
private Data(Data old, long newDataPoint) {
if (old == null) {
past5 = new long[]{newDataPoint};
} else {
past5 = new long[Math.min(5, old.past5.length + 1)];
int copyLen = past5.length - 1;
System.arraycopy(old.past5, old.past5.length - copyLen, this.past5, 0, copyLen);
past5[past5.length - 1] = newDataPoint;
}
}
/**
* Computes the recurrence of the time out
*/
private int failureCount() {
int cnt = 0;
for (int i = past5.length - 1; i >= 0 && past5[i] < 0; i--, cnt++)
;
return cnt;
}
/**
* Computes the average response time, by taking the time out into
* account.
*/
@Exported
public long getAverage() {
long total = 0;
for (long l : past5) {
if (l < 0) {
total += TIMEOUT;
} else {
total += l;
}
}
return total / past5.length;
}
public boolean hasTooManyTimeouts() {
return failureCount() >= 5;
}
/**
* HTML rendering of the data
*/
@Override
public String toString() {
// StringBuilder buf = new StringBuilder();
// for (long l : past5) {
// if(buf.length()>0) buf.append(',');
// buf.append(l);
// }
// return buf.toString();
int fc = failureCount();
if (fc > 0) {
return Util.wrapToErrorSpan(Messages.ResponseTimeMonitor_TimeOut(fc));
}
return getAverage() + "ms";
}
}
private static class NoopTask implements Callable<String, RuntimeException> {
public String call() {
return null;
}
private static final long serialVersionUID = 1L;
}
/**
* Time out interval in milliseconds.
*/
private static final long TIMEOUT = 5000;
private static final Logger LOGGER = Logger.getLogger(ResponseTimeMonitor.class.getName());
}