Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
languagejavascript
titleCódigo JQuery/Javascript
linenumberstrue
$(function() {
   function getData(beforeTime, graph, zoomMode, nowGetTime) {
      var dataUrl = '/LS-Sched/?method=get_wireless_experiment_data' +
         '&before_time_size=' + ( beforeTime + 1 ) + '&now_get_time=' + nowGetTime;
      
      $.ajax({ 
         url: dataUrl,
         dataType: 'json',
         async: true,
         success: function(statistics) {
            var distance = getTimmerDistance(graph);
            
            updateData(statistics.method_result, graph);
            updateGraph(graph);
            
            if(zoomMode == false) {
               setTimeout(function(){ getData(distance, graph, false, null); }, 1000);
            }
            
            // Update console box
            try {
               var expOutput = statistics.method_result.output.replace(/\n/g, "<br />").replace("<br />", "").trim();
            } catch(e) {
               var expOutput = "";
            }
            
            try {
               if(expOutput) {
                  var experimentOutput = $("#experimentOutput");
                  if(experimentOutput.text() != expOutput.replace(/<br \/>/g, "")) {
                     var oldContent = experimentOutput.text().trim();
                     var isInBottom = ((experimentOutput.scrollTop() + experimentOutput.height() + 10) >= experimentOutput[0].scrollHeight);
                     
                     experimentOutput.html(expOutput);
                     
                     if(isInBottom || !oldContent) {
                        experimentOutput.scrollTop(experimentOutput[0].scrollHeight);
                     }
                  }
               }
            } catch(e) {}
            
            // Enable test run when its finished.
            try {
               if (expOutput.indexOf("INFO run: Experiment") >= 0) {
                  $("input[type=submit]").prop("disabled", false);
                  $(".hide-when-done").hide();
               }
            } catch(e) {}
         }
      });
   }
   
   function updateData( statistics, graph ) {
      if( graph.data.length == 0 ) { 
         graph.data.push({ 
            data: statistics.client,
            label: "Client output",
            name: "client"
         });
         
         graph.data.push({ 
            data: statistics.server,
            label: "Server input",
            name: "server"
         });
      } else {
         graph.data.forEach( function( graphData ) {
            switch( graphData.name ) {
               case "client":
                  var newData = statistics.client;
                  break;
               case "server":
                  var newData = statistics.server;
                  break;
            }
            graphData.data = graphData.data.concat(newData);
            graphData.data.sort( function(a, b){ return a[0] - b[0] } );
         });
      }
   }
   
   var pGraph = {
      type: "throughput",
      realTime: true,
      plot: {
         element: "#throughput-graph",
         graph: $.plot("#throughput-graph", [], getOptions("Mbps", [ "red", "blue" ], $("#throughput-legend")))
      },
      data: []
   };
   
   var zoomButton = $(pGraph.plot.element).parent().parent().find(".box-header .box-tools .btn-zoom-out");
   var realTimeButton = $(pGraph.plot.element).parent().parent().find(".box-header .box-tools .realtime .btn");
   
   // install select option
   $(pGraph.plot.element).bind("plotselected", function (event, ranges) {
      $(realTimeButton[1]).trigger("click");
      
      $.each(pGraph.plot.graph.getXAxes(), function(_, axis) {
         var opts = axis.options;
         opts.min = ranges.xaxis.from;
         opts.max = ranges.xaxis.to;
      });
      
      pGraph.plot.graph.setupGrid();
      pGraph.plot.graph.draw();
      pGraph.plot.graph.clearSelection();
   });
   
   // add zoom out button
   zoomButton.click(function (event) {
      event.preventDefault();
      $(realTimeButton[1]).trigger("click");
      
      $.each(pGraph.plot.graph.getXAxes(), function(_, axis) {
         var opts = axis.options;
         var distance = 0;
         var now = new Date().getTime() - 3000;
         if( opts.min != null ) {
            if( opts.max != null ) {
               distance = opts.max - opts.min;
            } else {
               distance = now- opts.min;
            }
            
            distance = distance / 2;
         }
         
         opts.min = opts.min - distance;
         if( opts.max == null ) {
            opts.max = now;
         } else {
            opts.max = opts.max + distance;
         }

         if( opts.max > now ) {
            opts.max = now;
         }
         
         if(pGraph.data.length > 0) {
            var firstDataTime = Math.floor(pGraph.data[0].data[0][0] / 1000);
            var optsTime = Math.floor(opts.min / 1000 );
            
            if( optsTime < firstDataTime ) {
               var beforeData = Math.round( ( firstDataTime -  optsTime ) );
               getData(beforeData, pGraph, true, firstDataTime);
               updateGraph(pGraph);
            }
         }
      });
      
      pGraph.plot.graph.setupGrid();
      pGraph.plot.graph.draw();
      pGraph.plot.graph.clearSelection();
   });
   
   // Configure real time buttons
   realTimeButton.click(function (event) {
      event.preventDefault();
      if ($(this).attr("data-toggle") === "on") {
         pGraph.plot.graph.clearSelection();
         updateRealTime( pGraph.plot.graph, 5 );
         
         pGraph.plot.graph.setupGrid();
         pGraph.plot.graph.draw();
         pGraph.realTime = true;
      } else {
         if(pGraph.realTime === true) {
            $.each( pGraph.plot.graph.getXAxes(), function(_, axis) {
               var opts = axis.options;
               opts.max = new Date().getTime() - 3000;
            });
         }
         pGraph.realTime = false;
      }
   });
   
   getData(1800, pGraph, false, null);
});

 

  • A função getData getData presente nas linhas 2~52 é a responsável pelas chamadas que são realizadas de forma assíncrona para a API LS-Sched e que busca os dados do experimento executado no tutorial.
    • Observe que o método chamado la API do LS-Sched é o get_wireless_experiment_data, alguns outros parâmetros são passados neste método, mas isso veremos posteriormente na seção 3.
    • Uma vez de posse dos dados, o código presente nas linhas 10~50 é responsável por realizar a atualização do gráfico e do output da console na interface.
  • A função updateData presente nas linhas 54~81 é a responsável por atualizar a estrutura de dados que contém os dados dos gráficos. Essa estutura de dados possui os padrões pedidos pela biblioteca do Flot Charts de modo que ela é diretamente utilizada na atualização do gráfico.
  • O código das linhas 83~179 é executado assim que a página é carregada juntamente a este javascript e ele é responsável por iniciar a biblioteca do Flot Charts e configurar todo o gráfico.
  • A linha 179 é a responsável por iniciar as requisições assíncronas à API LS-Sched, realizando a primeira chamada à função getData.

...

3. Criando um método na API do LS-Sched para buscar dados do experimento da base de dados do OML

O LS-Sched também foi desenvolvido seguindo o mesmo modelo MVC, entretanto a camada de visualização é estática para retornar apenas a saída em formato JSON. No caso dos experimentos temos uma peculiaridade também, que é uma camada de Serviços, respon?avel por executar tarefas auxiliares como chamadas de sistema durante a execução dos métodos de experimentos.

Para criar um método na API do LS-Sched com a finalidade de buscar dados a serem utilizados como saída e integrados na interface do LS-Web iremos precisar basicamente de realizar alterações nos seguintes arquivos:

  • "~/LS-Sched/Controller/ExperimentController.php": Classe controladora do que diz respeito aos experimentos no LS-Sched. Ela irá conter a definição do método que poderá ser utilizado pela API.
  • "~/LS-Sched/Model/ExperimentModel.php": Classe para manipulação de dados do que diz respeito aos experimentos, ou seja, é ela que é responsável por buscar os dados dos experimentos nas bases de dados do OML.
  • "~/LS-Sched/Service/ExperimentService.php": Classe de serviços auxiliares ao experimento. Nela, por exemplo, é que é realizada a leitura da saída do console do OMF para o experimento que está sendo executado.

 

Experiment Controller

Para criar um novo método na API LS-Sched basta criar um método público na classe ExperimentController. O nome do método criado dentro da classe será o nome do método chamado pela API, logo se você criar um método de nome teste, ele poderá ser chamado passando o parâmetro "?method=teste" para à API.

Como exemplo, abaixo temos o código do método para a coleta dos dados do tutorial 1: Wireless Experiment:

Code Block
languagephp
titleCódigo da controladora do método da API
linenumberstrue
/**
 * Gets the wireless experiment data.
 * @param array $parameters containing the treated call parameters.
 * @return array containing the wireless experiment data.
 */
public function getWirelessExperimentData($parameters)
{
    $beforeTimeSize = isset($parameters["before_time_size"]) ? $parameters["before_time_size"] : null;
    $nowGetTime = isset($parameters["now_get_time"]) ? $parameters["now_get_time"] : null;
    $experiment = "wireless_experiment";

    $return  = $this->Experiment->getIperfData($experiment . "_iperf", $nowGetTime, $beforeTimeSize);

    // Gets the experiment output
    $ownerExperiment = $this->Experiment->getExperimentSlice($experiment);
    if($ownerExperiment === false) {
        $return['output'] = "";
    } else {
        $return['output'] = $this->experimentService->getExperimentOutput($experiment,
            $ownerExperiment["username"], $ownerExperiment["slice"]);
    }

    return $return;
}

 

  • Este método define o método da API "get_wireless_experiment_data" ou "getWirelessExperimentData" (aceita tanto padrão camel quanto underscore).
  • Todos os métodos de API devem receber um parâmetro que irá conter os parâmetros de entrada da chamada.
    • O parâmetro será um array em forma de dicionário dos parâmetros passados durante a chamada.
    • Como pode ser visualizado nas linhas 8 e 9, utilizamos os parâmetros da chamada "before_time_size" e "now_get_time".
  • O retorno do método deve ser convertível para um JSON, ou seja, se o retorno for um objeto, este deve implementar a interface JsonSerializable. Arrays, strings, booleanos e numéricos são convertíveis naturalmente.
  • Na linha 12 utilizamos um método da model dos experimentos para buscar os dados do iperf coletados para este experimento.
  • Nas linhas 14~21 utilizamos o serviço de experimentos e a model para buscar a saída da console do experimento OMF que está sendo executado durante o tutorial.
 

Após criar o método da API, você deverá configurar quais são os parâmetros de entrada (caso não exista você pode pular essa parte) que o seu método aceita e quais os tipos de dados eles devem ser. Essa configuração deve ser feita no método getMethodParameters() encontrado na classe ExperimentController:

Code Block
languagephp
titleMétodo para definição de parâmetros de entrada
linenumberstrue
/**
 * Gets the obligatory and optional parameters to execute the api call method.
 * @return array containing the obligatory and the optional parameters to execute the api call method.
 */
public function getMethodParameters()
{
    $params = parent::getMethodParameters();

    $obParams = [];
    $opParams = [];
    switch($this->getApiCall()) {
        case "executeExperiment":
            $obParams = [
                'username' => 'string',
                'experiment' => 'string',
                'properties' => 'array'
            ];
            break;
        case "isExperimentExecuting":
        case "getExperimentScript":
            $obParams = [
                'experiment' => 'string'
            ];
            break;
        case "getWirelessExperimentData":
        case "getTcpBitrateExperimentData":
        case "getPowerControlExperimentData":
            $obParams = [
                'before_time_size' => 'numeric'
            ];
            $opParams = [
                'now_get_time' => 'numeric'
            ];
            break;
    }

    $params["obligatory"] = array_merge($params["obligatory"], $obParams);
    $params["optional"] = array_merge($params["optional"], $opParams);

    return $params;
}

 

  • Basta adicionar um "case" dentro do switch das linhas 11~35 com o nome de seu método definindo quais são os parâmetros obrigatórios ($obParams) e os parâmetros opcionais ($opParams).
    • Basta colocar dentro do array a chave contendo o nome do parâmetro e o valor contendo o tipo dele.
    • Os tipos aceitáveis são:
      • numeric
      • string
      • bool
      • array
      • double

Para finalizar com nosso desenvolvimento na controladora, precisamos definir o nível de acesso ao método. Por padrão, todos que possuem a key de acesso (passada via parâmetro) possuem acesso aos métodos da API, entretanto, para esses métodos de coleta dos dados para serem visualizados no portal, onde as requisições são feitas por quaisquer clientes, precisamos liberar o acesso sem a necessidade da Key. Para isto basta modificar o método beforeFilter() encontrado na classe ExperimentController:

Code Block
languagephp
titleControle de acesso
linenumberstrue
/**
 * Method executed before execute the api call method
 * Verifies if is possible or not to execute the API call method.
 * @return bool True if the api call method can be executed or False if not.
 */
public function beforeFilter()
{
    $noAuthMethods = ['getWirelessExperimentData', 'getTcpBitrateExperimentData', 'getPowerControlExperimentData'];
    if(in_array($this->getApiCall(), $noAuthMethods)) {
        return true;
    }

    return parent::beforeFilter();
}

 

  • Observe que nas linhas 8~11 já foi codificado uma forma de liberar o acesso para métodos que não precisam da key, ou seja, para você liberar seu método basta adiciona-lo no array contido na linha 8.
  • Caso seja necessário, você pode programar neste método outras formas de autorização, lembrando que o "parent::beforeFilter()" possui a validação padrão que é verificar se foi passado como parâmetro a chave de acesso e se ela é válida.