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

com.mgmtp.perfload.perfalyzer.binning.BinManager Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2013-2015 mgm technology partners GmbH
 *
 * 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 com.mgmtp.perfload.perfalyzer.binning;

import com.google.common.base.Charsets;
import com.mgmtp.perfload.perfalyzer.util.AggregationType;
import org.apache.commons.lang3.text.StrBuilder;
import org.apache.commons.math3.stat.StatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.channels.WritableByteChannel;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.DoubleStream;
import java.util.stream.LongStream;
import java.util.stream.LongStream.Builder;
import java.util.stream.Stream;

import static com.google.common.base.Preconditions.checkState;
import static com.mgmtp.perfload.perfalyzer.constants.PerfAlyzerConstants.DELIMITER;
import static com.mgmtp.perfload.perfalyzer.util.IoUtilities.writeLineToChannel;
import static com.mgmtp.perfload.perfalyzer.util.StrBuilderUtils.appendEscapedAndQuoted;
import static java.util.stream.IntStream.range;

/**
 * Encapsulates the actual binning logic.
 *
 * @author rnaegele
 */
public class BinManager {

	private static final Logger LOGGER = LoggerFactory.getLogger(BinManager.class);

	private final double domainStart;
	private final List bins = new ArrayList<>(50);
	private final int binSize;
	private final int indexOffset;

	/**
	 * @param domainStart
	 * 		the domain value where binning starts
	 * @param binSize
	 * 		the bin size
	 */
	public BinManager(final double domainStart, final int binSize) {
		this.domainStart = domainStart;
		this.binSize = binSize;
		this.indexOffset = (int) Math.ceil(domainStart / binSize);
	}

	/**
	 * Adds a value to be binned. This in fact incremets the count of the bin the domain value fits in. No range value is
	 * added to the bin. The specified value must be greater than or equal to the {@code domainStart} value specified
	 * in the constructor.
	 *
	 * @param domainValue
	 * 		the domain value
	 */
	public void addValue(final double domainValue) {
		addValue(domainValue, null);
	}

	/**
	 * Adds a value to be binned. This in fact incremets the count of the bin the domain value fits in. Additionally, a range
	 * values is added to the bin's list of range values for later per-bin aggragation. The specified domain value must be
	 * greater than or equal to the {@code domainStart} value specified in the constructor.
	 *
	 * @param domainValue
	 * @param rangeValue
	 */
	public void addValue(final double domainValue, final Double rangeValue) {
		double offset = domainValue - domainStart;
		checkState(offset >= 0, "Cannot add rangeValue to a bin [rangeValue (%s) < start of domain (%s)].", domainValue,
				domainStart);

		// calculate bin index for the rangeValue
		int binIndexInRange = (int) (offset / binSize);
		int existingBins = bins.size();
		Bin bin;

		if (existingBins <= binIndexInRange) {
			// create missing empty bins
			range(existingBins, binIndexInRange).forEach(i -> bins.add(new Bin(i + indexOffset)));

			// create new bin
			bin = new Bin(binIndexInRange + indexOffset);
			bins.add(bin);
		} else {
			bin = bins.get(binIndexInRange);
		}

		bin.counter++;
		if (rangeValue != null) {
			bin.values.add(rangeValue);
		}
	}

	/**
	 * Creates a {@link java.util.stream.LongStream} with the bin counts as its source.
	 *
	 * @return the stream
	 */
	public LongStream countStream() {
		Builder builder = LongStream.builder();
		bins.forEach(bin -> builder.add(bin.counter));
		return builder.build();
	}

	/**
	 * Creates a {@link java.util.stream.Stream} with the bins as its source.
	 *
	 * @return the stream
	 */
	public Stream binStream() {
		return bins.stream();
	}

	/**
	 * Creates a {@link java.util.stream.Stream} with the bins' lists of values as its source.
	 *
	 * @return the stream
	 */
	public Stream> valuesStream() {
		return bins.stream().map(Bin::getValues);
	}


	/**
	 * Creates a {@link java.util.stream.DoubleStream} with flattened bin values as its source.
	 *
	 * @return
	 */
	public DoubleStream flatValuesStream() {
		return valuesStream().flatMapToDouble(doubles -> doubles.stream().mapToDouble(d -> d));
	}


	/**
	 * Writes the bins as CSV to the specified channel. The bin counts are used as range values.
	 *
	 * @param destChannel
	 * 		the channel to write to
	 * @param domainHeader
	 * 		the domain header
	 * @param rangeHeader
	 * 		the range header
	 * @param numberFormat
	 * 		the number format
	 */
	public void toCsv(final WritableByteChannel destChannel, final String domainHeader, final String rangeHeader,
			final NumberFormat numberFormat) {
		toCsv(destChannel, domainHeader, rangeHeader, numberFormat, AggregationType.COUNT);
	}

	/**
	 * Writes the bins as CSV to the specified channel. The range values are aggregated per bin using the specified aggregation
	 * type.
	 *
	 * @param destChannel
	 * 		the channel to write to
	 * @param domainHeader
	 * 		the domain header
	 * @param rangeHeader
	 * 		the range header
	 * @param numberFormat
	 * 		the number format
	 * @param aggregationType the aggregation type
	 */
	public void toCsv(final WritableByteChannel destChannel, final String domainHeader, final String rangeHeader,
			final NumberFormat numberFormat, final AggregationType aggregationType) {
		StrBuilder sb = new StrBuilder(50);
		appendEscapedAndQuoted(sb, DELIMITER, domainHeader);
		appendEscapedAndQuoted(sb, DELIMITER, rangeHeader);
		writeLineToChannel(destChannel, sb.toString(), Charsets.UTF_8);

		for (Bin bin : bins) {
			sb = new StrBuilder();
			appendEscapedAndQuoted(sb, DELIMITER, numberFormat.format(bin.getAbsoluteBinIndex() * binSize / 1000));

			double[] values = bin.values.stream().mapToDouble(d -> d).toArray();
			switch (aggregationType) {
				case MEAN: {
					double mean = values.length == 0 ? 0d : StatUtils.mean(values);
					appendEscapedAndQuoted(sb, DELIMITER, numberFormat.format(mean));
					break;
				}
				case MEDIAN:
					double median = values.length == 0 ? 0d : StatUtils.percentile(values, 50d);
					appendEscapedAndQuoted(sb, DELIMITER, numberFormat.format(median));
					break;
				case COUNT:
					appendEscapedAndQuoted(sb, DELIMITER, numberFormat.format(bin.counter));
					break;
			}

			writeLineToChannel(destChannel, sb.toString(), Charsets.UTF_8);
		}
	}

	/**
	 * Represents a bin. Each bin has a counter and a list of values associated to the bin.
	 */
	public static class Bin {
		private final int absoluteBinIndex;
		private long counter;
		private final List values = new LinkedList<>();

		public Bin(final int absoluteBinIndex) {
			this.absoluteBinIndex = absoluteBinIndex;
		}

		public int getAbsoluteBinIndex() {
			return absoluteBinIndex;
		}

		public long getCounter() {
			return counter;
		}

		public List getValues() {
			return Collections.unmodifiableList(values);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy