/* * 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.shell.task; import com.fasterxml.jackson.databind.ObjectMapper; import com.spotify.heroic.QueryOptions; import com.spotify.heroic.common.DateRange; import com.spotify.heroic.common.Series; import com.spotify.heroic.dagger.CoreComponent; import com.spotify.heroic.metric.FetchData; import com.spotify.heroic.metric.FetchQuotaWatcher; import com.spotify.heroic.metric.Metric; import com.spotify.heroic.metric.MetricBackendGroup; import com.spotify.heroic.metric.MetricCollection; import com.spotify.heroic.metric.MetricManager; import com.spotify.heroic.metric.MetricType; import com.spotify.heroic.metric.Tracing; import com.spotify.heroic.shell.AbstractShellTaskParams; import com.spotify.heroic.shell.ShellIO; import com.spotify.heroic.shell.ShellTask; import com.spotify.heroic.shell.TaskName; import com.spotify.heroic.shell.TaskParameters; import com.spotify.heroic.shell.TaskUsage; import com.spotify.heroic.shell.Tasks; import com.spotify.heroic.time.Clock; import dagger.Component; import eu.toolchain.async.AsyncFramework; import eu.toolchain.async.AsyncFuture; import eu.toolchain.async.LazyTransform; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; import lombok.ToString; import org.kohsuke.args4j.Option; @TaskUsage("Fetch a range of data points") @TaskName("fetch") public class Fetch implements ShellTask { private final Clock clock; private final MetricManager metrics; private final ObjectMapper mapper; private final AsyncFramework async; @Inject public Fetch( Clock clock, MetricManager metrics, @Named("application/json") ObjectMapper mapper, AsyncFramework async ) { this.clock = clock; this.metrics = metrics; this.mapper = mapper; this.async = async; } @Override public TaskParameters params() { return new Parameters(); } @Override public AsyncFuture<Void> run(final ShellIO io, final TaskParameters base) throws Exception { final Parameters params = (Parameters) base; final long now = clock.currentTimeMillis(); final Series series = Tasks.parseSeries(mapper, params.series); final long start = params.start.map(t -> Tasks.parseInstant(t, now)).orElse(now); final long end = params.end.map(t -> Tasks.parseInstant(t, now)).orElseGet(() -> defaultEnd(start)); final DateRange range = new DateRange(start, end); final DateFormat flip = new SimpleDateFormat("yyyy-MM-dd HH:mm"); final DateFormat point = new SimpleDateFormat("HH:mm:ss.SSS"); final MetricBackendGroup readGroup = metrics.useOptionalGroup(params.group); final MetricType source = MetricType.fromIdentifier(params.source).orElse(MetricType.POINT); final QueryOptions.Builder optionsBuilder = QueryOptions.builder().tracing(Tracing.fromBoolean(params.tracing)); final QueryOptions options = optionsBuilder.build(); final Consumer<MetricCollection> printMetricsCollection = g -> { Calendar current = null; Calendar last = null; for (final Metric d : g.getData()) { current = Calendar.getInstance(); current.setTime(new Date(d.getTimestamp())); if (flipped(last, current)) { io.out().println(flip.format(current.getTime())); } String fs = String.format(" %s: %s", point.format(new Date(d.getTimestamp())), d); io.out().println(fs); last = current; } }; final LazyTransform<FetchData.Result, Void> handleResult = result -> { io.out().println("TRACE:"); result.getTrace().formatTrace(io.out()); io.out().flush(); return async.resolved(); }; if (params.slicedDataFetch) { return readGroup .fetch(new FetchData.Request(source, series, range, options), FetchQuotaWatcher.NO_QUOTA, printMetricsCollection) .lazyTransform(handleResult); } else { return readGroup .fetch(new FetchData.Request(source, series, range, options), FetchQuotaWatcher.NO_QUOTA) .lazyTransform(resultData -> { resultData.getGroups().forEach(printMetricsCollection); return handleResult.transform(resultData.getResult()); }); } } private boolean flipped(Calendar last, Calendar current) { if (last == null) { return true; } if (last.get(Calendar.YEAR) != current.get(Calendar.YEAR)) { return true; } if (last.get(Calendar.MONTH) != current.get(Calendar.MONTH)) { return true; } if (last.get(Calendar.DAY_OF_MONTH) != current.get(Calendar.DAY_OF_MONTH)) { return true; } if (last.get(Calendar.HOUR_OF_DAY) != current.get(Calendar.HOUR_OF_DAY)) { return true; } return false; } private long defaultEnd(long start) { return start + TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS); } @ToString private static class Parameters extends AbstractShellTaskParams { @Option(name = "-s", aliases = {"--series"}, usage = "Series to fetch", metaVar = "<json>") private Optional<String> series = Optional.empty(); @Option(name = "--source", aliases = {"--source"}, usage = "Source to fetch", metaVar = "<events|points>") private String source = MetricType.POINT.identifier(); @Option(name = "--start", usage = "Start date", metaVar = "<datetime>") private Optional<String> start = Optional.empty(); @Option(name = "--end", usage = "End date", metaVar = "<datetime>") private Optional<String> end = Optional.empty(); @Option(name = "-g", aliases = {"--group"}, usage = "Backend group to use", metaVar = "<group>") private Optional<String> group = Optional.empty(); @Option(name = "--tracing", usage = "Enable extensive tracing") private boolean tracing = false; @Option(name = "--sliced-data-fetch", usage = "Enable sliced data fetch") private boolean slicedDataFetch = false; } public static Fetch setup(final CoreComponent core) { return DaggerFetch_C.builder().coreComponent(core).build().task(); } @Component(dependencies = CoreComponent.class) interface C { Fetch task(); } }