/*
* 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.metric;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.spotify.heroic.aggregation.AggregationSession;
import com.spotify.heroic.common.Series;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import lombok.AccessLevel;
import lombok.Data;
import lombok.RequiredArgsConstructor;
/**
* A collection of metrics.
* <p>
* Metrics are constrained to the implemented types below, so far these are {@link Point}, {@link
* Event}, {@link Spread} , and {@link MetricGroup}.
* <p>
* There is a JSON serialization available in {@link com.spotify.heroic.metric.MetricCollection}
* which correctly preserves the type information of these collections.
* <p>
* This class is a carrier for _any_ of these metrics, the canonical way for accessing the
* underlying data is to first check it's type using {@link #getType()}, and then access the data
* with the appropriate cast using {@link #getDataAs(Class)}.
* <p>
* The following is an example for how you may access data from the collection.
* <p>
* <pre>
* final MetricCollection collection = ...;
*
* if (collection.getType() == MetricType.POINT) {
* final List<Point> points = collection.getDataAs(Point.class);
* ...
* }
* </pre>
*
* @author udoprog
* @see Point
* @see Spread
* @see Event
* @see MetricGroup
*/
@Data
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public abstract class MetricCollection {
final MetricType type;
final List<? extends Metric> data;
/**
* Helper method to fetch a collection of the given type, if applicable.
*
* @param expected The expected type to read.
* @return A list of the expected type.
*/
@SuppressWarnings("unchecked")
public <T> List<T> getDataAs(Class<T> expected) {
if (!expected.isAssignableFrom(type.type())) {
throw new IllegalArgumentException(
String.format("Cannot assign type (%s) to expected (%s)", type, expected));
}
return (List<T>) data;
}
/**
* Update the given aggregation with the content of this collection.
*/
public abstract void updateAggregation(
AggregationSession session, Map<String, String> tags, Set<Series> series
);
public int size() {
return data.size();
}
public boolean isEmpty() {
return data.isEmpty();
}
private static final MetricCollection empty = new EmptyMetricCollection();
public static MetricCollection empty() {
return empty;
}
// @formatter:off
private static final
Map<MetricType, Function<List<? extends Metric>, MetricCollection>> adapters = ImmutableMap.of(
MetricType.GROUP, GroupCollection::new,
MetricType.POINT, PointCollection::new,
MetricType.EVENT, EventCollection::new,
MetricType.SPREAD, SpreadCollection::new,
MetricType.CARDINALITY, CardinalityCollection::new
);
// @formatter:on
public static MetricCollection groups(List<MetricGroup> metrics) {
return new GroupCollection(metrics);
}
public static MetricCollection points(List<Point> metrics) {
return new PointCollection(metrics);
}
public static MetricCollection events(List<Event> metrics) {
return new EventCollection(metrics);
}
public static MetricCollection spreads(List<Spread> metrics) {
return new SpreadCollection(metrics);
}
public static MetricCollection cardinality(List<Payload> metrics) {
return new CardinalityCollection(metrics);
}
public static MetricCollection build(
final MetricType key, final List<? extends Metric> metrics
) {
final Function<List<? extends Metric>, MetricCollection> adapter =
checkNotNull(adapters.get(key), "adapter does not exist for type");
return adapter.apply(metrics);
}
public static MetricCollection mergeSorted(
final MetricType type, final List<List<? extends Metric>> values
) {
final List<Metric> data = ImmutableList.copyOf(Iterators.mergeSorted(
ImmutableList.copyOf(values.stream().map(Iterable::iterator).iterator()),
Metric.comparator()));
return build(type, data);
}
@SuppressWarnings("unchecked")
private static class PointCollection extends MetricCollection {
PointCollection(List<? extends Metric> points) {
super(MetricType.POINT, points);
}
@Override
public void updateAggregation(
AggregationSession session, Map<String, String> tags, Set<Series> series
) {
session.updatePoints(tags, series, adapt());
}
private List<Point> adapt() {
return (List<Point>) data;
}
}
@SuppressWarnings("unchecked")
private static class EventCollection extends MetricCollection {
EventCollection(List<? extends Metric> events) {
super(MetricType.EVENT, events);
}
@Override
public void updateAggregation(
AggregationSession session, Map<String, String> tags, Set<Series> series
) {
session.updateEvents(tags, series, adapt());
}
private List<Event> adapt() {
return (List<Event>) data;
}
}
@SuppressWarnings("unchecked")
private static class SpreadCollection extends MetricCollection {
SpreadCollection(List<? extends Metric> spread) {
super(MetricType.SPREAD, spread);
}
@Override
public void updateAggregation(
AggregationSession session, Map<String, String> tags, Set<Series> series
) {
session.updateSpreads(tags, series, adapt());
}
private List<Spread> adapt() {
return (List<Spread>) data;
}
}
@SuppressWarnings("unchecked")
private static class GroupCollection extends MetricCollection {
GroupCollection(List<? extends Metric> groups) {
super(MetricType.GROUP, groups);
}
@Override
public void updateAggregation(
AggregationSession session, Map<String, String> tags, Set<Series> series
) {
session.updateGroup(tags, series, adapt());
}
private List<MetricGroup> adapt() {
return (List<MetricGroup>) data;
}
}
@SuppressWarnings("unchecked")
private static class CardinalityCollection extends MetricCollection {
CardinalityCollection(List<? extends Metric> cardinality) {
super(MetricType.CARDINALITY, cardinality);
}
@Override
public void updateAggregation(
AggregationSession session, Map<String, String> tags, Set<Series> series
) {
session.updatePayload(tags, series, adapt());
}
private List<Payload> adapt() {
return (List<Payload>) data;
}
}
}