/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.node_monitors; import hudson.Util; import hudson.Extension; import hudson.model.Computer; import hudson.remoting.Callable; import jenkins.security.MasterToSlaveCallable; import net.sf.json.JSONObject; import org.kohsuke.stapler.StaplerRequest; import java.io.IOException; import java.io.Serializable; import java.util.Map; import java.util.Map.Entry; 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 agent. * * @author Kohsuke Kawaguchi */ public class ResponseTimeMonitor extends NodeMonitor { @Extension public static final AbstractNodeMonitorDescriptor<Data> DESCRIPTOR = new AbstractAsyncNodeMonitorDescriptor<Data>() { @Override protected Callable<Data,IOException> createCallable(Computer c) { return new Step1(get(c)); } @Override protected Map<Computer, Data> monitor() throws InterruptedException { Map<Computer, Data> base = super.monitor(); for (Entry<Computer, Data> e : base.entrySet()) { Computer c = e.getKey(); Data d = e.getValue(); if (d ==null) { // if we failed to monitor, put in the special value that indicates a failure e.setValue(d=new Data(get(c),-1L)); } if(d.hasTooManyTimeouts() && !isIgnored()) { // unlike other monitors whose failure still allow us to communicate with the agent, // the failure in this monitor indicates that we are just unable to make any requests // to this agent. 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 base; } public String getDisplayName() { return Messages.ResponseTimeMonitor_DisplayName(); } @Override public NodeMonitor newInstance(StaplerRequest req, JSONObject formData) throws FormException { return new ResponseTimeMonitor(); } }; private static final class Step1 extends MasterToSlaveCallable<Data,IOException> { private Data cur; private Step1(Data cur) { this.cur = cur; } public Data call() { // this method must be being invoked locally, which means the roundtrip time is zero and zero forever return new Data(cur,0); } private Object writeReplace() { return new Step2(cur); } private static final long serialVersionUID = 1L; } private static final class Step2 extends MasterToSlaveCallable<Step3,IOException> { private final Data cur; private final long start = System.currentTimeMillis(); public Step2(Data cur) { this.cur = cur; } public Step3 call() { // this method must be being invoked locally, which means the roundtrip time is zero and zero forever return new Step3(cur,start); } private static final long serialVersionUID = 1L; } private static final class Step3 implements Serializable { private final Data cur; private final long start; private Step3(Data cur, long start) { this.cur = cur; this.start = start; } private Object readResolve() { long end = System.currentTimeMillis(); return new Data(cur,(end-start)); } private static final long serialVersionUID = 1L; } /** * Immutable representation of the monitoring data. */ @ExportedBean public static final class Data extends MonitorOfflineCause implements Serializable { /** * 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; } /** * String 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 Messages.ResponseTimeMonitor_TimeOut(fc); return getAverage()+"ms"; } @Override public Class<? extends NodeMonitor> getTrigger() { return ResponseTimeMonitor.class; } 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()); }