/* * Copyright 2014 Rackspace * * 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.rackspacecloud.blueflood.inputs.handlers; import com.google.gson.internal.LazilyParsedNumber; import com.rackspacecloud.blueflood.inputs.formats.AggregatedPayload; import com.rackspacecloud.blueflood.types.*; import com.rackspacecloud.blueflood.utils.TimeValue; import java.io.IOError; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PreaggregateConversions { private static final Logger log = LoggerFactory.getLogger(PreaggregateConversions.class); // todo: punt on TTL private static final TimeValue DEFAULT_TTL = new TimeValue(48, TimeUnit.HOURS); private static final String NAME_DELIMITER = "//."; // NOTE: when you create objects from gson-converted json, you need to make sure to resolve numbers that // are not accessed via `doubleValue()` or `longValue()`, i.e., they are treated as `Number` instances. // the Number supplied by gson is and instance of LazilyParsedNumber and will cause breakage in certain // circumstances. e.g. calling `longValue()` when the number is obviously a double, or is used in the // type comparisions we use to determine how to serialize a number. public static Collection<IMetric> buildMetricsCollection(AggregatedPayload payload) { Collection<IMetric> metrics = new ArrayList<IMetric>(); metrics.addAll(PreaggregateConversions.convertCounters(payload.getTenantId(), payload.getTimestamp(), payload.getFlushIntervalMillis(), payload.getCounters())); metrics.addAll(PreaggregateConversions.convertGauges(payload.getTenantId(), payload.getTimestamp(), payload.getGauges())); metrics.addAll(PreaggregateConversions.convertSets(payload.getTenantId(), payload.getTimestamp(), payload.getSets())); metrics.addAll(PreaggregateConversions.convertTimers(payload.getTenantId(), payload.getTimestamp(), payload.getTimers())); return metrics; } public static Collection<PreaggregatedMetric> convertCounters(String tenant, long timestamp, long flushIntervalMillis, Collection<BluefloodCounter> counters) { List<PreaggregatedMetric> list = new ArrayList<PreaggregatedMetric>(counters.size()); for (BluefloodCounter counter : counters) { Locator locator = Locator.createLocatorFromPathComponents(tenant, counter.getName().split(NAME_DELIMITER, -1)); // flushIntervalMillis could be zero (if not specified in the statsD config). long sampleCount = flushIntervalMillis > 0 ? (long)(counter.getRate().doubleValue() * ((double)flushIntervalMillis/1000d)) : 1; Rollup rollup = new BluefloodCounterRollup() .withCount(resolveNumber(counter.getValue())) .withRate(counter.getRate().doubleValue()) .withSampleCount((int)sampleCount); PreaggregatedMetric metric = new PreaggregatedMetric(timestamp, locator, DEFAULT_TTL, rollup); list.add(metric); } return list; } public static Collection<PreaggregatedMetric> convertGauges(String tenant, long timestamp, Collection<BluefloodGauge> gauges) { List<PreaggregatedMetric> list = new ArrayList<PreaggregatedMetric>(gauges.size()); for (BluefloodGauge gauge : gauges) { Locator locator = Locator.createLocatorFromPathComponents(tenant, gauge.getName().split(NAME_DELIMITER, -1)); Points<SimpleNumber> points = new Points<SimpleNumber>(); points.add(new Points.Point<SimpleNumber>(timestamp, new SimpleNumber(resolveNumber(gauge.getValue())))); try { Rollup rollup = BluefloodGaugeRollup.buildFromRawSamples(points); PreaggregatedMetric metric = new PreaggregatedMetric(timestamp, locator, DEFAULT_TTL, rollup); list.add(metric); } catch (IOException ex) { throw new IOError(ex); } } return list; } public static Collection<PreaggregatedMetric> convertTimers(String tenant, long timestamp, Collection<BluefloodTimer> timers) { List<PreaggregatedMetric> list = new ArrayList<PreaggregatedMetric>(timers.size()); for (BluefloodTimer timer : timers) { Locator locator = Locator.createLocatorFromPathComponents(tenant, timer.getName().split(NAME_DELIMITER, -1)); BluefloodTimerRollup rollup = new BluefloodTimerRollup() .withCount(timer.getCount().longValue()) .withSampleCount(1) .withAverage(resolveNumber(timer.getAvg() == null ? 0.0d : timer.getAvg())) .withMaxValue(resolveNumber(timer.getMax() == null ? 0.0d : timer.getMax())) .withMinValue(resolveNumber(timer.getMin() == null ? 0.0d : timer.getMin())) .withCountPS(timer.getRate() == null ? 0.0d : timer.getRate().doubleValue()) .withSum(timer.getSum() == null ? 0L : timer.getSum().doubleValue()) .withVariance(Math.pow(timer.getStd() == null ? 0.0d : timer.getStd().doubleValue(), 2d)); for (Map.Entry<String, Percentile> entry : timer.getPercentiles().entrySet()) { // throw away max and sum. if (entry.getValue().getAvg() != null) { rollup.setPercentile(entry.getKey(), resolveNumber(entry.getValue().getAvg())); } } PreaggregatedMetric metric = new PreaggregatedMetric(timestamp, locator, DEFAULT_TTL, rollup); list.add(metric); } return list; } public static Collection<PreaggregatedMetric> convertSets(String tenant, long timestamp, Collection<BluefloodSet> sets) { List<PreaggregatedMetric> list = new ArrayList<PreaggregatedMetric>(sets.size()); for (BluefloodSet set : sets) { Locator locator = Locator.createLocatorFromPathComponents(tenant, set.getName().split(NAME_DELIMITER, -1)); BluefloodSetRollup rollup = new BluefloodSetRollup(); for (String value : set.getValues()) { rollup = rollup.withObject(value); } PreaggregatedMetric metric = new PreaggregatedMetric(timestamp, locator, DEFAULT_TTL, rollup); list.add(metric); } return list; } // resolve a number to a Long or double. public static Number resolveNumber(Number n) { if (n instanceof LazilyParsedNumber) { try { return n.longValue(); } catch (NumberFormatException ex) { return n.doubleValue(); } } else { // already resolved. return n; } } }