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

io.github.microcks.service.MetricsService Maven / Gradle / Ivy

The newest version!
/*
 * Copyright The Microcks Authors.
 *
 * 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 io.github.microcks.service;

import io.github.microcks.domain.Operation;
import io.github.microcks.domain.Service;
import io.github.microcks.domain.ServiceType;
import io.github.microcks.domain.TestCaseResult;
import io.github.microcks.domain.TestConformanceMetric;
import io.github.microcks.domain.TestResult;
import io.github.microcks.domain.TestStepResult;
import io.github.microcks.domain.Trend;
import io.github.microcks.repository.TestConformanceMetricRepository;
import io.github.microcks.util.IdBuilder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;

import java.util.Calendar;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Service bean for common processing around metrics.
 * 
 * @author laurent
 */
@org.springframework.stereotype.Service
@PropertySources({ @PropertySource("features.properties"),
      @PropertySource(value = "file:/deployments/config/features.properties", ignoreResourceNotFound = true),
      @PropertySource("application.properties") })
public class MetricsService {

   /** A simple logger for diagnostic messages. */
   private static Logger log = LoggerFactory.getLogger(MetricsService.class);

   @Autowired
   private MessageService messageService;

   @Autowired
   private TestConformanceMetricRepository metricRepository;

   @Value("${features.feature.repository-filter.label-key}")
   private String filterLabelKey;

   @Value("#{new Integer('${test-conformance.trend-size:3}')}")
   private Integer testConformanceTrendSize;

   @Value("#{new Integer('${test-conformance.trend-history-size:10}')}")
   private Integer testConformanceTrendHistorySize;

   /**
    * Configure a TestConformanceMetric object for a given Service.
    * 
    * @param service The Service to configure coverage metrics for.
    */
   public void configureTestConformanceMetric(Service service) {
      TestConformanceMetric metric = metricRepository.findByServiceId(service.getId());
      if (metric == null) {
         log.debug("Creating a new TestCoverageMetric for Service '{}'", service.getId());
         metric = new TestConformanceMetric();
         metric.setServiceId(service.getId());
         metric.setLatestTrend(Trend.STABLE);
      }

      // Update max possible score and aggregation label on crate or change.
      metric.setMaxPossibleScore(computeMaxPossibleConformanceScore(service));
      if (filterLabelKey != null && service.getMetadata().getLabels() != null) {
         metric.setAggregationLabelValue(service.getMetadata().getLabels().get(filterLabelKey));
      }

      metricRepository.save(metric);
   }

   /**
    * Remove TestConformanceMetric associated to a given Service.
    * 
    * @param serviceId The identifier of Service to remove coverage metrics for.
    */
   public void removeTestConformanceMetric(String serviceId) {
      TestConformanceMetric metric = metricRepository.findByServiceId(serviceId);
      if (metric != null) {
         metricRepository.delete(metric);
      }
   }

   /**
    * Update the test conformance score when a test is completed.
    *
    * @param testResult The newly completed Test for a Service.
    */
   public void updateTestConformanceMetricOnTestResult(TestResult testResult) {
      TestConformanceMetric metric = metricRepository.findByServiceId(testResult.getServiceId());
      if (metric != null) {
         // Compute current score.
         double currentScore = 0;
         if (testResult.isSuccess()) {
            currentScore = metric.getMaxPossibleScore();
         } else {
            int totalSteps = 0;
            int totalSuccess = 0;
            for (TestCaseResult caseResult : testResult.getTestCaseResults()) {
               for (TestStepResult stepResult : caseResult.getTestStepResults()) {
                  if (stepResult.isSuccess()) {
                     totalSuccess++;
                  }
                  totalSteps++;
               }
            }
            currentScore = metric.getMaxPossibleScore() * (((double) totalSuccess) / ((double) totalSteps));
         }

         // Update entity with score and last update day.
         metric.setCurrentScore(currentScore);
         metric.setLastUpdateDay(getTodayAsString());

         // If we have enough history, compute a trend.
         if (metric.getLatestScores().size() >= testConformanceTrendSize - 1) {
            // Start sorting the history map in reverse order (last days measure first)
            List> latestReversedScores = metric.getLatestScores().entrySet().stream()
                  .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())).collect(Collectors.toList());

            // Get history average and last n days average.
            double globalAvg = latestReversedScores.stream().collect(Collectors.averagingDouble(Map.Entry::getValue));

            double latestAvg = currentScore;
            for (int i = 0; i < testConformanceTrendSize - 1; i++) {
               latestAvg += latestReversedScores.get(i).getValue();
            }
            latestAvg = latestAvg / testConformanceTrendSize;

            // Compute a trend based on diff.
            double avgDiff = latestAvg - globalAvg;
            if (avgDiff >= 5) {
               metric.setLatestTrend(Trend.UP);
            } else if (avgDiff < 5 && avgDiff >= 0.2) {
               metric.setLatestTrend(Trend.LOW_UP);
            } else if (avgDiff < 0.2 && avgDiff > -0.2) {
               metric.setLatestTrend(Trend.STABLE);
            } else if (avgDiff <= -0.2 && avgDiff > -5) {
               metric.setLatestTrend(Trend.LOW_DOWN);
            } else if (avgDiff <= -5) {
               metric.setLatestTrend(Trend.DOWN);
            }

            // Remove all entries >9 to keep 10 entries.
            while (latestReversedScores.size() >= testConformanceTrendHistorySize) {
               Map.Entry lastEntry = latestReversedScores.get(latestReversedScores.size() - 1);
               metric.getLatestScores().remove(lastEntry.getKey());
               latestReversedScores.remove(latestReversedScores.size() - 1);
            }
            metric.getLatestScores().put(metric.getLastUpdateDay(), currentScore);
         }
         // Save updated metric.
         metricRepository.save(metric);
      }
   }

   /** Compute the max possible coverage for a Service. */
   private double computeMaxPossibleConformanceScore(Service service) {
      double maxScore = 0.00;
      double operationContrib = 100 / ((double) service.getOperations().size());

      for (Operation operation : service.getOperations()) {
         List exchanges;
         if (ServiceType.EVENT.equals(service.getType()) || ServiceType.GENERIC_EVENT.equals(service.getType())) {
            // If an event, we should explicitly retrieve event messages.
            exchanges = messageService.getEventByOperation(IdBuilder.buildOperationId(service, operation));
         } else {
            // Otherwise we have traditional request / response pairs.
            exchanges = messageService.getRequestResponseByOperation(IdBuilder.buildOperationId(service, operation));
         }
         if (exchanges != null) {
            if (exchanges.size() >= 2) {
               maxScore += operationContrib;
            } else if (exchanges.size() == 1) {
               maxScore += (operationContrib / 2);
            }
         }
      }
      return maxScore;
   }

   /** Return today's date as yyyyMMdd pattern. */
   private String getTodayAsString() {
      // Compute day string representation.
      Calendar calendar = Calendar.getInstance();
      int month = calendar.get(Calendar.MONTH) + 1;
      String monthStr = (month < 10 ? "0" : "") + String.valueOf(month);
      int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
      String dayOfMonthStr = (dayOfMonth < 10 ? "0" : "") + String.valueOf(dayOfMonth);

      return calendar.get(Calendar.YEAR) + monthStr + dayOfMonthStr;
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy