public String getPercentileChartName(
     OperationStats operationStats,
     final String operation,
     String configurationName,
     int cluster,
     int iteration,
     String node,
     Collection<StatisticType> presentedStatistics) {
   String resultFileName = "";
   if (presentedStatistics.contains(StatisticType.HISTOGRAM)) {
     final Histogram histogram =
         operationStats == null
             ? null
             : operationStats.getRepresentation(
                 Histogram.class,
                 configuration.histogramBuckets,
                 configuration.histogramPercentile);
     if (histogram == null) {
       return resultFileName;
     } else {
       resultFileName =
           String.format(
               "percentiles_%s_%s_%s_%d_%d_%s.png",
               testName, operation, configurationName, cluster, iteration, node);
     }
   }
   return resultFileName;
 }
  public String createHistogramAndPercentileChart(
      Statistics statistics,
      final String operation,
      final String configurationName,
      int cluster,
      int iteration,
      String node,
      Collection<StatisticType> presentedStatistics) {

    OperationStats operationStats = null;
    if (statistics != null) {
      operationStats = statistics.getOperationsStats().get(operation);
    }

    String resultFileName = "";
    if (presentedStatistics.contains(StatisticType.HISTOGRAM)) {
      final Histogram histogram =
          operationStats == null
              ? null
              : operationStats.getRepresentation(
                  Histogram.class,
                  configuration.histogramBuckets,
                  configuration.histogramPercentile);
      if (histogram == null) {

      } else {
        final String histogramFilename =
            getHistogramName(
                operationStats,
                operation,
                configurationName,
                cluster,
                iteration,
                node,
                presentedStatistics);
        chartTaskFutures.add(
            HtmlReporter.executor.submit(
                new Callable<Void>() {
                  @Override
                  public Void call() throws Exception {
                    log.debug("Generating histogram " + histogramFilename);
                    HistogramChart chart = new HistogramChart().setData(operation, histogram);
                    chart
                        .setWidth(configuration.histogramWidth)
                        .setHeight(configuration.histogramHeight);
                    chart.save(directory + File.separator + histogramFilename);
                    return null;
                  }
                }));

        final Histogram fullHistogram = operationStats.getRepresentation(Histogram.class);
        final String percentilesFilename =
            getPercentileChartName(
                operationStats,
                operation,
                configurationName,
                cluster,
                iteration,
                node,
                presentedStatistics);
        chartTaskFutures.add(
            HtmlReporter.executor.submit(
                new Callable<Void>() {
                  @Override
                  public Void call() throws Exception {
                    log.debug("Generating percentiles " + percentilesFilename);
                    PercentilesChart chart =
                        new PercentilesChart().addSeries(configurationName, fullHistogram);
                    chart
                        .setWidth(configuration.histogramWidth)
                        .setHeight(configuration.histogramHeight);
                    chart.save(directory + File.separator + percentilesFilename);
                    return null;
                  }
                }));
      }
    }
    return resultFileName;
  }
  protected boolean addToChart(
      ComparisonChart chart,
      String subCategory,
      String operation,
      ChartType chartType,
      Map<Report, List<Aggregation>> reportAggregationMap) {
    Map<String, List<Report>> byConfiguration =
        Projections.groupBy(
            reportAggregationMap.keySet(),
            new Projections.Func<Report, String>() {
              @Override
              public String project(Report report) {
                return report.getConfiguration().name;
              }
            });
    for (Map.Entry<Report, List<Aggregation>> entry : reportAggregationMap.entrySet()) {
      for (Aggregation aggregation : entry.getValue()) {
        OperationStats operationStats = aggregation.totalStats.getOperationsStats().get(operation);
        if (operationStats == null) return false;

        String categoryName = entry.getKey().getConfiguration().name;
        if (subCategory != null) {
          categoryName = String.format("%s, %s", categoryName, subCategory);
        }
        // if there are multiple reports for the same configuration (multiple clusters), use cluster
        // size in category
        if (byConfiguration.get(entry.getKey().getConfiguration().name).size() > 1) {
          categoryName =
              String.format("%s, size %d", categoryName, entry.getKey().getCluster().getSize());
        }

        double subCategoryNumeric;
        String subCategoryValue;
        if (maxIterations > 1) {
          subCategoryNumeric = aggregation.iteration.id;
          subCategoryValue =
              aggregation.iteration.getValue() != null
                  ? aggregation.iteration.getValue()
                  : String.valueOf(aggregation.iteration.id);
        } else {
          subCategoryNumeric = entry.getKey().getCluster().getSize();
          subCategoryValue = String.format("Size %.0f", subCategoryNumeric);
        }
        switch (chartType) {
          case MEAN_AND_DEV:
            {
              MeanAndDev meanAndDev = operationStats.getRepresentation(MeanAndDev.class);
              if (meanAndDev == null) return false;
              chart.addValue(
                  toMillis(meanAndDev.mean),
                  toMillis(meanAndDev.dev),
                  categoryName,
                  subCategoryNumeric,
                  subCategoryValue);
              break;
            }
          case OPERATION_THROUGHPUT_GROSS:
            {
              OperationThroughput throughput =
                  operationStats.getRepresentation(
                      OperationThroughput.class,
                      TimeUnit.MILLISECONDS.toNanos(
                          aggregation.totalStats.getEnd() - aggregation.totalStats.getBegin()));
              if (throughput == null) return false;
              chart.addValue(
                  throughput.gross, 0, categoryName, subCategoryNumeric, subCategoryValue);
              break;
            }
          case OPERATION_THROUGHPUT_NET:
            {
              OperationThroughput throughput =
                  operationStats.getRepresentation(
                      OperationThroughput.class,
                      TimeUnit.MILLISECONDS.toNanos(
                          aggregation.totalStats.getEnd() - aggregation.totalStats.getBegin()));
              if (throughput == null) return false;
              chart.addValue(throughput.net, 0, categoryName, subCategoryNumeric, subCategoryValue);
              break;
            }
          case DATA_THROUGHPUT:
            {
              DataThroughput dataThroughput =
                  operationStats.getRepresentation(
                      DataThroughput.class,
                      aggregation.totalThreads,
                      aggregation.totalStats.getEnd() - aggregation.totalStats.getBegin());
              if (dataThroughput == null) return false;
              chart.addValue(
                  dataThroughput.meanThroughput / (1024.0 * 1024.0),
                  dataThroughput.deviation / (1024.0 * 1024.0),
                  categoryName,
                  subCategoryNumeric,
                  subCategoryValue);
              break;
            }
        }
      }
    }
    return true;
  }