/* * PS3 Media Server, for streaming any medias to your PS3. * Copyright (C) 2011 G. Zsombor * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License only. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.pms.network; import java.net.InetAddress; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import net.pms.PMS; import net.pms.configuration.RendererConfiguration; import net.pms.io.OutputParams; import net.pms.io.ProcessWrapperImpl; import net.pms.io.SystemUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Network speed tester class. This can be used in an asynchronous way, as it returns Future objects. * * Future speed = SpeedStats.getInstance().getSpeedInMBits(addr); * * @see Future * * @author zsombor * */ public class SpeedStats { private static SpeedStats instance = new SpeedStats(); private static ExecutorService executor = Executors.newCachedThreadPool(); public static SpeedStats getInstance() { return instance; } private static final Logger logger = LoggerFactory.getLogger(RendererConfiguration.class); private final Map> speedStats = new HashMap>(); /** * Return the network throughput for the given IP address in MBits. It is calculated in the background, and cached, * so only a reference is given to the result, which can be retrieved with calling get() method on it. * @param addr * @return The network throughput */ public Future getSpeedInMBits(InetAddress addr, String rendererName) { synchronized(speedStats) { Future value = speedStats.get(addr.getHostAddress()); if (value != null) { return value; } value = executor.submit(new MeasureSpeed(addr, rendererName)); speedStats.put(addr.getHostAddress(), value); return value; } } class MeasureSpeed implements Callable { InetAddress addr; String rendererName; public MeasureSpeed(InetAddress addr, String rendererName) { this.addr = addr; this.rendererName = rendererName != null ? rendererName : "Unknown"; } @Override public Integer call() throws Exception { try { return doCall(); } catch (Exception e) { logger.warn("error during measuring network throughput : "+e.getMessage(), e); throw e; } } private Integer doCall() throws Exception { String ip = addr.getHostAddress(); logger.info("Checking ip:" + ip + " for " + rendererName); // calling canonical host name at the first time is slow, so we call it in a separate thread String hostname = addr.getCanonicalHostName(); synchronized(speedStats) { Future otherTask = speedStats.get(hostname); if (otherTask != null) { // wait a little bit try { // probably we are waiting for ourself, to finishing the work ... Integer value = otherTask.get(100, TimeUnit.MILLISECONDS); // if the other task already calculated, the speed, we get the result, // unless we will do it now if (value != null) { return value; } } catch (TimeoutException e) { logger.trace("we couldn't get the value based on canonical name"); } } } if (!ip.equals(hostname)) { logger.info("Renderer " + rendererName + " found on this address: " + hostname + " (" + ip + ")"); } else { logger.info("Renderer " + rendererName + " found on this address: " + ip); } // let's get that speed OutputParams op = new OutputParams(null); op.log = true; op.maxBufferSize = 1; SystemUtils sysUtil = PMS.get().getRegistry(); final ProcessWrapperImpl pw = new ProcessWrapperImpl(sysUtil.getPingCommand(addr.getHostAddress(), 3, 64000), op, true, false); Runnable r = new Runnable() { public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { } pw.stopProcess(); } }; Thread failsafe = new Thread(r, "SpeedStats Failsafe"); failsafe.start(); pw.runInSameThread(); List ls = pw.getOtherResults(); int time = 0; int c = 0; for (String line : ls) { int msPos = line.indexOf("ms"); try { if (msPos > -1) { String timeString = line.substring(line.lastIndexOf("=", msPos) + 1, msPos).trim(); time += Double.parseDouble(timeString); c++; } } catch (Exception e) { // no big deal } } if (c > 0) { time = (int) (time / c); } if (time > 0) { int speedInMbits = (int) (1024 / time); logger.info("Address " + addr + " has an estimated network speed of: " + speedInMbits + " Mb/s"); synchronized(speedStats) { CompletedFuture result = new CompletedFuture(speedInMbits); // change the statistics with a computed future values speedStats.put(ip, result); speedStats.put(hostname, result); } return speedInMbits; } return -1; } } static class CompletedFuture implements Future { X value; public CompletedFuture(X value) { this.value = value; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } @Override public X get() throws InterruptedException, ExecutionException { return value; } @Override public X get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return value; } } }