All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kurento.test.latency.LatencyController Maven / Gradle / Ivy

Go to download

This project contains test cases for testing Kurento Java Client and Kurento Media Server.

There is a newer version: 6.18.0
Show newest version
/*
 * (C) Copyright 2014 Kurento (http://kurento.org/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.kurento.test.latency;

import java.awt.Color;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import org.kurento.test.base.KurentoTest;
import org.kurento.test.browser.WebPage;
import org.kurento.test.monitor.SystemMonitorManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Latency controller.
 *
 * @author Micael Gallego ([email protected])
 * @author Boni Garcia ([email protected])
 * @since 5.0.5
 */
public class LatencyController implements ChangeColorEventListener {

  private static final int MAX_DISTANCE = 60;

  public Logger log = LoggerFactory.getLogger(LatencyController.class);

  private Map latencyMap;

  private String name;

  private long latencyThreshold;
  private TimeUnit latencyThresholdTimeUnit;

  private long timeout;
  private TimeUnit timeoutTimeUnit;

  private ChangeColorObservable localChangeColor;
  private ChangeColorObservable remoteChangeColor;

  private long lastLocalColorChangeTime = -1;
  private long lastRemoteColorChangeTime = -1;
  private long lastLocalColorChangeTimeAbsolute = -1;
  private long lastRemoteColorChangeTimeAbsolute = -1;

  private Color lastLocalColor;
  private Color lastRemoteColor;

  private Thread localColorTrigger;
  private Thread remoteColorTrigger;

  private Semaphore localEventLatch = new Semaphore(0);
  private Semaphore remoteEventLatch = new Semaphore(0);

  private boolean failIfLatencyProblem;

  private long latencyRate;

  private int consecutiveFailMax;

  private SystemMonitorManager monitor;

  public LatencyController(String name) {
    this();
    this.name = name;
  }

  public LatencyController(String name, SystemMonitorManager monitor) {
    this(name);
    this.monitor = monitor;
  }

  public LatencyController() {
    // Defaults
    latencyThreshold = 3000;
    latencyThresholdTimeUnit = TimeUnit.MILLISECONDS;

    timeout = 30;
    timeoutTimeUnit = TimeUnit.SECONDS;

    failIfLatencyProblem = false;

    latencyRate = 100; // milliseconds

    consecutiveFailMax = 3;

    // Latency map (registry)
    latencyMap = new TreeMap();
  }

  @Override
  public synchronized void onEvent(ChangeColorEvent event) {
    if (event.getVideoTag().getVideoTagType() == VideoTagType.LOCAL) {
      lastLocalColorChangeTimeAbsolute = new Date().getTime();
      lastLocalColorChangeTime = event.getTime();
      lastLocalColor = event.getColor();
      localEventLatch.release();
    } else if (event.getVideoTag().getVideoTagType() == VideoTagType.REMOTE) {
      lastRemoteColorChangeTimeAbsolute = new Date().getTime();
      lastRemoteColorChangeTime = event.getTime();
      lastRemoteColor = event.getColor();
      remoteEventLatch.release();
    }
  }

  public void checkLatencyInBackground(final long testTime, final TimeUnit testTimeUnit,
      final WebPage client) throws InterruptedException, IOException {
    new Thread() {
      @Override
      public void run() {
        try {
          checkLatency(testTime, testTimeUnit, client);
        } catch (InterruptedException e1) {
          log.warn("checkLatencyInBackground InterruptedException: {}", e1.getMessage());
        } catch (IOException e2) {
          throw new RuntimeException(e2);
        }
      }
    }.start();
  }

  public void checkLatencyInBackground(final long testTime, final TimeUnit testTimeUnit,
      final WebPage localClient, final WebPage remoteClient) {
    new Thread() {
      @Override
      public void run() {
        checkLatency(testTime, testTimeUnit, localClient, remoteClient);
      }
    }.start();
  }

  public void checkLatencyInBackground(final WebPage localClient, final WebPage remoteClient) {
    new Thread() {
      @Override
      public void run() {
        checkLatency(Long.MAX_VALUE, TimeUnit.SECONDS, localClient, remoteClient);
      }
    }.start();
  }

  public void checkLatency(final long testTime, final TimeUnit testTimeUnit, WebPage client)
      throws InterruptedException, IOException {
    long playTime = TimeUnit.MILLISECONDS.convert(testTime, testTimeUnit);
    long endTimeMillis = System.currentTimeMillis() + playTime;
    int consecutiveFailCounter = 0;
    boolean first = true;

    while (true) {
      if (System.currentTimeMillis() > endTimeMillis) {
        break;
      }
      Thread.sleep(latencyRate);

      long latency = 0;
      LatencyRegistry latencyRegistry = new LatencyRegistry();
      try {
        latency = client.getLatency();
        if (latency == Long.MIN_VALUE || latency == 0) {
          continue;
        }
        if (first) {
          // First latency measurement is discarded
          first = false;
          continue;
        }
        log.debug(">>> Latency adquired: {} ms", latency);

      } catch (LatencyException le) {
        latencyRegistry.setLatencyException(le);

        if (failIfLatencyProblem) {
          throw le;
        }
      }

      long latencyTime = client.getCurrentTime(new VideoTag(VideoTagType.REMOTE));
      latencyRegistry.setLatency(latency);

      if (latency > getLatencyThreshold(TimeUnit.MILLISECONDS)) {

        String parsedtime = new SimpleDateFormat("mm-ss.SSS").format(latencyTime);
        client.takeScreeshot(
            KurentoTest.getDefaultOutputFile("-" + parsedtime + "-error-screenshot.png"));

        LatencyException latencyException = new LatencyException(latency, TimeUnit.MILLISECONDS);

        latencyRegistry.setLatencyException(latencyException);
        if (failIfLatencyProblem) {
          throw latencyException;
        }

        consecutiveFailCounter++;
        if (consecutiveFailCounter >= consecutiveFailMax) {
          throw new RuntimeException(
              consecutiveFailMax + " consecutive latency errors detected. Latest: "
                  + latencyException.getLocalizedMessage());
        }
      } else {
        // Reset the consecutive fail counter
        consecutiveFailCounter = 0;
      }
      latencyMap.put(latencyTime, latencyRegistry);
    }
  }

  public void checkLatency(final long testTime, final TimeUnit testTimeUnit, WebPage localClient,
      WebPage remoteClient) {

    addChangeColorEventListener(new VideoTag(VideoTagType.LOCAL), localClient,
        getName() + " " + VideoTagType.LOCAL);
    addChangeColorEventListener(new VideoTag(VideoTagType.REMOTE), remoteClient,
        getName() + " " + VideoTagType.REMOTE);

    String msgName = name != null ? "[" + name + "] " : "";

    if (localChangeColor == null || remoteChangeColor == null) {
      throw new RuntimeException(msgName + "Bad setup in latency controller "
          + " (local and remote tag of browser(s) needed");
    }

    try {
      final Thread waitingThread = Thread.currentThread();

      Thread thread;
      if (testTimeUnit != null) {
        thread = new Thread() {
          @Override
          public void run() {
            try {
              testTimeUnit.sleep(testTime);
              waitingThread.interrupt();
            } catch (InterruptedException e) {
              // Intentionally left blank
            }
          }
        };
        thread.setDaemon(true);
        thread.start();
      } else {
        thread = waitingThread;
      }

      // Synchronization with the green color
      do {
        waitForLocalColor(msgName, thread);
      } while (!similarColor(lastLocalColor, Color.GREEN));
      do {
        waitForRemoteColor(msgName, thread);
      } while (!similarColor(lastRemoteColor, Color.GREEN));

      while (true) {

        waitForLocalColor(msgName, thread);
        waitForRemoteColor(msgName, thread);

        long latencyMilis =
            Math.abs(lastRemoteColorChangeTimeAbsolute - lastLocalColorChangeTimeAbsolute);

        SimpleDateFormat formater = new SimpleDateFormat("mm:ss.SSS");
        String parsedLocaltime = formater.format(lastLocalColorChangeTimeAbsolute);
        String parsedRemotetime = formater.format(lastRemoteColorChangeTimeAbsolute);

        log.debug(
            "latencyMilis={} -- lastLocalColor={} -- lastRemoteColor={} -- "
                + "lastLocalColorChangeTime={} -- lastRemoteColorChangeTime={} -- "
                + "lastLocalColorChangeTimeAbsolute={} -- lastRemoteColorChangeTimeAbsolute={}",
            latencyMilis, lastLocalColor, lastRemoteColor,
            formater.format(lastLocalColorChangeTime), formater.format(lastRemoteColorChangeTime),
            parsedLocaltime, parsedRemotetime);

        if (similarColor(lastLocalColor, lastRemoteColor)) {
          log.debug("--> Latency adquired ({} ms)", latencyMilis);

          if (monitor != null) {
            monitor.addCurrentLatency(latencyMilis);
          }

          LatencyRegistry LatencyRegistry = new LatencyRegistry(lastRemoteColor, latencyMilis);

          if (latencyMilis > getLatencyThreshold(TimeUnit.MILLISECONDS)) {
            LatencyException latencyException = new LatencyException(latencyMilis, testTimeUnit,
                parsedLocaltime, parsedRemotetime, testTime, latencyMilis);
            LatencyRegistry.setLatencyException(latencyException);
            if (failIfLatencyProblem) {
              thread.interrupt();
              throw latencyException;
            } else {
              log.warn(latencyException.getMessage());
            }
            if (monitor != null) {
              monitor.incrementLatencyErrors();
            }
          }

          latencyMap.put(lastRemoteColorChangeTime, LatencyRegistry);
        }

      }

    } catch (InterruptedException e) {
      log.debug("Finished LatencyController thread due to Interrupted Exception");
    }
    localColorTrigger.interrupt();
    remoteColorTrigger.interrupt();
  }

  private void waitForRemoteColor(String msgName, Thread thread) throws InterruptedException {
    if (!remoteEventLatch.tryAcquire(timeout, timeoutTimeUnit)) {
      thread.interrupt();
      throw new RuntimeException(msgName + "Change color not detected in REMOTE steam after "
          + timeout + " " + timeoutTimeUnit);
    }
  }

  private void waitForLocalColor(String msgName, Thread thread) throws InterruptedException {
    if (!localEventLatch.tryAcquire(timeout, timeoutTimeUnit)) {
      thread.interrupt();

      throw new RuntimeException(msgName + "Change color not detected in LOCAL steam after "
          + timeout + " " + timeoutTimeUnit);
    }
  }

  private boolean similarColor(Color expectedColor, Color realColor) {
    int realRed = realColor.getRed();
    int realGreen = realColor.getGreen();
    int realBlue = realColor.getBlue();

    int expectedRed = expectedColor.getRed();
    int expectedGreen = expectedColor.getGreen();
    int expectedBlue = expectedColor.getBlue();

    double distance = Math.sqrt((realRed - expectedRed) * (realRed - expectedRed)
        + (realGreen - expectedGreen) * (realGreen - expectedGreen)
        + (realBlue - expectedBlue) * (realBlue - expectedBlue));
    return distance <= MAX_DISTANCE;
  }

  public void addChangeColorEventListener(VideoTag type, WebPage testClient, String name) {
    final long timeoutSeconds = TimeUnit.SECONDS.convert(timeout, timeoutTimeUnit);

    if (type.getVideoTagType() == VideoTagType.LOCAL) {
      localChangeColor = new ChangeColorObservable();
      localChangeColor.addListener(this);
      localColorTrigger =
          new Thread(new ColorTrigger(type, testClient, localChangeColor, timeoutSeconds));
      if (name != null) {
        localColorTrigger.setName(name);
      }
      localColorTrigger.start();
    } else {
      remoteChangeColor = new ChangeColorObservable();
      remoteChangeColor.addListener(this);
      remoteColorTrigger =
          new Thread(new ColorTrigger(type, testClient, remoteChangeColor, timeoutSeconds));
      if (name != null) {
        remoteColorTrigger.setName(name);
      }
      remoteColorTrigger.start();
    }
  }

  public void drawChart(String filename, int width, int height) throws IOException {
    ChartWriter chartWriter = new ChartWriter(latencyMap, getName());
    chartWriter.drawChart(filename, width, height);
  }

  public void writeCsv(String csvTitle) throws IOException {
    PrintWriter pw = new PrintWriter(new FileWriter(csvTitle));
    for (long time : latencyMap.keySet()) {
      pw.println(time + "," + latencyMap.get(time).getLatency());
    }
    pw.close();
  }

  public void logLatencyErrorrs() throws IOException {
    log.debug("---------------------------------------------");
    log.debug("LATENCY ERRORS " + getName());
    log.debug("---------------------------------------------");
    int numErrors = 0;
    for (LatencyRegistry registry : latencyMap.values()) {
      if (registry.isLatencyError()) {
        numErrors++;
        log.debug(registry.getLatencyException().getMessage());
      }
    }

    log.debug("{} errors of latency detected (threshold: {} {})", numErrors, latencyThreshold,
        latencyThresholdTimeUnit);
    log.debug("---------------------------------------------");
  }

  public long getLatencyThreshold(TimeUnit timeUnit) {
    return timeUnit.convert(latencyThreshold, latencyThresholdTimeUnit);
  }

  public long getLatencyThreshold() {
    return latencyThreshold;
  }

  public void setLatencyThreshold(long latencyThreshold, TimeUnit latencyThresholdTimeUnit) {
    this.latencyThreshold = latencyThreshold;
    this.latencyThresholdTimeUnit = latencyThresholdTimeUnit;
  }

  public TimeUnit getLatencyTimeUnit() {
    return latencyThresholdTimeUnit;
  }

  public long getTimeout() {
    return timeout;
  }

  public void setTimeout(long timeout, TimeUnit timeoutTimeUnit) {
    this.timeout = timeout;
    this.timeoutTimeUnit = timeoutTimeUnit;
  }

  public TimeUnit getTimeoutTimeUnit() {
    return timeoutTimeUnit;
  }

  public void failIfLatencyProblem() {
    this.failIfLatencyProblem = true;
  }

  public String getName() {
    return name != null ? name : "";
  }

  public void setLatencyRate(long latencyRate) {
    this.latencyRate = latencyRate;
  }

  public void setConsecutiveFailMax(int consecutiveFailMax) {
    this.consecutiveFailMax = consecutiveFailMax;
  }

  public Map getLatencyMap() {
    return latencyMap;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy