Package net.pms.network

Source Code of net.pms.network.SpeedStats$CompletedFuture

/*
* 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.*;
import net.pms.PMS;
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<Integer> speed = SpeedStats.getInstance().getSpeedInMBits(addr);
*
* @see Future
*
* @author zsombor <gzsombor@gmail.com>
*
*/
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(SpeedStats.class);

  private final Map<String, Future<Integer>> speedStats = new HashMap<>();

  public Future<Integer> getSpeedInMBitsStored(InetAddress addr, String rendererName) {
    // only look in the store
    // if no pings are done resort to conf values
    synchronized (speedStats) {
      return speedStats.get(addr.getHostAddress());
    }
  }

  /**
   * 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 by calling the get() method on it.
   *
   * @param addr
   * @param rendererName
   *
   * @return The network throughput
   */
  public Future<Integer> getSpeedInMBits(InetAddress addr, String rendererName) {
    synchronized (speedStats) {
      Future<Integer> 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<Integer> {
    InetAddress addr;
    String rendererName;

    public MeasureSpeed(InetAddress addr, String rendererName) {
      this.addr = addr;
      this.rendererName = rendererName != null ? rendererName.replaceAll("\n", "") : "Unknown";
    }

    @Override
    public Integer call() throws Exception {
      try {
        return doCall();
      } catch (Exception e) {
        LOGGER.warn("Error 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 the canonical host name the first time is slow, so we call it in a separate thread
      String hostname = addr.getCanonicalHostName();
      synchronized (speedStats) {
        Future<Integer> otherTask = speedStats.get(hostname);
        if (otherTask != null) {
          // wait a little bit
          try {
            // probably we are waiting for ourself to finish the work...
            Integer value = otherTask.get(100, TimeUnit.MILLISECONDS);
            // if the other task already calculated the speed, we get the result,
            // unless we do it now
            if (value != null) {
              return value;
            }
          } catch (TimeoutException e) {
            LOGGER.trace("We couldn't get the value based on the 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);
      }

      int[] sizes = {512, 1476, 9100, 32000, 64000};
      double bps = 0;
      int cnt = 0;

      for (int i = 0; i < sizes.length; i++) {
        double p = doPing(sizes[i]);
        if (p != 0) {
          bps += p;
          cnt++;
        }
      }
      int speedInMbits = (int) (bps / (cnt * 1000000));
      LOGGER.info("Renderer " + rendererName + " has an estimated network speed of " + speedInMbits + " Mb/s");
      synchronized (speedStats) {
        CompletedFuture<Integer> result = new CompletedFuture<>(speedInMbits);
        // update the statistics with a computed future value
        speedStats.put(ip, result);
        speedStats.put(hostname, result);
      }
      return speedInMbits;
    }

    private double doPing(int size) {
      // 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, size), op, true, false);
      Runnable r = new Runnable() {
        @Override
        public void run() {
          try {
            Thread.sleep(3000);
          } catch (InterruptedException e) {
          }
          pw.stopProcess();
        }
      };

      Thread failsafe = new Thread(r, "SpeedStats Failsafe");
      failsafe.start();
      pw.runInSameThread();
      List<String> ls = pw.getOtherResults();
      double time = 0;
      int c = 0;
      String timeString;

      for (String line : ls) {
        timeString = sysUtil.parsePingLine(line);
        if (timeString == null) {
          continue;
        }
        try {
          time += Double.parseDouble(timeString);
          c++;
        } catch (NumberFormatException e) {
          // no big deal
          LOGGER.debug("Could not estimate network speed from time: \"" + timeString + "\"");
        }
      }

      if (c > 0) {
        time /= c;
        int frags = ((size + 8) / 1500) + 1;
        LOGGER.debug("est speed for " + size + " " + frags + " is " + ((size + 8 + (frags * 32)) * 8000 * 2) / time);
        return ((size + 8 + (frags * 32)) * 8000 * 2) / time;
      }
      return time;
    }
  }

  static class CompletedFuture<X> implements Future<X> {
    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;
    }
  }
}
TOP

Related Classes of net.pms.network.SpeedStats$CompletedFuture

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.