/* * Copyright (c) 2015 Spotify AB. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.spotify.heroic.aggregation; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.spotify.heroic.common.DateRange; import com.spotify.heroic.common.Series; import com.spotify.heroic.common.Statistics; import com.spotify.heroic.metric.Event; import com.spotify.heroic.metric.MetricGroup; import com.spotify.heroic.metric.Payload; import com.spotify.heroic.metric.Point; import com.spotify.heroic.metric.Spread; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import lombok.Data; import lombok.RequiredArgsConstructor; /** * A special aggregation method that is a chain of other aggregation methods. * * @author udoprog */ @Data public class ChainInstance implements AggregationInstance { private final List<AggregationInstance> chain; /** * The last aggregation in the chain determines the estimated number of samples. */ @Override public long estimate(DateRange range) { return chain.get(chain.size() - 1).estimate(range); } /** * The last aggregation in the chain determines the cadence. */ @Override public long cadence() { return chain .stream() .map(AggregationInstance::cadence) .filter(c -> c >= 0) .reduce((a, b) -> b) .orElse(-1L); } @Override public AggregationInstance distributed() { final Iterator<AggregationInstance> it = chain.iterator(); final ImmutableList.Builder<AggregationInstance> chain = ImmutableList.builder(); AggregationInstance last = it.next(); if (!last.distributable()) { return EmptyInstance.INSTANCE; } while (it.hasNext()) { final AggregationInstance next = it.next(); if (!next.distributable()) { chain.add(last.distributed()); return fromList(chain.build()); } chain.add(last); last = next; } chain.add(last.distributed()); return fromList(chain.build()); } @Override public AggregationInstance reducer() { final Iterator<AggregationInstance> it = chain.iterator(); AggregationInstance last = it.next(); final ImmutableList.Builder<AggregationInstance> chain = ImmutableList.builder(); if (!last.distributable()) { chain.add(EmptyInstance.INSTANCE); chain.add(last); while (it.hasNext()) { chain.add(it.next()); } return fromList(chain.build()); } while (it.hasNext()) { final AggregationInstance next = it.next(); if (!next.distributable()) { chain.add(last.reducer()); chain.add(next); while (it.hasNext()) { chain.add(it.next()); } return fromList(chain.build()); } last = next; } return last.reducer(); } @Override public AggregationSession session( final DateRange range, final RetainQuotaWatcher watcher, final BucketStrategy bucketStrategy ) { final Iterator<AggregationInstance> it = chain.iterator(); final AggregationInstance first = it.next(); final AggregationSession head = first.session(range, watcher, bucketStrategy); final List<AggregationSession> tail = new ArrayList<>(); while (it.hasNext()) { final AggregationSession s = it.next().session(range, watcher, bucketStrategy); tail.add(s); } return new Session(head, tail); } @Override public Set<String> requiredTags() { return chain.iterator().next().requiredTags(); } private static final Joiner CHAIN_JOINER = Joiner.on(" -> "); @Override public String toString() { return "[" + CHAIN_JOINER.join(chain.stream().map(Object::toString).iterator()) + "]"; } public static AggregationInstance of(final AggregationInstance... chain) { return fromList(ImmutableList.copyOf(chain)); } public static AggregationInstance fromList(final List<AggregationInstance> chain) { final List<AggregationInstance> c = flattenChain(chain); if (c.size() == 1) { return c.iterator().next(); } return new ChainInstance(c); } private static List<AggregationInstance> flattenChain( final List<AggregationInstance> chain ) { final ImmutableList.Builder<AggregationInstance> child = ImmutableList.builder(); for (final AggregationInstance i : chain) { if (i instanceof ChainInstance) { child.addAll(flattenChain(ChainInstance.class.cast(i).getChain())); } else { child.add(i); } } return child.build(); } @RequiredArgsConstructor private static final class Session implements AggregationSession { private final AggregationSession first; private final Iterable<AggregationSession> rest; @Override public void updatePoints( Map<String, String> key, Set<Series> series, List<Point> values ) { first.updatePoints(key, series, values); } @Override public void updateEvents( Map<String, String> key, Set<Series> series, List<Event> values ) { first.updateEvents(key, series, values); } @Override public void updateSpreads( Map<String, String> key, Set<Series> series, List<Spread> values ) { first.updateSpreads(key, series, values); } @Override public void updateGroup( Map<String, String> key, Set<Series> series, List<MetricGroup> values ) { first.updateGroup(key, series, values); } @Override public void updatePayload( Map<String, String> key, Set<Series> series, List<Payload> values ) { first.updatePayload(key, series, values); } @Override public AggregationResult result() { final AggregationResult firstResult = first.result(); List<AggregationOutput> current = firstResult.getResult(); Statistics statistics = firstResult.getStatistics(); for (final AggregationSession session : rest) { for (final AggregationOutput u : current) { u.getMetrics().updateAggregation(session, u.getKey(), u.getSeries()); } final AggregationResult next = session.result(); current = next.getResult(); statistics = statistics.merge(next.getStatistics()); } return new AggregationResult(current, statistics); } @Override public String toString() { if (rest.iterator().hasNext()) { return "[" + first + ", " + Joiner.on(", ").join(rest) + "]"; } return "[" + first + "]"; } } }